/* * @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 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 { public ThisEscapeForeach1() { for (Object v : this) v.hashCode(); // possible leak here } @Override public final java.util.Iterator iterator() { return new java.util.Iterator() { @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)this::self).get().mightLeak(); // leak here } public DeferredReturn(float x) { ((Supplier)() -> 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)() -> 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; } } }