jdk-24/test/langtools/tools/javac/warnings/ThisEscape.java
2024-04-17 17:35:17 +00:00

769 lines
21 KiB
Java

/*
* @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 static 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<Test4, Object> 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 correctly follows outer instances through member classes
public static class ThisEscapeOuterRef1 {
class Inner {
void foo() {
ThisEscapeOuterRef1.this.hashCode();
}
}
public ThisEscapeOuterRef1() {
new Inner().foo();
}
}
// Verify 'this' escape correctly follows outer instances through local classes
public static class ThisEscapeOuterRef2 {
public ThisEscapeOuterRef2() {
class Inner {
void foo() {
ThisEscapeOuterRef2.this.hashCode();
}
}
new Inner().foo();
}
}
// Verify 'this' escape correctly follows outer instances through anonymous classes
public static class ThisEscapeOuterRef3 {
public ThisEscapeOuterRef3() {
new Runnable() {
public void run() {
ThisEscapeOuterRef3.this.hashCode(); // leak here
}
}.run();
}
}
// Verify 'this' escape knows that enhanced for loops invoke iterator(), etc.
public static class ThisEscapeForeach1 implements Iterable<Object> {
public ThisEscapeForeach1() {
for (Object v : this)
v.hashCode(); // possible leak here
}
@Override
public final java.util.Iterator<Object> iterator() {
return new java.util.Iterator<Object>() {
@Override
public boolean hasNext() {
return true;
}
@Override
public Object next() {
return ThisEscapeForeach1.this;
}
};
}
}
// Verify 'this' escape tracks deferred return values
public static class DeferredReturn {
public DeferredReturn(int x) {
((Supplier<DeferredReturn>)this::self).get().mightLeak(); // leak here
}
public DeferredReturn(float x) {
((Supplier<DeferredReturn>)() -> this).get().mightLeak(); // leak here
}
private final DeferredReturn self() {
return this;
}
protected void mightLeak() {
}
}
// Verify 'this' escape properly tracks variable types
public static class TypeTracking {
public TypeTracking() {
Runnable r = new Runnable() {
public void run() {
TypeTracking.this.mightLeak();
}
};
r.run(); // leak here - we know "r" has type TypeTracking$1
}
protected void mightLeak() {
}
}
// Verify 'this' escape doesn't warn for outer instances of anonymous classes that don't use them
public static class ThisEscapeOuterRef4 {
public ThisEscapeOuterRef4() {
new Runnable() {
public void run() {
// there is no leak in here
}
}.run();
}
}
// Verify 'this' escape doesn't warn for doubly-outer instance references
public static class ThisEscapeDoubleOuter {
public class Inner1 {
public Inner1() {
new Inner2().foo(); // NOT a 'this' leak
}
class Inner2 {
void foo() {
ThisEscapeDoubleOuter.this.hashCode();
}
}
}
}
// 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 {
}
}
// Verify no assertion error occurs (JDK-8317336)
public static class ThisEscapeAssertionError {
public ThisEscapeAssertionError() {
System.out.println((Supplier<Object>)() -> this);
}
}
// Verify no assertion error occurs (JDK-8317336)
public static class ThisEscapeAssertionError2 {
public ThisEscapeAssertionError2() {
ThisEscapeAssertionError2[] array = new ThisEscapeAssertionError2[] { this };
for (Object obj : array)
;
}
}
// Verify no infinite recursion loop occurs (JDK-8317818)
public static class ThisEscapeRecursionExplosion {
private Object obj;
public ThisEscapeRecursionExplosion() {
getObject();
}
private Object getObject() {
if (this.obj == null) {
this.obj = new Object();
getObject().hashCode();
getObject().hashCode();
getObject().hashCode();
getObject().hashCode();
getObject().hashCode();
getObject().hashCode();
getObject().hashCode();
getObject().hashCode();
getObject().hashCode();
getObject().hashCode();
getObject().hashCode();
getObject().hashCode();
getObject().hashCode();
getObject().hashCode();
getObject().hashCode();
getObject().hashCode();
}
return this.obj;
}
}
}