| /* |
| * @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<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 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; |
| } |
| } |
| } |