From 8f5bb538aba42bffb9611546a18f14eb2ea82b70 Mon Sep 17 00:00:00 2001 From: "Archie L. Cobbs" Date: Fri, 17 Mar 2023 22:05:50 +0000 Subject: [PATCH] 8015831: Add lint check for calling overridable methods from a constructor 6557145: Warn about calling abstract methods in constructors Reviewed-by: ihse, vromero, mcimadamore --- make/CompileDemos.gmk | 17 +- make/CompileModuleTools.gmk | 2 +- make/modules/java.base/Java.gmk | 2 + make/modules/java.datatransfer/Java.gmk | 2 + make/modules/java.desktop/Java.gmk | 2 +- make/modules/java.logging/Java.gmk | 2 + make/modules/java.management/Java.gmk | 2 + make/modules/java.naming/Java.gmk | 2 + make/modules/java.net.http/Java.gmk | 26 + make/modules/java.rmi/Java.gmk | 2 + make/modules/java.security.jgss/Java.gmk | 2 + make/modules/java.security.sasl/Java.gmk | 26 + make/modules/java.sql.rowset/Java.gmk | 2 + make/modules/java.sql/Java.gmk | 2 + make/modules/java.xml.crypto/Java.gmk | 2 + make/modules/java.xml/Java.gmk | 2 +- make/modules/jdk.charsets/Java.gmk | 2 + make/modules/jdk.compiler/Gendata.gmk | 2 +- make/modules/jdk.compiler/Java.gmk | 2 + make/modules/jdk.crypto.ec/Java.gmk | 26 + make/modules/jdk.crypto.mscapi/Java.gmk | 26 + make/modules/jdk.hotspot.agent/Java.gmk | 2 +- make/modules/jdk.httpserver/Java.gmk | 2 +- make/modules/jdk.internal.jvmstat/Java.gmk | 2 + make/modules/jdk.internal.le/Java.gmk | 2 + make/modules/jdk.internal.opt/Java.gmk | 2 + make/modules/jdk.internal.vm.ci/Java.gmk | 2 + make/modules/jdk.javadoc/Gendata.gmk | 2 +- make/modules/jdk.javadoc/Java.gmk | 2 + make/modules/jdk.jcmd/Java.gmk | 2 + make/modules/jdk.jconsole/Java.gmk | 2 + make/modules/jdk.jdeps/Gensrc.gmk | 2 + make/modules/jdk.jdeps/Java.gmk | 2 + make/modules/jdk.jdeps/Launcher.gmk | 2 + make/modules/jdk.jdi/Java.gmk | 2 + make/modules/jdk.jlink/Java.gmk | 26 + make/modules/jdk.jpackage/Java.gmk | 2 + make/modules/jdk.jshell/Java.gmk | 2 + make/modules/jdk.jstatd/Java.gmk | 26 + make/modules/jdk.localedata/Java.gmk | 2 + make/modules/jdk.management/Java.gmk | 26 + make/modules/jdk.sctp/Java.gmk | 2 + make/test/BuildFailureHandler.gmk | 2 +- make/test/BuildMicrobenchmark.gmk | 4 +- .../com/sun/tools/javac/code/Lint.java | 5 + .../com/sun/tools/javac/comp/Flow.java | 1 + .../tools/javac/comp/ThisEscapeAnalyzer.java | 1479 +++++++++++++++++ .../tools/javac/resources/compiler.properties | 6 + .../tools/javac/resources/javac.properties | 4 + .../share/classes/module-info.java | 1 + src/jdk.compiler/share/man/javac.1 | 45 + .../javac/diags/examples/ThisEscape.java | 37 + .../tools/javac/warnings/ThisEscape.java | 604 +++++++ .../tools/javac/warnings/ThisEscape.out | 26 + 54 files changed, 2463 insertions(+), 18 deletions(-) create mode 100644 make/modules/java.net.http/Java.gmk create mode 100644 make/modules/java.security.sasl/Java.gmk create mode 100644 make/modules/jdk.crypto.ec/Java.gmk create mode 100644 make/modules/jdk.crypto.mscapi/Java.gmk create mode 100644 make/modules/jdk.jlink/Java.gmk create mode 100644 make/modules/jdk.jstatd/Java.gmk create mode 100644 make/modules/jdk.management/Java.gmk create mode 100644 src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java create mode 100644 test/langtools/tools/javac/diags/examples/ThisEscape.java create mode 100644 test/langtools/tools/javac/warnings/ThisEscape.java create mode 100644 test/langtools/tools/javac/warnings/ThisEscape.out diff --git a/make/CompileDemos.gmk b/make/CompileDemos.gmk index 14f5b459b27..c85bc102acb 100644 --- a/make/CompileDemos.gmk +++ b/make/CompileDemos.gmk @@ -171,41 +171,41 @@ $(BUILD_DEMO_CodePointIM_JAR): $(CODEPOINT_METAINF_SERVICE_FILE) $(eval $(call SetupBuildDemo, FileChooserDemo, \ DEMO_SUBDIR := jfc, \ - DISABLED_WARNINGS := rawtypes deprecation unchecked, \ + DISABLED_WARNINGS := rawtypes deprecation unchecked this-escape, \ )) $(eval $(call SetupBuildDemo, SwingSet2, \ DEMO_SUBDIR := jfc, \ EXTRA_COPY_TO_JAR := .java, \ EXTRA_MANIFEST_ATTR := SplashScreen-Image: resources/images/splash.png, \ - DISABLED_WARNINGS := rawtypes deprecation unchecked static serial cast, \ + DISABLED_WARNINGS := rawtypes deprecation unchecked static serial cast this-escape, \ )) $(eval $(call SetupBuildDemo, Font2DTest, \ - DISABLED_WARNINGS := rawtypes deprecation unchecked serial cast, \ + DISABLED_WARNINGS := rawtypes deprecation unchecked serial cast this-escape, \ DEMO_SUBDIR := jfc, \ )) $(eval $(call SetupBuildDemo, J2Ddemo, \ DEMO_SUBDIR := jfc, \ MAIN_CLASS := java2d.J2Ddemo, \ - DISABLED_WARNINGS := rawtypes deprecation unchecked cast lossy-conversions, \ + DISABLED_WARNINGS := rawtypes deprecation unchecked cast lossy-conversions this-escape, \ JAR_NAME := J2Ddemo, \ )) $(eval $(call SetupBuildDemo, Metalworks, \ - DISABLED_WARNINGS := rawtypes unchecked, \ + DISABLED_WARNINGS := rawtypes unchecked this-escape, \ DEMO_SUBDIR := jfc, \ )) $(eval $(call SetupBuildDemo, Notepad, \ - DISABLED_WARNINGS := rawtypes, \ + DISABLED_WARNINGS := rawtypes this-escape, \ DEMO_SUBDIR := jfc, \ )) $(eval $(call SetupBuildDemo, Stylepad, \ DEMO_SUBDIR := jfc, \ - DISABLED_WARNINGS := rawtypes unchecked, \ + DISABLED_WARNINGS := rawtypes unchecked this-escape, \ EXTRA_SRC_DIR := $(DEMO_SHARE_SRC)/jfc/Notepad, \ EXCLUDE_FILES := $(DEMO_SHARE_SRC)/jfc/Notepad/README.txt, \ )) @@ -215,11 +215,12 @@ $(eval $(call SetupBuildDemo, SampleTree, \ )) $(eval $(call SetupBuildDemo, TableExample, \ - DISABLED_WARNINGS := rawtypes unchecked deprecation, \ + DISABLED_WARNINGS := rawtypes unchecked deprecation this-escape, \ DEMO_SUBDIR := jfc, \ )) $(eval $(call SetupBuildDemo, TransparentRuler, \ + DISABLED_WARNINGS := this-escape, \ DEMO_SUBDIR := jfc, \ MAIN_CLASS := transparentruler.Ruler, \ )) diff --git a/make/CompileModuleTools.gmk b/make/CompileModuleTools.gmk index 18cd42f0612..59d651d2d96 100644 --- a/make/CompileModuleTools.gmk +++ b/make/CompileModuleTools.gmk @@ -53,7 +53,7 @@ $(eval $(call SetupJavaCompilation, BUILD_JIGSAW_TOOLS, \ build/tools/jigsaw, \ COPY := .properties .html, \ BIN := $(TOOLS_CLASSES_DIR), \ - DISABLED_WARNINGS := fallthrough, \ + DISABLED_WARNINGS := fallthrough this-escape, \ JAVAC_FLAGS := \ --add-modules jdk.jdeps \ --add-exports java.base/jdk.internal.module=ALL-UNNAMED \ diff --git a/make/modules/java.base/Java.gmk b/make/modules/java.base/Java.gmk index 3c867e27440..11fa2cac4fa 100644 --- a/make/modules/java.base/Java.gmk +++ b/make/modules/java.base/Java.gmk @@ -23,6 +23,8 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' JAVAC_FLAGS += -XDstringConcat=inline \ diff --git a/make/modules/java.datatransfer/Java.gmk b/make/modules/java.datatransfer/Java.gmk index 29feb6df9f5..12eb67cff51 100644 --- a/make/modules/java.datatransfer/Java.gmk +++ b/make/modules/java.datatransfer/Java.gmk @@ -23,6 +23,8 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' COPY += flavormap.properties diff --git a/make/modules/java.desktop/Java.gmk b/make/modules/java.desktop/Java.gmk index 108ee925efe..19cd5b7da83 100644 --- a/make/modules/java.desktop/Java.gmk +++ b/make/modules/java.desktop/Java.gmk @@ -23,7 +23,7 @@ # questions. # -DISABLED_WARNINGS_java += lossy-conversions +DISABLED_WARNINGS_java += lossy-conversions this-escape DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' COPY += .gif .png .wav .txt .xml .css .pf diff --git a/make/modules/java.logging/Java.gmk b/make/modules/java.logging/Java.gmk index f49e425718e..04409a6f331 100644 --- a/make/modules/java.logging/Java.gmk +++ b/make/modules/java.logging/Java.gmk @@ -23,5 +23,7 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' diff --git a/make/modules/java.management/Java.gmk b/make/modules/java.management/Java.gmk index f49e425718e..04409a6f331 100644 --- a/make/modules/java.management/Java.gmk +++ b/make/modules/java.management/Java.gmk @@ -23,5 +23,7 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' diff --git a/make/modules/java.naming/Java.gmk b/make/modules/java.naming/Java.gmk index 648853d6f53..519156b578e 100644 --- a/make/modules/java.naming/Java.gmk +++ b/make/modules/java.naming/Java.gmk @@ -23,6 +23,8 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' CLEAN += jndiprovider.properties diff --git a/make/modules/java.net.http/Java.gmk b/make/modules/java.net.http/Java.gmk new file mode 100644 index 00000000000..269a1195b6a --- /dev/null +++ b/make/modules/java.net.http/Java.gmk @@ -0,0 +1,26 @@ +# +# Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +DISABLED_WARNINGS_java += this-escape diff --git a/make/modules/java.rmi/Java.gmk b/make/modules/java.rmi/Java.gmk index 69448146fa4..caab1204441 100644 --- a/make/modules/java.rmi/Java.gmk +++ b/make/modules/java.rmi/Java.gmk @@ -23,6 +23,8 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' CLEAN_FILES += $(wildcard \ diff --git a/make/modules/java.security.jgss/Java.gmk b/make/modules/java.security.jgss/Java.gmk index e124e8844d3..837cdd08096 100644 --- a/make/modules/java.security.jgss/Java.gmk +++ b/make/modules/java.security.jgss/Java.gmk @@ -23,5 +23,7 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' diff --git a/make/modules/java.security.sasl/Java.gmk b/make/modules/java.security.sasl/Java.gmk new file mode 100644 index 00000000000..269a1195b6a --- /dev/null +++ b/make/modules/java.security.sasl/Java.gmk @@ -0,0 +1,26 @@ +# +# Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +DISABLED_WARNINGS_java += this-escape diff --git a/make/modules/java.sql.rowset/Java.gmk b/make/modules/java.sql.rowset/Java.gmk index 63437c6113f..bfb6f8f034e 100644 --- a/make/modules/java.sql.rowset/Java.gmk +++ b/make/modules/java.sql.rowset/Java.gmk @@ -23,6 +23,8 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' CLEAN_FILES += $(wildcard \ diff --git a/make/modules/java.sql/Java.gmk b/make/modules/java.sql/Java.gmk index e124e8844d3..837cdd08096 100644 --- a/make/modules/java.sql/Java.gmk +++ b/make/modules/java.sql/Java.gmk @@ -23,5 +23,7 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' diff --git a/make/modules/java.xml.crypto/Java.gmk b/make/modules/java.xml.crypto/Java.gmk index 2680829fb60..ce9bb131d3c 100644 --- a/make/modules/java.xml.crypto/Java.gmk +++ b/make/modules/java.xml.crypto/Java.gmk @@ -23,6 +23,8 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' COPY += .dtd .xml diff --git a/make/modules/java.xml/Java.gmk b/make/modules/java.xml/Java.gmk index d97370680a0..7d64387548b 100644 --- a/make/modules/java.xml/Java.gmk +++ b/make/modules/java.xml/Java.gmk @@ -23,7 +23,7 @@ # questions. # -DISABLED_WARNINGS_java += lossy-conversions +DISABLED_WARNINGS_java += lossy-conversions this-escape DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:$(call CommaList, javax.xml.catalog javax.xml.datatype \ javax.xml.transform javax.xml.validation javax.xml.xpath)' diff --git a/make/modules/jdk.charsets/Java.gmk b/make/modules/jdk.charsets/Java.gmk index 2745327c8e4..7198efd9fcf 100644 --- a/make/modules/jdk.charsets/Java.gmk +++ b/make/modules/jdk.charsets/Java.gmk @@ -23,4 +23,6 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + COPY += .dat diff --git a/make/modules/jdk.compiler/Gendata.gmk b/make/modules/jdk.compiler/Gendata.gmk index 5471fa1127c..24d4707c54a 100644 --- a/make/modules/jdk.compiler/Gendata.gmk +++ b/make/modules/jdk.compiler/Gendata.gmk @@ -57,7 +57,7 @@ $(eval $(call SetupJavaCompilation, COMPILE_CREATE_SYMBOLS, \ $(TOPDIR)/src/jdk.jdeps/share/classes, \ INCLUDES := build/tools/symbolgenerator com/sun/tools/classfile, \ BIN := $(BUILDTOOLS_OUTPUTDIR)/create_symbols, \ - DISABLED_WARNINGS := options, \ + DISABLED_WARNINGS := options this-escape, \ JAVAC_FLAGS := \ $(INTERIM_LANGTOOLS_ARGS) \ $(COMPILECREATESYMBOLS_ADD_EXPORTS), \ diff --git a/make/modules/jdk.compiler/Java.gmk b/make/modules/jdk.compiler/Java.gmk index 49572b6efb9..cabf0b9ba48 100644 --- a/make/modules/jdk.compiler/Java.gmk +++ b/make/modules/jdk.compiler/Java.gmk @@ -23,6 +23,8 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:-com.sun.tools.*,-jdk.internal.*,sun.tools.serialver.resources.*' JAVAC_FLAGS += -XDstringConcat=inline diff --git a/make/modules/jdk.crypto.ec/Java.gmk b/make/modules/jdk.crypto.ec/Java.gmk new file mode 100644 index 00000000000..269a1195b6a --- /dev/null +++ b/make/modules/jdk.crypto.ec/Java.gmk @@ -0,0 +1,26 @@ +# +# Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +DISABLED_WARNINGS_java += this-escape diff --git a/make/modules/jdk.crypto.mscapi/Java.gmk b/make/modules/jdk.crypto.mscapi/Java.gmk new file mode 100644 index 00000000000..269a1195b6a --- /dev/null +++ b/make/modules/jdk.crypto.mscapi/Java.gmk @@ -0,0 +1,26 @@ +# +# Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +DISABLED_WARNINGS_java += this-escape diff --git a/make/modules/jdk.hotspot.agent/Java.gmk b/make/modules/jdk.hotspot.agent/Java.gmk index 6e2d47f75fc..2819813b4f4 100644 --- a/make/modules/jdk.hotspot.agent/Java.gmk +++ b/make/modules/jdk.hotspot.agent/Java.gmk @@ -24,5 +24,5 @@ # DISABLED_WARNINGS_java += rawtypes serial cast static overrides \ - fallthrough + fallthrough this-escape COPY += .gif .png .properties diff --git a/make/modules/jdk.httpserver/Java.gmk b/make/modules/jdk.httpserver/Java.gmk index f892ebeb84c..c1b5a0d6d0c 100644 --- a/make/modules/jdk.httpserver/Java.gmk +++ b/make/modules/jdk.httpserver/Java.gmk @@ -23,4 +23,4 @@ # questions. # -DISABLED_WARNINGS_java += missing-explicit-ctor +DISABLED_WARNINGS_java += missing-explicit-ctor this-escape diff --git a/make/modules/jdk.internal.jvmstat/Java.gmk b/make/modules/jdk.internal.jvmstat/Java.gmk index c22ef922aa0..697171c156b 100644 --- a/make/modules/jdk.internal.jvmstat/Java.gmk +++ b/make/modules/jdk.internal.jvmstat/Java.gmk @@ -23,4 +23,6 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + COPY += aliasmap diff --git a/make/modules/jdk.internal.le/Java.gmk b/make/modules/jdk.internal.le/Java.gmk index 4aedb8491f8..17e7cdf71b4 100644 --- a/make/modules/jdk.internal.le/Java.gmk +++ b/make/modules/jdk.internal.le/Java.gmk @@ -23,4 +23,6 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + COPY += .properties .caps .txt diff --git a/make/modules/jdk.internal.opt/Java.gmk b/make/modules/jdk.internal.opt/Java.gmk index ef2d3bcfa7c..4299496b1fc 100644 --- a/make/modules/jdk.internal.opt/Java.gmk +++ b/make/modules/jdk.internal.opt/Java.gmk @@ -23,4 +23,6 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + COPY += .properties diff --git a/make/modules/jdk.internal.vm.ci/Java.gmk b/make/modules/jdk.internal.vm.ci/Java.gmk index 8720c97a36b..7b846bc1bd9 100644 --- a/make/modules/jdk.internal.vm.ci/Java.gmk +++ b/make/modules/jdk.internal.vm.ci/Java.gmk @@ -23,6 +23,8 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + # -parameters provides method's parameters information in class file, # JVMCI compilers make use of that information for various sanity checks. # Don't use Indy strings concatenation to have good JVMCI startup performance. diff --git a/make/modules/jdk.javadoc/Gendata.gmk b/make/modules/jdk.javadoc/Gendata.gmk index 69c93c29468..501b0540c53 100644 --- a/make/modules/jdk.javadoc/Gendata.gmk +++ b/make/modules/jdk.javadoc/Gendata.gmk @@ -55,7 +55,7 @@ $(eval $(call SetupJavaCompilation, COMPILE_CREATE_SYMBOLS, \ $(TOPDIR)/src/jdk.jdeps/share/classes, \ INCLUDES := build/tools/symbolgenerator com/sun/tools/classfile, \ BIN := $(BUILDTOOLS_OUTPUTDIR)/create_symbols_javadoc, \ - DISABLED_WARNINGS := options, \ + DISABLED_WARNINGS := options this-escape, \ JAVAC_FLAGS := \ $(INTERIM_LANGTOOLS_ARGS) \ $(COMPILECREATESYMBOLS_ADD_EXPORTS), \ diff --git a/make/modules/jdk.javadoc/Java.gmk b/make/modules/jdk.javadoc/Java.gmk index d64dd37b10b..92bbec738a0 100644 --- a/make/modules/jdk.javadoc/Java.gmk +++ b/make/modules/jdk.javadoc/Java.gmk @@ -23,4 +23,6 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + COPY += .xml .css .svg .js .js.template .png .txt diff --git a/make/modules/jdk.jcmd/Java.gmk b/make/modules/jdk.jcmd/Java.gmk index bec844a96f0..287bcbc6945 100644 --- a/make/modules/jdk.jcmd/Java.gmk +++ b/make/modules/jdk.jcmd/Java.gmk @@ -23,4 +23,6 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + COPY += _options diff --git a/make/modules/jdk.jconsole/Java.gmk b/make/modules/jdk.jconsole/Java.gmk index 8e38b9419da..7688f7f6b5c 100644 --- a/make/modules/jdk.jconsole/Java.gmk +++ b/make/modules/jdk.jconsole/Java.gmk @@ -23,6 +23,8 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + COPY += .gif .png CLEAN_FILES += $(wildcard \ diff --git a/make/modules/jdk.jdeps/Gensrc.gmk b/make/modules/jdk.jdeps/Gensrc.gmk index f26d1c2fa02..a5a72c543c7 100644 --- a/make/modules/jdk.jdeps/Gensrc.gmk +++ b/make/modules/jdk.jdeps/Gensrc.gmk @@ -26,6 +26,8 @@ include GensrcCommon.gmk include GensrcProperties.gmk +DISABLED_WARNINGS_java += this-escape + $(eval $(call SetupVersionProperties, JAVAP_VERSION, \ com/sun/tools/javap/resources/version.properties)) diff --git a/make/modules/jdk.jdeps/Java.gmk b/make/modules/jdk.jdeps/Java.gmk index 914b0c66587..99db76fe36f 100644 --- a/make/modules/jdk.jdeps/Java.gmk +++ b/make/modules/jdk.jdeps/Java.gmk @@ -25,6 +25,8 @@ COPY += .txt +DISABLED_WARNINGS_java += this-escape + CLEAN_FILES += $(wildcard \ $(TOPDIR)/src/jdk.jdeps/share/classes/com/sun/tools/jdeps/resources/*.properties \ $(TOPDIR)/src/jdk.jdeps/share/classes/com/sun/tools/javap/resources/*.properties) diff --git a/make/modules/jdk.jdeps/Launcher.gmk b/make/modules/jdk.jdeps/Launcher.gmk index 217523c48cc..4a5b5f408d5 100644 --- a/make/modules/jdk.jdeps/Launcher.gmk +++ b/make/modules/jdk.jdeps/Launcher.gmk @@ -25,6 +25,8 @@ include LauncherCommon.gmk +DISABLED_WARNINGS_java += this-escape + $(eval $(call SetupBuildLauncher, javap, \ MAIN_CLASS := com.sun.tools.javap.Main, \ CFLAGS := -DEXPAND_CLASSPATH_WILDCARDS, \ diff --git a/make/modules/jdk.jdi/Java.gmk b/make/modules/jdk.jdi/Java.gmk index 2be905a6092..49e35d9c951 100644 --- a/make/modules/jdk.jdi/Java.gmk +++ b/make/modules/jdk.jdi/Java.gmk @@ -23,6 +23,8 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + EXCLUDES += \ com/sun/tools/example/debug/bdi \ com/sun/tools/example/debug/event \ diff --git a/make/modules/jdk.jlink/Java.gmk b/make/modules/jdk.jlink/Java.gmk new file mode 100644 index 00000000000..269a1195b6a --- /dev/null +++ b/make/modules/jdk.jlink/Java.gmk @@ -0,0 +1,26 @@ +# +# Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +DISABLED_WARNINGS_java += this-escape diff --git a/make/modules/jdk.jpackage/Java.gmk b/make/modules/jdk.jpackage/Java.gmk index b5a44bba4ef..04cef0d5285 100644 --- a/make/modules/jdk.jpackage/Java.gmk +++ b/make/modules/jdk.jpackage/Java.gmk @@ -23,6 +23,8 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + COPY += .gif .png .txt .spec .script .prerm .preinst \ .postrm .postinst .list .sh .desktop .copyright .control .plist .template \ .icns .scpt .wxs .wxl .wxi .ico .bmp .tiff .service diff --git a/make/modules/jdk.jshell/Java.gmk b/make/modules/jdk.jshell/Java.gmk index a9db351c333..1a8d779d9ef 100644 --- a/make/modules/jdk.jshell/Java.gmk +++ b/make/modules/jdk.jshell/Java.gmk @@ -23,4 +23,6 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + COPY += .jsh .properties diff --git a/make/modules/jdk.jstatd/Java.gmk b/make/modules/jdk.jstatd/Java.gmk new file mode 100644 index 00000000000..269a1195b6a --- /dev/null +++ b/make/modules/jdk.jstatd/Java.gmk @@ -0,0 +1,26 @@ +# +# Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +DISABLED_WARNINGS_java += this-escape diff --git a/make/modules/jdk.localedata/Java.gmk b/make/modules/jdk.localedata/Java.gmk index fde703d7640..9f8ee95addd 100644 --- a/make/modules/jdk.localedata/Java.gmk +++ b/make/modules/jdk.localedata/Java.gmk @@ -23,6 +23,8 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + COPY += _dict _th # Exclude BreakIterator classes that are just used in compile process to generate # data files and shouldn't go in the product diff --git a/make/modules/jdk.management/Java.gmk b/make/modules/jdk.management/Java.gmk new file mode 100644 index 00000000000..269a1195b6a --- /dev/null +++ b/make/modules/jdk.management/Java.gmk @@ -0,0 +1,26 @@ +# +# Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +DISABLED_WARNINGS_java += this-escape diff --git a/make/modules/jdk.sctp/Java.gmk b/make/modules/jdk.sctp/Java.gmk index 10793d45c84..08cd8fca1ce 100644 --- a/make/modules/jdk.sctp/Java.gmk +++ b/make/modules/jdk.sctp/Java.gmk @@ -23,6 +23,8 @@ # questions. # +DISABLED_WARNINGS_java += this-escape + # No SCTP implementation on Mac OS X or AIX. These classes should be excluded. SCTP_IMPL_CLASSES = \ $(TOPDIR)/src/jdk.sctp/unix/classes/sun/nio/ch/sctp/AssociationChange.java \ diff --git a/make/test/BuildFailureHandler.gmk b/make/test/BuildFailureHandler.gmk index e69c9bf6fea..ac133dc6a68 100644 --- a/make/test/BuildFailureHandler.gmk +++ b/make/test/BuildFailureHandler.gmk @@ -51,7 +51,7 @@ $(eval $(call SetupJavaCompilation, BUILD_FAILURE_HANDLER, \ TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \ SRC := $(FH_BASEDIR)/src/share/classes $(FH_BASEDIR)/src/share/conf, \ BIN := $(FH_SUPPORT)/classes, \ - DISABLED_WARNINGS := options serial try, \ + DISABLED_WARNINGS := options serial try this-escape, \ COPY := .properties, \ CLASSPATH := $(JTREG_JAR) $(TOOLS_JAR), \ JAR := $(FH_JAR), \ diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk index 9c20b237936..1ae5e3c328b 100644 --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -76,7 +76,7 @@ $(eval $(call SetupJavaCompilation, BUILD_INDIFY, \ TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \ SRC := $(TOPDIR)/test/jdk/java/lang/invoke, \ INCLUDE_FILES := indify/Indify.java, \ - DISABLED_WARNINGS := rawtypes serial options, \ + DISABLED_WARNINGS := this-escape rawtypes serial options, \ BIN := $(MICROBENCHMARK_TOOLS_CLASSES), \ JAVAC_FLAGS := -XDstringConcat=inline -Xprefer:newer, \ )) @@ -91,7 +91,7 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \ TARGET_RELEASE := $(TARGET_RELEASE_NEWJDK_UPGRADED), \ SMALL_JAVA := false, \ CLASSPATH := $(MICROBENCHMARK_CLASSPATH), \ - DISABLED_WARNINGS := processing rawtypes cast serial preview, \ + DISABLED_WARNINGS := this-escape processing rawtypes cast serial preview, \ SRC := $(MICROBENCHMARK_SRC), \ BIN := $(MICROBENCHMARK_CLASSES), \ JAVAC_FLAGS := --add-exports java.base/sun.security.util=ALL-UNNAMED \ diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java index 3e7bdca20ff..fe65976488e 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java @@ -306,6 +306,11 @@ public class Lint */ TEXT_BLOCKS("text-blocks"), + /** + * Warn about possible 'this' escapes before subclass instance is fully initialized. + */ + THIS_ESCAPE("this-escape"), + /** * Warn about issues relating to use of try blocks (i.e. try-with-resources) */ diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java index 2a12961490d..7c3264c0594 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java @@ -224,6 +224,7 @@ public class Flow { new AssignAnalyzer().analyzeTree(env, make); new FlowAnalyzer().analyzeTree(env, make); new CaptureAnalyzer().analyzeTree(env, make); + new ThisEscapeAnalyzer(names, syms, types, log, lint).analyzeTree(env); } public void analyzeLambda(Env env, JCLambda that, TreeMaker make, boolean speculative) { 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 new file mode 100644 index 00000000000..67912090092 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java @@ -0,0 +1,1479 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.comp; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.sun.tools.javac.code.Directive; +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Lint; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.*; +import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.Types; +import com.sun.tools.javac.resources.CompilerProperties.Warnings; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.*; +import com.sun.tools.javac.tree.TreeInfo; +import com.sun.tools.javac.tree.TreeScanner; +import com.sun.tools.javac.util.Assert; +import com.sun.tools.javac.util.JCDiagnostic; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Log; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Names; +import com.sun.tools.javac.util.Pair; + +import static com.sun.tools.javac.code.Kinds.Kind.*; +import static com.sun.tools.javac.code.TypeTag.*; +import static com.sun.tools.javac.tree.JCTree.Tag.*; + +/** + * Looks for possible 'this' escapes and generates corresponding warnings. + * + *

+ * A 'this' escape is when a constructor invokes a method that could be overridden in a + * subclass, in which case the method will execute before the subclass constructor has + * finished initializing the instance. + * + *

+ * This class attempts to identify possible 'this' escapes while also striking a balance + * between false positives, false negatives, and code complexity. We do this by "executing" + * the code in candidate constructors and tracking where the original 'this' reference goes. + * If it passes to code outside of the current module, we declare a possible leak. + * + *

+ * As we analyze constructors and the methods they invoke, we track the various things in scope + * that could possibly reference the 'this' instance we are following. Such references are + * represented by {@link Ref} instances, of which there are these varieties: + *

+ * + *

+ * For each type of reference, we distinguish between direct and indirect references. + * A direct reference means the reference directly refers to the 'this' instance we are tracking. + * An indirect reference means the reference refers to the 'this' instance we are tracking through + * at least one level of indirection. + * + *

+ * Currently we do not attempt to explicitly track references stored in fields (for future study). + * + *

+ * A few notes on this implementation: + *

+ */ +class ThisEscapeAnalyzer extends TreeScanner { + + private final Names names; + private final Symtab syms; + private final Types types; + private final Log log; + private Lint lint; + +// These fields are scoped to the entire compilation unit + + /** Maps symbols of all methods to their corresponding declarations. + */ + private final Map methodMap = new LinkedHashMap<>(); + + /** Contains symbols of fields and constructors that have warnings suppressed. + */ + private final Set suppressed = new HashSet<>(); + + /** The declaring class of the constructor we're currently analyzing. + * This is the 'this' type we're trying to detect leaks of. + */ + private JCClassDecl targetClass; + + /** Snapshots of {@link #callStack} where possible 'this' escapes occur. + */ + private final ArrayList warningList = new ArrayList<>(); + +// These fields are scoped to the constructor being analyzed + + /** The declaring class of the "invoked" method we're currently analyzing. + * This is either the analyzed constructor or some method it invokes. + */ + private JCClassDecl methodClass; + + /** The current "call stack" during our analysis. The first entry is some method + * invoked from the target constructor; if empty, we're still in the constructor. + */ + private final ArrayDeque callStack = new ArrayDeque<>(); + + /** Used to terminate recursion in {@link #invokeInvokable invokeInvokable()}. + */ + private final Set>> invocations = new HashSet<>(); + + /** Snapshot of {@link #callStack} where a possible 'this' escape occurs. + * If non-null, a 'this' escape warning has been found in the current + * constructor statement, initialization block statement, or field initializer. + */ + private DiagnosticPosition[] pendingWarning; + +// These fields are scoped to the constructor or invoked method being analyzed + + /** Current lexical scope depth in the constructor or method we're currently analyzing. + * Depth zero is the outermost scope. Depth -1 means we're not analyzing. + */ + private int depth = -1; + + /** Possible 'this' references in the constructor or method we're currently analyzing. + * Null value means we're not analyzing. + */ + private RefSet refs; + +// Constructor + + ThisEscapeAnalyzer(Names names, Symtab syms, Types types, Log log, Lint lint) { + this.names = names; + this.syms = syms; + this.types = types; + this.log = log; + this.lint = lint; + } + +// +// Main method +// + + public void analyzeTree(Env env) { + + // Sanity check + Assert.check(checkInvariants(false, false)); + Assert.check(methodMap.isEmpty()); // we are not prepared to be used more than once + + // Short circuit if warnings are totally disabled + if (!lint.isEnabled(Lint.LintCategory.THIS_ESCAPE)) + return; + + // Determine which packages are exported by the containing module, if any. + // A null set indicates the unnamed module: all packages are implicitly exported. + Set exportedPackages = Optional.ofNullable(env.toplevel.modle) + .filter(mod -> mod != syms.noModule) + .filter(mod -> mod != syms.unnamedModule) + .map(mod -> mod.exports.stream() + .map(Directive.ExportsDirective::getPackage) + .collect(Collectors.toSet())) + .orElse(null); + + // Build a set of symbols for classes declared in this file + final Set classSyms = new HashSet<>(); + new TreeScanner() { + @Override + public void visitClassDef(JCClassDecl tree) { + classSyms.add(tree.sym); + super.visitClassDef(tree); + } + }.scan(env.tree); + + // Build a mapping from symbols of methods to their declarations. + // Classify all ctors and methods as analyzable and/or invokable. + // Track which constructors and fields have warnings suppressed. + new TreeScanner() { + + private Lint lint = ThisEscapeAnalyzer.this.lint; + private JCClassDecl currentClass; + private boolean nonPublicOuter; + + @Override + public void visitClassDef(JCClassDecl tree) { + JCClassDecl currentClassPrev = currentClass; + boolean nonPublicOuterPrev = nonPublicOuter; + Lint lintPrev = lint; + lint = lint.augment(tree.sym); + try { + currentClass = tree; + nonPublicOuter |= tree.sym.isAnonymous(); + nonPublicOuter |= (tree.mods.flags & Flags.PUBLIC) == 0; + + // Recurse + super.visitClassDef(tree); + } finally { + currentClass = currentClassPrev; + nonPublicOuter = nonPublicOuterPrev; + lint = lintPrev; + } + } + + @Override + public void visitVarDef(JCVariableDecl tree) { + Lint lintPrev = lint; + lint = lint.augment(tree.sym); + try { + + // Track warning suppression of fields + if (tree.sym.owner.kind == TYP && !lint.isEnabled(Lint.LintCategory.THIS_ESCAPE)) + suppressed.add(tree.sym); + + // Recurse + super.visitVarDef(tree); + } finally { + lint = lintPrev; + } + } + + @Override + public void visitMethodDef(JCMethodDecl tree) { + Lint lintPrev = lint; + lint = lint.augment(tree.sym); + try { + + // Track warning suppression of constructors + if (TreeInfo.isConstructor(tree) && !lint.isEnabled(Lint.LintCategory.THIS_ESCAPE)) + suppressed.add(tree.sym); + + // Determine if this is a constructor we should analyze + boolean extendable = currentClassIsExternallyExtendable(); + boolean analyzable = extendable && + TreeInfo.isConstructor(tree) && + (tree.sym.flags() & (Flags.PUBLIC | Flags.PROTECTED)) != 0 && + !suppressed.contains(tree.sym); + + // Determine if this method is "invokable" in an analysis (can't be overridden) + boolean invokable = !extendable || + TreeInfo.isConstructor(tree) || + (tree.mods.flags & (Flags.STATIC | Flags.PRIVATE | Flags.FINAL)) != 0; + + // Add method or constructor to map + methodMap.put(tree.sym, new MethodInfo(currentClass, tree, analyzable, invokable)); + + // Recurse + super.visitMethodDef(tree); + } finally { + lint = lintPrev; + } + } + + // Determines if the current class could be extended in some other package/module + private boolean currentClassIsExternallyExtendable() { + return !currentClass.sym.isFinal() && + currentClass.sym.isPublic() && + (exportedPackages == null || exportedPackages.contains(currentClass.sym.packge())) && + !currentClass.sym.isSealed() && + !currentClass.sym.isDirectlyOrIndirectlyLocal() && + !nonPublicOuter; + } + }.scan(env.tree); + + // Analyze non-static field initializers and initialization blocks, + // but only for classes having at least one analyzable constructor. + methodMap.values().stream() + .filter(MethodInfo::analyzable) + .map(MethodInfo::declaringClass) + .distinct() + .forEach(klass -> { + for (List defs = klass.defs; defs.nonEmpty(); defs = defs.tail) { + + // Ignore static stuff + if ((TreeInfo.flags(defs.head) & Flags.STATIC) != 0) + continue; + + // Handle field initializers + if (defs.head instanceof JCVariableDecl vardef) { + visitTopLevel(klass, () -> { + scan(vardef); + copyPendingWarning(); + }); + continue; + } + + // Handle initialization blocks + if (defs.head instanceof JCBlock block) { + visitTopLevel(klass, () -> analyzeStatements(block.stats)); + continue; + } + } + }); + + // Analyze all of the analyzable constructors we found + methodMap.values().stream() + .filter(MethodInfo::analyzable) + .forEach(methodInfo -> { + visitTopLevel(methodInfo.declaringClass(), + () -> analyzeStatements(methodInfo.declaration().body.stats)); + }); + + // Eliminate duplicate warnings. Warning B duplicates warning A if the stack trace of A is a prefix + // of the stack trace of B. For example, if constructor Foo(int x) has a leak, and constructor + // Foo() invokes this(0), then emitting a warning for Foo() would be redundant. + BiPredicate extendsAsPrefix = (warning1, warning2) -> { + if (warning2.length < warning1.length) + return false; + for (int index = 0; index < warning1.length; index++) { + if (warning2[index].getPreferredPosition() != warning1[index].getPreferredPosition()) + return false; + } + return true; + }; + + // Stack traces are ordered top to bottom, and so duplicates always have the same first element(s). + // Sort the stack traces lexicographically, so that duplicates immediately follow what they duplicate. + Comparator ordering = (warning1, warning2) -> { + for (int index1 = 0, index2 = 0; true; index1++, index2++) { + boolean end1 = index1 >= warning1.length; + boolean end2 = index2 >= warning2.length; + if (end1 && end2) + return 0; + if (end1) + return -1; + if (end2) + return 1; + int posn1 = warning1[index1].getPreferredPosition(); + int posn2 = warning2[index2].getPreferredPosition(); + int diff = Integer.compare(posn1, posn2); + if (diff != 0) + return diff; + } + }; + warningList.sort(ordering); + + // Now emit the warnings, but skipping over duplicates as we go through the list + DiagnosticPosition[] previous = null; + for (DiagnosticPosition[] warning : warningList) { + + // Skip duplicates + if (previous != null && extendsAsPrefix.test(previous, warning)) + continue; + previous = warning; + + // Emit warnings showing the entire stack trace + JCDiagnostic.Warning key = Warnings.PossibleThisEscape; + int remain = warning.length; + do { + DiagnosticPosition pos = warning[--remain]; + log.warning(Lint.LintCategory.THIS_ESCAPE, pos, key); + key = Warnings.PossibleThisEscapeLocation; + } while (remain > 0); + } + warningList.clear(); + } + + // Analyze statements, but stop at (and record) the first warning generated + private void analyzeStatements(List stats) { + for (JCStatement stat : stats) { + scan(stat); + if (copyPendingWarning()) + break; + } + } + + @Override + public void scan(JCTree tree) { + + // Check node + if (tree == null || tree.type == Type.stuckType) + return; + + // Sanity check + Assert.check(checkInvariants(true, false)); + + // Can this expression node possibly leave a 'this' reference on the stack? + boolean referenceExpressionNode; + switch (tree.getTag()) { + case SWITCH_EXPRESSION: + case CONDEXPR: + case YIELD: + case APPLY: + case NEWCLASS: + case NEWARRAY: + case LAMBDA: + case PARENS: + case ASSIGN: + case TYPECAST: + case INDEXED: + case SELECT: + case REFERENCE: + case IDENT: + case NULLCHK: + case LETEXPR: + referenceExpressionNode = true; + break; + default: + referenceExpressionNode = false; + break; + } + + // Scan node + super.scan(tree); + + // Sanity check + Assert.check(checkInvariants(true, referenceExpressionNode)); + } + +// +// Visitor methods - Class Declarations +// + + @Override + public void visitClassDef(JCClassDecl tree) { + return; // we're busy analyzing another class - skip + } + +// +// Visitor methods - Variable Declarations +// + + @Override + public void visitVarDef(JCVariableDecl tree) { + + // Skip if ignoring warnings for this field + if (suppressed.contains(tree.sym)) + return; + + // Scan initializer, if any + scan(tree.init); + if (isParamOrVar(tree.sym)) + refs.replaceExprs(depth, direct -> new VarRef(tree.sym, direct)); + else + refs.discardExprs(depth); // we don't track fields yet + } + +// +// Visitor methods - Methods +// + + @Override + public void visitMethodDef(JCMethodDecl tree) { + Assert.check(false); // we should never get here + } + + @Override + public void visitApply(JCMethodInvocation invoke) { + + // Get method symbol + MethodSymbol sym = (MethodSymbol)TreeInfo.symbolFor(invoke.meth); + + // Recurse on method expression + scan(invoke.meth); + boolean direct = refs.remove(ExprRef.direct(depth)); + boolean indirect = refs.remove(ExprRef.indirect(depth)); + + // Determine if method receiver represents a possible reference + RefSet receiverRefs = RefSet.newEmpty(); + if (sym != null && !sym.isStatic()) { + if (direct) + receiverRefs.add(ThisRef.direct()); + if (indirect) + receiverRefs.add(ThisRef.indirect()); + } + + // If "super()": ignore - we don't try to track into superclasses + if (TreeInfo.name(invoke.meth) == names._super) + return; + + // "Invoke" the method + invoke(invoke, sym, invoke.args, receiverRefs); + } + + private void invoke(JCTree site, MethodSymbol sym, List args, RefSet receiverRefs) { + + // Skip if ignoring warnings for a constructor invoked via 'this()' + if (suppressed.contains(sym)) + return; + + // Ignore final methods in java.lang.Object (getClass(), notify(), etc.) + if (sym != null && + sym.owner.kind == TYP && + sym.owner.type.tsym == syms.objectType.tsym && + sym.isFinal()) { + return; + } + + // Analyze method if possible, otherwise assume nothing + MethodInfo methodInfo = methodMap.get(sym); + if (methodInfo != null && methodInfo.invokable()) + invokeInvokable(site, args, receiverRefs, methodInfo); + else + invokeUnknown(site, args, receiverRefs); + } + + // Handle the invocation of a local analyzable method or constructor + private void invokeInvokable(JCTree site, List args, + RefSet receiverRefs, MethodInfo methodInfo) { + Assert.check(methodInfo.invokable()); + + // Collect 'this' references found in method parameters + JCMethodDecl method = methodInfo.declaration(); + RefSet paramRefs = RefSet.newEmpty(); + List params = method.params; + while (args.nonEmpty() && params.nonEmpty()) { + VarSymbol sym = params.head.sym; + scan(args.head); + refs.removeExprs(depth, direct -> paramRefs.add(new VarRef(sym, direct))); + args = args.tail; + params = params.tail; + } + + // "Invoke" the method + JCClassDecl methodClassPrev = methodClass; + methodClass = methodInfo.declaringClass(); + RefSet refsPrev = refs; + refs = RefSet.newEmpty(); + int depthPrev = depth; + depth = 0; + callStack.push(site); + try { + + // Add initial references from method receiver + refs.addAll(receiverRefs); + + // Add initial references from parameters + refs.addAll(paramRefs); + + // Stop trivial cases here + if (refs.isEmpty()) + return; + + // Stop infinite recursion here + Pair> invocation = Pair.of(site, refs.clone()); + if (!invocations.add(invocation)) + return; + + // Scan method body to "execute" it + try { + scan(method.body); + } finally { + invocations.remove(invocation); + } + + // "Return" any references from method return value + refs.mapInto(refsPrev, ReturnRef.class, direct -> new ExprRef(depthPrev, direct)); + } finally { + callStack.pop(); + depth = depthPrev; + refs = refsPrev; + methodClass = methodClassPrev; + } + } + + // Handle invocation of an unknown or overridable method or constructor + private void invokeUnknown(JCTree invoke, List args, RefSet receiverRefs) { + + // Detect leak via receiver + if (!receiverRefs.isEmpty()) + leakAt(invoke); + + // Detect leaks via method parameters + for (JCExpression arg : args) { + scan(arg); + if (refs.discardExprs(depth)) + leakAt(arg); + } + } + +// +// Visitor methods - new Foo() +// + + @Override + public void visitNewClass(JCNewClass tree) { + MethodInfo methodInfo = methodMap.get(tree.constructor); + if (methodInfo != null && methodInfo.invokable()) + invokeInvokable(tree, tree.args, outerThisRefs(tree.encl, tree.clazz.type), methodInfo); + else + invokeUnknown(tree, tree.args, outerThisRefs(tree.encl, tree.clazz.type)); + } + + // Determine 'this' references passed to a constructor via the outer 'this' instance + private RefSet outerThisRefs(JCExpression explicitOuterThis, Type type) { + RefSet outerRefs = RefSet.newEmpty(); + if (explicitOuterThis != null) { + scan(explicitOuterThis); + refs.removeExprs(depth, direct -> outerRefs.add(new OuterRef(direct))); + } else if (type.tsym != methodClass.sym && type.tsym.isEnclosedBy(methodClass.sym)) { + refs.mapInto(outerRefs, ThisRef.class, OuterRef::new); + } + return outerRefs; + } + +// +// Visitor methods - Codey Bits +// + + @Override + public void visitBlock(JCBlock tree) { + visitScoped(false, () -> super.visitBlock(tree)); + Assert.check(checkInvariants(true, false)); + } + + @Override + public void visitDoLoop(JCDoWhileLoop tree) { + visitLooped(tree, super::visitDoLoop); + } + + @Override + public void visitWhileLoop(JCWhileLoop tree) { + visitLooped(tree, super::visitWhileLoop); + } + + @Override + public void visitForLoop(JCForLoop tree) { + visitLooped(tree, super::visitForLoop); + } + + @Override + public void visitForeachLoop(JCEnhancedForLoop tree) { + visitLooped(tree, super::visitForeachLoop); + } + + @Override + public void visitSwitch(JCSwitch tree) { + visitScoped(false, () -> { + scan(tree.selector); + refs.discardExprs(depth); + scan(tree.cases); + }); + } + + @Override + public void visitSwitchExpression(JCSwitchExpression tree) { + visitScoped(true, () -> { + scan(tree.selector); + refs.discardExprs(depth); + RefSet combinedRefs = new RefSet<>(); + for (List cases = tree.cases; cases.nonEmpty(); cases = cases.tail) { + scan(cases.head.stats); + refs.replace(YieldRef.class, direct -> new ExprRef(depth, direct)); + combinedRefs.addAll(refs.removeExprs(depth)); + } + refs.addAll(combinedRefs); + }); + } + + @Override + public void visitCase(JCCase tree) { + scan(tree.stats); // no need to scan labels + } + + @Override + public void visitYield(JCYield tree) { + scan(tree.value); + refs.replaceExprs(depth, YieldRef::new); + } + + @Override + public void visitLetExpr(LetExpr tree) { + visitScoped(true, () -> super.visitLetExpr(tree)); + } + + @Override + public void visitReturn(JCReturn tree) { + scan(tree.expr); + refs.replaceExprs(depth, ReturnRef::new); + } + + @Override + public void visitLambda(JCLambda lambda) { + visitDeferred(() -> visitScoped(false, () -> super.visitLambda(lambda))); + } + + @Override + public void visitAssign(JCAssign tree) { + scan(tree.lhs); + refs.discardExprs(depth); + scan(tree.rhs); + VarSymbol sym = (VarSymbol)TreeInfo.symbolFor(tree.lhs); + if (isParamOrVar(sym)) + refs.replaceExprs(depth, direct -> new VarRef(sym, direct)); + else + refs.discardExprs(depth); // we don't track fields yet + } + + @Override + public void visitIndexed(JCArrayAccess tree) { + scan(tree.indexed); + refs.remove(ExprRef.direct(depth)); + boolean indirectRef = refs.remove(ExprRef.indirect(depth)); + scan(tree.index); + refs.discardExprs(depth); + if (indirectRef) { + refs.add(ExprRef.direct(depth)); + refs.add(ExprRef.indirect(depth)); + } + } + + @Override + public void visitSelect(JCFieldAccess tree) { + + // Scan the selected thing + scan(tree.selected); + boolean selectedDirectRef = refs.remove(ExprRef.direct(depth)); + boolean selectedIndirectRef = refs.remove(ExprRef.indirect(depth)); + + // Explicit 'this' reference? + Type.ClassType currentClassType = (Type.ClassType)methodClass.sym.type; + if (isExplicitThisReference(types, currentClassType, tree)) { + refs.mapInto(refs, ThisRef.class, direct -> new ExprRef(depth, direct)); + return; + } + + // Explicit outer 'this' reference? + Type selectedType = types.erasure(tree.selected.type); + if (selectedType.hasTag(CLASS)) { + ClassSymbol currentClassSym = (ClassSymbol)currentClassType.tsym; + ClassSymbol selectedTypeSym = (ClassSymbol)selectedType.tsym; + if (tree.name == names._this && + selectedTypeSym != currentClassSym && + currentClassSym.isEnclosedBy(selectedTypeSym)) { + refs.mapInto(refs, OuterRef.class, direct -> new ExprRef(depth, direct)); + return; + } + } + + // Methods - the "value" of a non-static method is a reference to its instance + Symbol sym = tree.sym; + if (sym.kind == MTH) { + if ((sym.flags() & Flags.STATIC) == 0) { + if (selectedDirectRef) + refs.add(ExprRef.direct(depth)); + if (selectedIndirectRef) + refs.add(ExprRef.indirect(depth)); + } + return; + } + + // Unknown + return; + } + + @Override + public void visitReference(JCMemberReference tree) { + + // Scan target expression and extract 'this' references, if any + scan(tree.expr); + boolean direct = refs.remove(ExprRef.direct(depth)); + boolean indirect = refs.remove(ExprRef.indirect(depth)); + + // Gather receiver references for deferred invocation + RefSet receiverRefs = RefSet.newEmpty(); + switch (tree.kind) { + case UNBOUND: + case STATIC: + case TOPLEVEL: + case ARRAY_CTOR: + return; + case SUPER: + refs.mapInto(receiverRefs, ThisRef.class, ThisRef::new); + break; + case BOUND: + if (direct) + receiverRefs.add(ThisRef.direct()); + if (indirect) + receiverRefs.add(ThisRef.indirect()); + break; + case IMPLICIT_INNER: + receiverRefs.addAll(outerThisRefs(null, tree.expr.type)); + break; + default: + throw new RuntimeException("non-exhaustive?"); + } + + // Treat method reference just like the equivalent lambda + visitDeferred(() -> invoke(tree, (MethodSymbol)tree.sym, List.nil(), receiverRefs)); + } + + @Override + public void visitIdent(JCIdent tree) { + + // Reference to this? + if (tree.name == names._this || tree.name == names._super) { + refs.mapInto(refs, ThisRef.class, direct -> new ExprRef(depth, direct)); + return; + } + + // Parameter or local variable? + if (isParamOrVar(tree.sym)) { + VarSymbol sym = (VarSymbol)tree.sym; + if (refs.contains(VarRef.direct(sym))) + refs.add(ExprRef.direct(depth)); + if (refs.contains(VarRef.indirect(sym))) + refs.add(ExprRef.indirect(depth)); + return; + } + + // An unqualified, non-static method invocation must reference 'this' or outer 'this'. + // The "value" of a non-static method is a reference to its instance. + if (tree.sym.kind == MTH && (tree.sym.flags() & Flags.STATIC) == 0) { + MethodSymbol sym = (MethodSymbol)tree.sym; + + // Check for implicit 'this' reference + ClassSymbol methodClassSym = methodClass.sym; + if (methodClassSym.isSubClass(sym.owner, types)) { + refs.mapInto(refs, ThisRef.class, direct -> new ExprRef(depth, direct)); + return; + } + + // Check for implicit outer 'this' reference + if (methodClassSym.isEnclosedBy((ClassSymbol)sym.owner)) { + refs.mapInto(refs, OuterRef.class, direct -> new ExprRef(depth, direct)); + return; + } + + // What could it be? + //Assert.check(false); + return; + } + + // Unknown + return; + } + + @Override + public void visitSynchronized(JCSynchronized tree) { + scan(tree.lock); + refs.discardExprs(depth); + scan(tree.body); + } + + @Override + public void visitConditional(JCConditional tree) { + scan(tree.cond); + refs.discardExprs(depth); + RefSet combinedRefs = new RefSet<>(); + scan(tree.truepart); + combinedRefs.addAll(refs.removeExprs(depth)); + scan(tree.falsepart); + combinedRefs.addAll(refs.removeExprs(depth)); + refs.addAll(combinedRefs); + } + + @Override + public void visitIf(JCIf tree) { + scan(tree.cond); + refs.discardExprs(depth); + scan(tree.thenpart); + scan(tree.elsepart); + } + + @Override + public void visitExec(JCExpressionStatement tree) { + scan(tree.expr); + refs.discardExprs(depth); + } + + @Override + public void visitThrow(JCThrow tree) { + scan(tree.expr); + if (refs.discardExprs(depth)) // we don't try to "catch" refs from thrown exceptions + leakAt(tree); + } + + @Override + public void visitAssert(JCAssert tree) { + scan(tree.cond); + refs.discardExprs(depth); + scan(tree.detail); + refs.discardExprs(depth); + } + + @Override + public void visitNewArray(JCNewArray tree) { + boolean ref = false; + if (tree.elems != null) { + for (List elems = tree.elems; elems.nonEmpty(); elems = elems.tail) { + scan(elems.head); + ref |= refs.discardExprs(depth); + } + } + if (ref) + refs.add(ExprRef.indirect(depth)); + } + + @Override + public void visitTypeCast(JCTypeCast tree) { + scan(tree.expr); + } + + @Override + public void visitConstantCaseLabel(JCConstantCaseLabel tree) { + } + + @Override + public void visitPatternCaseLabel(JCPatternCaseLabel tree) { + } + + @Override + public void visitParenthesizedPattern(JCParenthesizedPattern tree) { + } + + @Override + public void visitRecordPattern(JCRecordPattern that) { + } + + @Override + public void visitTypeTest(JCInstanceOf tree) { + scan(tree.expr); + refs.discardExprs(depth); + } + + @Override + public void visitTypeArray(JCArrayTypeTree tree) { + } + + @Override + public void visitTypeApply(JCTypeApply tree) { + } + + @Override + public void visitTypeUnion(JCTypeUnion tree) { + } + + @Override + public void visitTypeIntersection(JCTypeIntersection tree) { + } + + @Override + public void visitTypeParameter(JCTypeParameter tree) { + } + + @Override + public void visitWildcard(JCWildcard tree) { + } + + @Override + public void visitTypeBoundKind(TypeBoundKind that) { + } + + @Override + public void visitModifiers(JCModifiers tree) { + } + + @Override + public void visitAnnotation(JCAnnotation tree) { + } + + @Override + public void visitAnnotatedType(JCAnnotatedType tree) { + } + +// +// Visitor methods - Non-Reference Stuff +// + + @Override + public void visitAssignop(JCAssignOp tree) { + scan(tree.lhs); + refs.discardExprs(depth); + scan(tree.rhs); + refs.discardExprs(depth); + } + + @Override + public void visitUnary(JCUnary tree) { + scan(tree.arg); + refs.discardExprs(depth); + } + + @Override + public void visitBinary(JCBinary tree) { + scan(tree.lhs); + refs.discardExprs(depth); + scan(tree.rhs); + refs.discardExprs(depth); + } + +// Helper methods + + private void visitTopLevel(JCClassDecl klass, Runnable action) { + Assert.check(targetClass == null); + Assert.check(methodClass == null); + Assert.check(depth == -1); + Assert.check(refs == null); + targetClass = klass; + methodClass = klass; + try { + + // Add the initial 'this' reference + refs = RefSet.newEmpty(); + refs.add(ThisRef.direct()); + + // Perform action + this.visitScoped(false, action); + } finally { + Assert.check(depth == -1); + methodClass = null; + targetClass = null; + refs = null; + } + } + + // Recurse through indirect code that might get executed later, e.g., a lambda. + // We stash any pending warning and the current RefSet, then recurse into the deferred + // code (still using the current RefSet) to see if it would leak. Then we restore the + // pending warning and the current RefSet. Finally, if the deferred code would have + // leaked, we create an indirect ExprRef because it must be holding a 'this' reference. + // If the deferred code would not leak, then obviously no leak is possible, period. + private void visitDeferred(Runnable recurse) { + DiagnosticPosition[] pendingWarningPrev = pendingWarning; + pendingWarning = null; + RefSet refsPrev = refs.clone(); + boolean deferredCodeLeaks; + try { + recurse.run(); + deferredCodeLeaks = pendingWarning != null; + } finally { + refs = refsPrev; + pendingWarning = pendingWarningPrev; + } + if (deferredCodeLeaks) + refs.add(ExprRef.indirect(depth)); + } + + // Repeat loop as needed until the current set of references converges + private void visitLooped(T tree, Consumer visitor) { + visitScoped(false, () -> { + while (true) { + RefSet prevRefs = refs.clone(); + visitor.accept(tree); + if (refs.equals(prevRefs)) + break; + } + }); + } + + // Perform the given action within a new scope + private void visitScoped(boolean promote, Runnable action) { + pushScope(); + try { + + // Perform action + Assert.check(checkInvariants(true, false)); + action.run(); + Assert.check(checkInvariants(true, promote)); + + // "Promote" ExprRef's to the enclosing lexical scope, if requested + if (promote) { + Assert.check(depth > 0); + refs.removeExprs(depth, direct -> refs.add(new ExprRef(depth - 1, direct))); + } + } finally { + popScope(); + } + } + + private void pushScope() { + depth++; + } + + private void popScope() { + Assert.check(depth >= 0); + depth--; + refs.removeIf(ref -> ref.getDepth() > depth); + } + + // Note a possible 'this' reference leak at the specified location + private void leakAt(JCTree tree) { + + // Generate at most one warning per statement + if (pendingWarning != null) + return; + + // Snapshot the current stack trace + callStack.push(tree.pos()); + pendingWarning = callStack.toArray(new DiagnosticPosition[0]); + callStack.pop(); + } + + // Copy pending warning, if any, to the warning list and reset + private boolean copyPendingWarning() { + if (pendingWarning == null) + return false; + warningList.add(pendingWarning); + pendingWarning = null; + return true; + } + + // Does the symbol correspond to a parameter or local variable (not a field)? + private boolean isParamOrVar(Symbol sym) { + return sym != null && + sym.kind == VAR && + (sym.owner.kind == MTH || sym.owner.kind == VAR); + } + + /** Check if the given tree is an explicit reference to the 'this' instance of the + * class currently being compiled. This is true if tree is: + * - An unqualified 'this' identifier + * - A 'super' identifier qualified by a class name whose type is 'currentClass' or a supertype + * - A 'this' identifier qualified by a class name whose type is 'currentClass' or a supertype + * but also NOT an enclosing outer class of 'currentClass'. + */ + private boolean isExplicitThisReference(Types types, Type.ClassType currentClass, JCTree tree) { + switch (tree.getTag()) { + case PARENS: + return isExplicitThisReference(types, currentClass, TreeInfo.skipParens(tree)); + case IDENT: + { + JCIdent ident = (JCIdent)tree; + Names names = ident.name.table.names; + return ident.name == names._this; + } + case SELECT: + { + JCFieldAccess select = (JCFieldAccess)tree; + Type selectedType = types.erasure(select.selected.type); + if (!selectedType.hasTag(CLASS)) + return false; + ClassSymbol currentClassSym = (ClassSymbol)((Type.ClassType)types.erasure(currentClass)).tsym; + ClassSymbol selectedClassSym = (ClassSymbol)((Type.ClassType)selectedType).tsym; + Names names = select.name.table.names; + return currentClassSym.isSubClass(selectedClassSym, types) && + (select.name == names._super || + (select.name == names._this && + (currentClassSym == selectedClassSym || + !currentClassSym.isEnclosedBy(selectedClassSym)))); + } + default: + return false; + } + } + + // When scanning nodes we can be in one of two modes: + // (a) Looking for constructors - we do not recurse into any code blocks + // (b) Analyzing a constructor - we are tracing its possible execution paths + private boolean isAnalyzing() { + return targetClass != null; + } + +// Debugging + + // Invariant checks + private boolean checkInvariants(boolean analyzing, boolean allowExpr) { + Assert.check(analyzing == isAnalyzing()); + if (isAnalyzing()) { + Assert.check(methodClass != null); + Assert.check(targetClass != null); + Assert.check(refs != null); + Assert.check(depth >= 0); + Assert.check(refs.stream().noneMatch(ref -> ref.getDepth() > depth)); + Assert.check(allowExpr || !refs.contains(ExprRef.direct(depth))); + Assert.check(allowExpr || !refs.contains(ExprRef.indirect(depth))); + } else { + Assert.check(targetClass == null); + Assert.check(refs == null); + Assert.check(depth == -1); + Assert.check(callStack.isEmpty()); + Assert.check(pendingWarning == null); + Assert.check(invocations.isEmpty()); + } + return true; + } + +// Ref's + + /** Represents a location that could possibly hold a 'this' reference. + * + *

+ * If not "direct", the reference is found through at least one indirection. + */ + private abstract static class Ref { + + private final int depth; + private final boolean direct; + + Ref(int depth, boolean direct) { + this.depth = depth; + this.direct = direct; + } + + public int getDepth() { + return depth; + } + + public boolean isDirect() { + return direct; + } + + @Override + public int hashCode() { + return getClass().hashCode() + ^ Integer.hashCode(depth) + ^ Boolean.hashCode(direct); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj == null || obj.getClass() != getClass()) + return false; + Ref that = (Ref)obj; + return depth == that.depth + && direct == that.direct; + } + + @Override + public String toString() { + ArrayList properties = new ArrayList<>(); + addProperties(properties); + return getClass().getSimpleName() + + "[" + properties.stream().collect(Collectors.joining(",")) + "]"; + } + + protected void addProperties(ArrayList properties) { + properties.add("depth=" + depth); + properties.add(direct ? "direct" : "indirect"); + } + } + + /** A reference from the current 'this' instance. + */ + private static class ThisRef extends Ref { + + ThisRef(boolean direct) { + super(0, direct); + } + + public static ThisRef direct() { + return new ThisRef(true); + } + + public static ThisRef indirect() { + return new ThisRef(false); + } + } + + /** A reference from the current outer 'this' instance. + */ + private static class OuterRef extends Ref { + + OuterRef(boolean direct) { + super(0, direct); + } + } + + /** A reference from the expression that was just evaluated. + * In other words, a reference that's sitting on top of the stack. + */ + private static class ExprRef extends Ref { + + ExprRef(int depth, boolean direct) { + super(depth, direct); + } + + public static ExprRef direct(int depth) { + return new ExprRef(depth, true); + } + + public static ExprRef indirect(int depth) { + return new ExprRef(depth, false); + } + } + + /** A reference from the return value of the current method being "invoked". + */ + private static class ReturnRef extends Ref { + + ReturnRef(boolean direct) { + super(0, direct); + } + } + + /** A reference from the yield value of the current switch expression. + */ + private static class YieldRef extends Ref { + + YieldRef(boolean direct) { + super(0, direct); + } + } + + /** A reference from a variable. + */ + private static class VarRef extends Ref { + + private final VarSymbol sym; + + VarRef(VarSymbol sym, boolean direct) { + super(0, direct); + this.sym = sym; + } + + public VarSymbol getSymbol() { + return sym; + } + + public static VarRef direct(VarSymbol sym) { + return new VarRef(sym, true); + } + + public static VarRef indirect(VarSymbol sym) { + return new VarRef(sym, false); + } + + @Override + public int hashCode() { + return super.hashCode() + ^ Objects.hashCode(sym); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) + return true; + if (!super.equals(obj)) + return false; + VarRef that = (VarRef)obj; + return Objects.equals(sym, that.sym); + } + + @Override + protected void addProperties(ArrayList properties) { + super.addProperties(properties); + properties.add("sym=" + sym); + } + } + +// RefSet + + /** Contains locations currently known to hold a possible 'this' reference. + */ + @SuppressWarnings("serial") + private static class RefSet extends HashSet { + + public static RefSet newEmpty() { + return new RefSet<>(); + } + + /** + * Discard any {@link ExprRef}'s at the specified depth. + * Do this when discarding whatever is on top of the stack. + */ + public boolean discardExprs(int depth) { + return remove(ExprRef.direct(depth)) | remove(ExprRef.indirect(depth)); + } + + /** + * Extract any {@link ExprRef}'s at the specified depth. + */ + public RefSet removeExprs(int depth) { + return Stream.of(ExprRef.direct(depth), ExprRef.indirect(depth)) + .filter(this::remove) + .collect(Collectors.toCollection(RefSet::new)); + } + + /** + * Extract any {@link ExprRef}'s at the specified depth and do something with them. + */ + public void removeExprs(int depth, Consumer handler) { + Stream.of(ExprRef.direct(depth), ExprRef.indirect(depth)) + .filter(this::remove) + .map(ExprRef::isDirect) + .forEach(handler); + } + + /** + * Replace any references of the given type. + */ + public void replace(Class type, Function mapper) { + final List oldRefs = this.stream() + .filter(type::isInstance) + .collect(List.collector()); // avoid ConcurrentModificationException + this.removeAll(oldRefs); + oldRefs.stream() + .map(Ref::isDirect) + .map(mapper) + .forEach(this::add); + } + + /** + * Replace any {@link ExprRef}'s at the specified depth. + */ + public void replaceExprs(int depth, Function mapper) { + removeExprs(depth, direct -> add(mapper.apply(direct))); + } + + /** + * Find references of the given type, map them, and add them to {@code dest}. + */ + public void mapInto(RefSet dest, Class type, + Function mapper) { + final List newRefs = this.stream() + .filter(type::isInstance) + .map(Ref::isDirect) + .map(mapper) + .collect(List.collector()); // avoid ConcurrentModificationException + dest.addAll(newRefs); + } + + @Override + @SuppressWarnings("unchecked") + public RefSet clone() { + return (RefSet)super.clone(); + } + } + +// MethodInfo + + // Information about a constructor or method in the compilation unit + private record MethodInfo( + JCClassDecl declaringClass, // the class declaring "declaration" + JCMethodDecl declaration, // the method or constructor itself + boolean analyzable, // it's a constructor that we should analyze + boolean invokable) { // it may be safely "invoked" during analysis + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties index 16b316bb11a..2cb524dd181 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties @@ -662,6 +662,12 @@ compiler.err.not.in.profile=\ compiler.warn.forward.ref=\ reference to variable ''{0}'' before it has been initialized +compiler.warn.possible.this.escape=\ + possible ''this'' escape before subclass is fully initialized + +compiler.warn.possible.this.escape.location=\ + previous possible ''this'' escape happens here via invocation + compiler.err.illegal.self.ref=\ self-reference in initializer diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties index f4acc78a4f7..5e388b65627 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties @@ -258,6 +258,10 @@ javac.opt.Xlint.desc.strictfp=\ javac.opt.Xlint.desc.text-blocks=\ Warn about inconsistent white space characters in text block indentation. +javac.opt.Xlint.desc.this-escape=\ + Warn when a constructor invokes a method that could be overriden in an external subclass.\n\ +\ Such a method would execute before the subclass constructor completes its initialization. + javac.opt.Xlint.desc.try=\ Warn about issues relating to use of try blocks (i.e. try-with-resources). diff --git a/src/jdk.compiler/share/classes/module-info.java b/src/jdk.compiler/share/classes/module-info.java index 85458109604..4c0cca06929 100644 --- a/src/jdk.compiler/share/classes/module-info.java +++ b/src/jdk.compiler/share/classes/module-info.java @@ -184,6 +184,7 @@ import javax.tools.StandardLocation; * {@code strictfp} unnecessary use of the {@code strictfp} modifier * {@code synchronization} synchronization attempts on instances of value-based classes * {@code text-blocks} inconsistent white space characters in text block indentation + * {@code this-escape} superclass constructor leaking {@code this} before subclass initialized * {@code try} issues relating to use of {@code try} blocks * (that is, try-with-resources) * {@code unchecked} unchecked operations diff --git a/src/jdk.compiler/share/man/javac.1 b/src/jdk.compiler/share/man/javac.1 index ebc02e801ee..c9fcdb7d05c 100644 --- a/src/jdk.compiler/share/man/javac.1 +++ b/src/jdk.compiler/share/man/javac.1 @@ -776,6 +776,9 @@ instances of value-based classes. \f[V]text-blocks\f[R]: Warns about inconsistent white space characters in text block indentation. .IP \[bu] 2 +\f[V]this-escape\f[R]: Warns about constructors leaking +\f[V]this\f[R] prior to subclass initialization. +.IP \[bu] 2 \f[V]try\f[R]: Warns about the issues relating to the use of try blocks (that is, try-with-resources). .IP \[bu] 2 @@ -2213,6 +2216,48 @@ Alternately, you can remove the \f[V]static\f[R] keyword from the declaration of the method \f[V]m1\f[R]. .RE .TP +\f[V]this\-escape\f[R] +Warns about constructors leaking \f[V]this\f[R] prior to subclass +initialization. +For example, this class: +.RS +.IP +.nf +\f[CB] +public class MyClass { + public MyClass() { + System.out.println(this.hashCode()); + } +} +\f[R] +.fi +.PP +generates the following warning: +.IP +.nf +\f[CB] +MyClass.java:3: warning: [this-escape] possible 'this' escape + before subclass is fully initialized + System.out.println(this.hashCode()); + ^ +\f[R] +.fi +.PP +A 'this' escape warning is generated when a constructor does something +that might result in a subclass method being invoked before the +constructor returns. +In such cases the subclass method would be operating on an incompletely +initialized instance. +In the above example, a subclass of \f[V]MyClass\f[R] that overrides +\f[V]hashCode()\f[R] to incorporate its own fields would likely produce +an incorrect result when invoked as shown. +.PP +Warnings are only generated if a subclass could exist that is outside +of the current module (or package, if no module) being compiled. +So, for example, constructors in final and non-public classes do not +generate warnings. +.RE +.TP \f[V]try\f[R] Warns about issues relating to the use of \f[V]try\f[R] blocks, including try-with-resources statements. diff --git a/test/langtools/tools/javac/diags/examples/ThisEscape.java b/test/langtools/tools/javac/diags/examples/ThisEscape.java new file mode 100644 index 00000000000..7cdb72b4c11 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/ThisEscape.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.warn.possible.this.escape +// key: compiler.warn.possible.this.escape.location +// options: -Xlint:this-escape + +public class ThisEscape { + + public ThisEscape() { + this.method(); + } + + public final void method() { + this.hashCode(); + } +} diff --git a/test/langtools/tools/javac/warnings/ThisEscape.java b/test/langtools/tools/javac/warnings/ThisEscape.java new file mode 100644 index 00000000000..65bca36923b --- /dev/null +++ b/test/langtools/tools/javac/warnings/ThisEscape.java @@ -0,0 +1,604 @@ +/* + * @test /nodynamiccopyright/ + * @bug 8015831 + * @compile/ref=ThisEscape.out -Xlint:this-escape -XDrawDiagnostics ThisEscape.java + * @summary Verify 'this' escape detection + */ + +import java.util.function.*; + +public class ThisEscape { + + // Verify 'this' escape detection can follow references embedded as array elements + public static class ThisEscapeArrayElement { + + public ThisEscapeArrayElement() { + final Object[][] array = new Object[][] { { this } }; + ((ThisEscapeArrayElement)array[0][0]).mightLeak(); + } + + public void mightLeak() { + } + } + + // Verify basic 'this' escape detection + public static class ThisEscapeBasic { + + public ThisEscapeBasic() { + this.mightLeak(); + } + + public void mightLeak() { + } + } + + // Verify 'this' escape detection can follow references through various Java code structures + public static class ThisEscapeComplex { + + public ThisEscapeComplex() { + this.method1().mightLeak(); + } + + public void mightLeak() { + } + + private ThisEscapeComplex method1() { + while (true) { + do { + for (ThisEscapeComplex x = this.method2(); new Object().hashCode() < 10; ) { + for (int y : new int[] { 123, 456 }) { + return x; + } + } + } while (true); + } + } + + private ThisEscapeComplex method2() { + switch (new Object().hashCode()) { + case 1: + case 2: + case 3: + return null; + default: + return this.method3(); + } + } + + private ThisEscapeComplex method3() { + return switch (new Object().hashCode()) { + case 1, 2, 3 -> this.method4(); + default -> null; + }; + } + + private ThisEscapeComplex method4() { + return ThisEscapeComplex.this.method5(); + } + + private ThisEscapeComplex method5() { + final ThisEscapeComplex foo = this.method6(); + return foo; + } + + private ThisEscapeComplex method6() { + synchronized (new Object()) { + return this.method7(); + } + } + + private ThisEscapeComplex method7() { + ThisEscapeComplex x = null; + ThisEscapeComplex y = this.method8(); + if (new Object().hashCode() == 3) + return x; + else + return y; + } + + private ThisEscapeComplex method8() { + return (ThisEscapeComplex)(Object)this.method9(); + } + + private ThisEscapeComplex method9() { + return new Object().hashCode() == 3 ? this : null; + } + } + + // Verify pruning of 'this' escape warnings for various constructors + public static class ThisEscapeCtors { + + // This constructor should NOT generate a warning because it would be a + // duplicate of the warning already generated for ThisEscapeCtors(short). + public ThisEscapeCtors(char x) { + this((short)x); + } + + // This constructor should generate a warning because it invokes leaky this() + // and is accessible to subclasses. + public ThisEscapeCtors(short x) { + this(); + } + + // This constructor should generate a warning because it invokes leaky this() + // and is accessible to subclasses. + public ThisEscapeCtors(int x) { + this(); + } + + // This constructor should NOT generate a warning because it is not accessbile + // to subclasses. However, other constructors do invoke it, and that should cause + // them to generate an indirect warning. + private ThisEscapeCtors() { + this.mightLeak(); + } + + public void mightLeak() { + } + } + + // Verify 'this' escape detection in field initializers + public static class ThisEscapeFields { + + private final int field1 = this.mightLeak1(); + + private final int field2 = this.mightLeak2(); + + public int mightLeak1() { + return 123; + } + + public int mightLeak2() { + return 456; + } + } + + // Verify 'this' escape detection properly handles lambdas + public static class ThisEscapeLambda { + + public ThisEscapeLambda() { + Runnable r = () -> { + this.mightLeak(); + }; + System.out.println(r); + } + + public void mightLeak() { + } + } + + // Verify 'this' escape detection properly handles loop convergence + public static class ThisEscapeLoop { + + public ThisEscapeLoop() { + ThisEscapeLoop ref1 = this; + ThisEscapeLoop ref2 = null; + ThisEscapeLoop ref3 = null; + ThisEscapeLoop ref4 = null; + for (int i = 0; i < 100; i++) { + ref4 = ref3; + ref3 = ref2; + ref2 = ref1; + if (ref4 != null) + ref4.mightLeak(); + } + } + + public void mightLeak() { + } + } + + // Verify 'this' escape detection handles leaks via outer 'this' + public static class ThisEscapeOuterThis { + + public ThisEscapeOuterThis() { + new InnerClass(); + } + + public void mightLeak() { + } + + public class InnerClass { + + InnerClass() { + ThisEscapeOuterThis.this.mightLeak(); + } + } + + // No leak here because class 'Local' cannot be externally extended + public static void method1() { + class Local { + Local() { + this.wontLeak(); + } + void wontLeak() { + } + } + } + } + + // Verify 'this' escape detection handles leaks via passing 'this' as a parameter + public static class ThisEscapeParameter { + + public ThisEscapeParameter() { + ThisEscapeParameter.method(this); + } + + public static void method(Object obj) { + obj.hashCode(); + } + } + + // Verify 'this' escape detection properly handles leaks via recursive methods + public static class ThisEscapeRecursion { + + public ThisEscapeRecursion() { + this.noLeak(0); // no leak here + this.mightLeak(); // possible leak here + } + + public final void noLeak(int depth) { + if (depth < 10) + this.noLeak(depth - 1); + } + + public void mightLeak() { + } + } + + // Verify proper handling of 'this' escape warnings from method references + public static class ThisEscapeReference { + + // Test 1 - ReferenceKind.SUPER + + public static class Test1 { + public void mightLeak() { + } + } + + public static class Test1b extends Test1 { + public Test1b() { + new Thread(super::mightLeak); // this is a leak + } + } + + public static class Test1c extends Test1 { + public Test1c() { + new Thread(super::notify); // this is not a leak + } + } + + // Test 2 - ReferenceKind.BOUND + + public static class Test2 { + + public Test2() { + new Thread(this::mightLeak); // this is a leak + } + + public Test2(int x) { + final Test2 foo = new Test2(); + new Thread(foo::mightLeak); // this is not a leak + } + + public Test2(char x) { + new Thread(this::noLeak); // this is not a leak + } + + public void mightLeak() { + } + + private void noLeak() { + } + } + + // Test 3 - ReferenceKind.IMPLICIT_INNER + + public static class Test3 { + + public Test3() { + new Thread(Inner1::new); // this is a leak + } + + public Test3(int x) { + new Thread(Inner2::new); // this is not a leak + } + + public void mightLeak() { + } + + public class Inner1 { + public Inner1() { + Test3.this.mightLeak(); + } + } + + public class Inner2 { + public Inner2() { + new Test3().mightLeak(); + } + } + } + + // Test 4 - ReferenceKind.UNBOUND, STATIC, TOPLEVEL, ARRAY_CTOR + + public static class Test4 { + + // ReferenceKind.UNBOUND + public Test4() { + Test4.bar(Test4::sameHashCode); + } + + // ReferenceKind.STATIC + public Test4(int x) { + new Thread(Test4::noLeak); // this is not a leak + } + + // ReferenceKind.ARRAY_CTOR + public Test4(char x) { + Test4.foo(String[]::new); // this is not a leak + } + + // ReferenceKind.TOPLEVEL + public Test4(short x) { + Test4.foo(Test4::new); // this is not a leak + } + + public static void noLeak() { + } + + public static void foo(IntFunction x) { + x.hashCode(); + } + + public static void bar(BiPredicate x) { + x.hashCode(); + } + + public boolean sameHashCode(Object obj) { + return obj.hashCode() == this.hashCode(); + } + } + } + + // Verify 'this' escape detection properly handles leaks via method return values + public static class ThisEscapeReturnValue { + + public ThisEscapeReturnValue() { + final Object rval = ThisEscapeReturnValue.method(this); + ((ThisEscapeReturnValue)rval).mightLeak(); + } + + public static Object method(Object obj) { + return obj; + } + + public void mightLeak() { + } + } + + // Verify 'this' escape detection from a thrown 'this' + public static class ThisEscapeThrown extends RuntimeException { + + public ThisEscapeThrown(Object obj) { + if (obj == null) + throw this; + } + } + + // Verify proper 'this' escape interpretation of unqualified non-static method invocations + public static class ThisEscapeUnqualified { + + // This class has a leak + public static class Example1 { + + public Example1() { + new Inner(); + } + + public final class Inner { + public Inner() { + mightLeak(); // refers to Example1.mightLeak() + } + } + + public void mightLeak() { + } + } + + // This class does NOT have a leak + public static class Example2 { + + public Example2() { + new Inner(); + } + + public final class Inner { + public Inner() { + mightLeak(); // refers to Inner.mightLeak() + } + + public void mightLeak() { + } + } + + public void mightLeak() { + } + } + } + + // Verify 'this' escape detection handles leaks via switch expression yields + public static class ThisEscapeYield { + + public ThisEscapeYield(int x) { + ThisEscapeYield y = switch (x) { + case 3: + if (x > 17) + yield this; + else + yield null; + default: + yield null; + }; + if (y != null) + y.mightLeak(); + } + + public void mightLeak() { + } + } + + // Verify 'this' escape warnings can be properly suppressed on constructors + public static class ThisEscapeSuppressCtor { + + private final int x = this.mightLeak(); + + @SuppressWarnings("this-escape") + public ThisEscapeSuppressCtor() { + this.mightLeak(); + } + + public int mightLeak() { + return 0; + } + } + + // Verify 'this' escape warnings can be properly suppressed on fields + public static class ThisEscapeSuppressField { + + @SuppressWarnings("this-escape") + private final int x = this.mightLeak(); + + public ThisEscapeSuppressField() { + this.mightLeak(); + } + + public int mightLeak() { + return 0; + } + } + + // Verify 'this' escape warnings can be properly suppressed on classes + public static class ThisEscapeSuppressClass { + + @SuppressWarnings("this-escape") + private final int x = this.mightLeak(); + + @SuppressWarnings("this-escape") + public ThisEscapeSuppressClass() { + this.mightLeak(); + } + + public int mightLeak() { + return 0; + } + } + + // Verify 'this' escape detection doesn't generate certain false positives + public static class ThisEscapeNoEscapes { + + public ThisEscapeNoEscapes() { + this.noLeak1(); // invoked method is private + this.noLeak2(); // invoked method is final + ThisEscapeNoEscapes.noLeak3(); // invoked method is static + this.noLeak4(this); // parameter is 'this' but it's not leaked + this.noLeak5(new ThisEscapeNoEscapes(0)); // parameter is not 'this', so no leak + this.noLeak6(null, this, null); // method leaks 1st and 3rd parameters only + this.noLeak7(); // method does complicated stuff but doesn't leak + Runnable r1 = () -> { // lambda does not leak 'this' + if (System.out == System.err) + throw new RuntimeException(); + }; + System.out.println(r1); // lambda does not leak 'this' + Runnable r2 = () -> { // lambda leaks 'this' but is never used + this.mightLeak1(); + }; + Runnable r3 = this::mightLeak1; // reference leaks 'this' but is never used + } + + public ThisEscapeNoEscapes(int x) { + } + + public void mightLeak1() { + } + + private void noLeak1() { + } + + public final void noLeak2() { + } + + public static void noLeak3() { + } + + public static void noLeak4(ThisEscapeNoEscapes param) { + param.noLeak1(); + param.noLeak2(); + } + + public final void noLeak5(ThisEscapeNoEscapes param) { + param.mightLeak1(); + } + + public final void noLeak6(ThisEscapeNoEscapes param1, + ThisEscapeNoEscapes param2, ThisEscapeNoEscapes param3) { + if (param1 != null) + param1.mightLeak1(); + if (param2 != null) + param2.noLeak2(); + if (param3 != null) + param3.mightLeak1(); + } + + public final void noLeak7() { + ((ThisEscapeNoEscapes)(Object)this).noLeak2(); + final ThisEscapeNoEscapes obj1 = switch (new Object().hashCode()) { + case 1, 2, 3 -> null; + default -> new ThisEscapeNoEscapes(0); + }; + obj1.mightLeak1(); + } + + // PrivateClass + + private static class PrivateClass { + + PrivateClass() { + this.cantLeak(); // method is inside a private class + } + + public void cantLeak() { + } + } + + // FinalClass + + public static final class FinalClass extends ThisEscapeNoEscapes { + + public FinalClass() { + this.mightLeak1(); // class and therefore method is final + } + } + + public static void main(String[] args) { + new ThisEscapeNoEscapes(); + } + } + + // Verify 'this' escape detection doesn't warn for sealed classes with local permits + public static sealed class ThisEscapeSealed permits ThisEscapeSealed.Sub1, ThisEscapeSealed.Sub2 { + + public ThisEscapeSealed() { + this.mightLeak(); + } + + public void mightLeak() { + } + + public static final class Sub1 extends ThisEscapeSealed { + } + + public static final class Sub2 extends ThisEscapeSealed { + } + } +} diff --git a/test/langtools/tools/javac/warnings/ThisEscape.out b/test/langtools/tools/javac/warnings/ThisEscape.out new file mode 100644 index 00000000000..70c3bb06072 --- /dev/null +++ b/test/langtools/tools/javac/warnings/ThisEscape.out @@ -0,0 +1,26 @@ +ThisEscape.java:17:60: compiler.warn.possible.this.escape +ThisEscape.java:28:27: compiler.warn.possible.this.escape +ThisEscape.java:39:37: compiler.warn.possible.this.escape +ThisEscape.java:120:17: compiler.warn.possible.this.escape +ThisEscape.java:133:27: compiler.warn.possible.this.escape.location +ThisEscape.java:126:17: compiler.warn.possible.this.escape +ThisEscape.java:133:27: compiler.warn.possible.this.escape.location +ThisEscape.java:143:51: compiler.warn.possible.this.escape +ThisEscape.java:145:51: compiler.warn.possible.this.escape +ThisEscape.java:163:32: compiler.warn.possible.this.escape +ThisEscape.java:183:35: compiler.warn.possible.this.escape +ThisEscape.java:195:13: compiler.warn.possible.this.escape +ThisEscape.java:204:51: compiler.warn.possible.this.escape.location +ThisEscape.java:224:39: compiler.warn.possible.this.escape +ThisEscape.java:228:25: compiler.warn.possible.this.escape.location +ThisEscape.java:237:27: compiler.warn.possible.this.escape +ThisEscape.java:261:28: compiler.warn.possible.this.escape +ThisEscape.java:276:28: compiler.warn.possible.this.escape +ThisEscape.java:300:28: compiler.warn.possible.this.escape +ThisEscape.java:369:52: compiler.warn.possible.this.escape +ThisEscape.java:385:17: compiler.warn.possible.this.escape +ThisEscape.java:396:17: compiler.warn.possible.this.escape +ThisEscape.java:401:30: compiler.warn.possible.this.escape.location +ThisEscape.java:444:28: compiler.warn.possible.this.escape +ThisEscape.java:473:27: compiler.warn.possible.this.escape +25 warnings