| /* | |
| * @test /nodynamiccopyright/ | |
| * @bug 8015831 8355753 | |
| * @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; | |
| } | |
| } | |
| // JDK-8355753 - @SuppressWarnings("this-escape") not respected for indirect leak via field | |
| public static class SuppressedIndirectLeakViaField { | |
| private final int x = this.mightLeak(); // this leak should be suppressed | |
| private int y; | |
| { | |
| y = this.mightLeak(); // this leak should be suppressed | |
| } | |
| public SuppressedIndirectLeakViaField() { | |
| this(""); | |
| } | |
| @SuppressWarnings("this-escape") | |
| private SuppressedIndirectLeakViaField(String s) { | |
| } | |
| public int mightLeak() { | |
| return 0; | |
| } | |
| } | |
| public static class UnsuppressedIndirectLeakViaField { | |
| private final int x = this.mightLeak(); // this leak should not be suppressed | |
| public UnsuppressedIndirectLeakViaField() { | |
| this(""); // this constructor does not trigger the warning | |
| } | |
| @SuppressWarnings("this-escape") | |
| private UnsuppressedIndirectLeakViaField(String s) { | |
| // this constructor does not trigger the warning (obviously; it's directly suppressed) | |
| } | |
| public UnsuppressedIndirectLeakViaField(int z) { | |
| // this constructor triggers the warning | |
| } | |
| public UnsuppressedIndirectLeakViaField(float z) { | |
| // this constructor also triggers the warning, but should not create a duplicate | |
| } | |
| public int mightLeak() { | |
| return 0; | |
| } | |
| } | |
| } |