064628471b
Reviewed-by: vromero
769 lines
21 KiB
Java
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;
|
|
}
|
|
}
|
|
}
|