Repository: mockito/mockito
Issue: Mock object premature garbage collected when using 'One-liner stubs'

### Problem:
'One-liner stubs' may throw an exception because the actual mock object is already garbage collected when trying to return the mock.

### Reproduce:
1. Mockito 2.23.5 using mockito-inline
2. -Xmx256m

```
public class OneLinerStubStressTest {

    public class TestClass {
        public String getStuff() {
            return "A";
        }
    }

    private static String generateString() {
        final int length = 10000;
        final StringBuilder stringBuilder = new StringBuilder(length);
        for (int i = 0; i <= length; i++) {
            stringBuilder.append("B");
        }
        return stringBuilder.toString();
    }

    @Test
    public void call_a_lot_of_mocks() {
        final String returnValue = generateString();
        for (int i = 0; i < 40000; i++) {
            final TestClass mock = when(mock(TestClass.class).getStuff())
                                   .thenReturn(returnValue).getMock();
            assertEquals(returnValue, mock.getStuff());
        }
    }
}
```
#### Expected:
Test finishes successfully

#### Actual:
```
java.lang.IllegalStateException: The mock object was garbage collected. This should not happen in normal circumstances when using public API. Typically, the test class keeps strong reference to the mock object and it prevents getting the mock collected. Mockito internally needs to keep weak references to mock objects to avoid memory leaks for certain types of MockMaker implementations. If you see this exception using Mockito public API, please file a bug. For more information see issue #1313.
        at org.mockito.internal.invocation.mockref.MockWeakReference.get(MockWeakReference.java:32)
        at org.mockito.internal.invocation.InterceptedInvocation.getMock(InterceptedInvocation.java:103)
        at org.mockito.internal.stubbing.InvocationContainerImpl.invokedMock(InvocationContainerImpl.java:157)
        at org.mockito.internal.stubbing.ConsecutiveStubbing.getMock(ConsecutiveStubbing.java:28)
```

### Analysis:
As part of #1313 `DefaultInvocationFactory` and `InterceptedInvocation` were refactored to keep week reference to the mock. This was necessary in order to get rid of strong references by the handlers which caused memory leaks. On the other hand this requires to have a strong reference to the mock in the test class in order to prevent premature garbage collection. Unfortunately if using 'One-liner stubs' (https://static.javadoc.io/org.mockito/mockito-core/2.23.4/org/mockito/Mockito.html#one_liner_stub) the mock object may be premature cleaned up. This occurs because we gain access to the `InterceptedInvocation` when calling `getMock()` (which tries to get the mock from the invocation), without having any strong reference to the mock itself. At this point the mock may already be cleaned up.

Issue comments:
Great investigation! Do you have a suggestion on how to resolve this?
Tried different approaches. I believe I found one solution that works well. Created a PR.

Pull request review comments:

Patch that resolved the issue:
diff --git a/src/main/java/org/mockito/internal/stubbing/BaseStubbing.java b/src/main/java/org/mockito/internal/stubbing/BaseStubbing.java
index 6dd99cd802..4c86cbd05d 100644
--- a/src/main/java/org/mockito/internal/stubbing/BaseStubbing.java
+++ b/src/main/java/org/mockito/internal/stubbing/BaseStubbing.java
@@ -4,17 +4,31 @@
  */
 package org.mockito.internal.stubbing;
 
-import static org.mockito.internal.exceptions.Reporter.notAnException;
-import static org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress;
-import static org.objenesis.ObjenesisHelper.newInstance;
-
 import org.mockito.internal.stubbing.answers.CallsRealMethods;
 import org.mockito.internal.stubbing.answers.Returns;
 import org.mockito.internal.stubbing.answers.ThrowsException;
+import org.mockito.stubbing.Answer;
 import org.mockito.stubbing.OngoingStubbing;
 
+import static org.mockito.internal.exceptions.Reporter.notAnException;
+import static org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress;
+import static org.objenesis.ObjenesisHelper.newInstance;
+
 public abstract class BaseStubbing<T> implements OngoingStubbing<T> {
 
+
+    // Keep strong ref to mock preventing premature garbage collection when using 'One-liner stubs'. See #1541.
+    private final Object strongMockRef;
+
+    BaseStubbing(Object mock) {
+        this.strongMockRef = mock;
+    }
+
+    @Override
+    public OngoingStubbing<T> then(Answer<?> answer) {
+        return thenAnswer(answer);
+    }
+
     @Override
     public OngoingStubbing<T> thenReturn(T value) {
         return thenAnswer(new Returns(value));
@@ -79,6 +93,12 @@ public OngoingStubbing<T> thenThrow(Class<? extends Throwable> toBeThrown, Class
     public OngoingStubbing<T> thenCallRealMethod() {
         return thenAnswer(new CallsRealMethods());
     }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <M> M getMock() {
+        return (M) this.strongMockRef;
+    }
 }
 
 
diff --git a/src/main/java/org/mockito/internal/stubbing/ConsecutiveStubbing.java b/src/main/java/org/mockito/internal/stubbing/ConsecutiveStubbing.java
index a5cdc04829..32ef8eedfd 100644
--- a/src/main/java/org/mockito/internal/stubbing/ConsecutiveStubbing.java
+++ b/src/main/java/org/mockito/internal/stubbing/ConsecutiveStubbing.java
@@ -8,23 +8,17 @@
 import org.mockito.stubbing.OngoingStubbing;
 
 public class ConsecutiveStubbing<T> extends BaseStubbing<T> {
-    private final InvocationContainerImpl invocationContainerImpl;
 
-    public ConsecutiveStubbing(InvocationContainerImpl invocationContainerImpl) {
-        this.invocationContainerImpl = invocationContainerImpl;
+    private final InvocationContainerImpl invocationContainer;
+
+    ConsecutiveStubbing(InvocationContainerImpl invocationContainer) {
+        super(invocationContainer.invokedMock());
+        this.invocationContainer = invocationContainer;
     }
 
     public OngoingStubbing<T> thenAnswer(Answer<?> answer) {
-        invocationContainerImpl.addConsecutiveAnswer(answer);
+        invocationContainer.addConsecutiveAnswer(answer);
         return this;
     }
 
-    public OngoingStubbing<T> then(Answer<?> answer) {
-        return thenAnswer(answer);
-    }
-
-    @SuppressWarnings("unchecked")
-    public <M> M getMock() {
-        return (M) invocationContainerImpl.invokedMock();
-    }
 }
diff --git a/src/main/java/org/mockito/internal/stubbing/OngoingStubbingImpl.java b/src/main/java/org/mockito/internal/stubbing/OngoingStubbingImpl.java
index cd27c392dd..419160421e 100644
--- a/src/main/java/org/mockito/internal/stubbing/OngoingStubbingImpl.java
+++ b/src/main/java/org/mockito/internal/stubbing/OngoingStubbingImpl.java
@@ -19,6 +19,7 @@ public class OngoingStubbingImpl<T> extends BaseStubbing<T> {
     private Strictness strictness;
 
     public OngoingStubbingImpl(InvocationContainerImpl invocationContainer) {
+        super(invocationContainer.invokedMock());
         this.invocationContainer = invocationContainer;
     }
 
@@ -32,22 +33,11 @@ public OngoingStubbing<T> thenAnswer(Answer<?> answer) {
         return new ConsecutiveStubbing<T>(invocationContainer);
     }
 
-    @Override
-    public OngoingStubbing<T> then(Answer<?> answer) {
-        return thenAnswer(answer);
-    }
-
     public List<Invocation> getRegisteredInvocations() {
         //TODO interface for tests
         return invocationContainer.getInvocations();
     }
 
-    @Override
-    @SuppressWarnings("unchecked")
-    public <M> M getMock() {
-        return (M) invocationContainer.invokedMock();
-    }
-
     public void setStrictness(Strictness strictness) {
         this.strictness = strictness;
     }
diff --git a/subprojects/inline/inline.gradle b/subprojects/inline/inline.gradle
index b8d2577ab5..22db17b067 100644
--- a/subprojects/inline/inline.gradle
+++ b/subprojects/inline/inline.gradle
@@ -9,5 +9,6 @@ dependencies {
 
 tasks.javadoc.enabled = false
 
-//required by the "StressTest.java"
+//required by the "StressTest.java" and "OneLinerStubStressTest.java"
 test.maxHeapSize = "256m"
+retryTest.maxHeapSize = "256m"


Test patch:
diff --git a/subprojects/inline/src/test/java/org/mockitoinline/OneLinerStubStressTest.java b/subprojects/inline/src/test/java/org/mockitoinline/OneLinerStubStressTest.java
new file mode 100644
index 0000000000..44ff95959e
--- /dev/null
+++ b/subprojects/inline/src/test/java/org/mockitoinline/OneLinerStubStressTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockitoinline;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class OneLinerStubStressTest {
+
+    public class OneLinerStubTestClass {
+        public String getStuff() {
+            return "A";
+        }
+    }
+
+    private static String generateLargeString() {
+        final int length = 2000000;
+        final StringBuilder stringBuilder = new StringBuilder(length);
+        for (int i = 0; i <= length; i++) {
+            stringBuilder.append("B");
+        }
+        return stringBuilder.toString();
+    }
+
+    @Test
+    public void call_a_lot_of_mocks_using_one_line_stubbing() {
+        //This requires smaller heap set for the test process, see "inline.gradle"
+        final String returnValue = generateLargeString();
+        for (int i = 0; i < 50000; i++) {
+            // make sure that mock object does not get cleaned up prematurely
+            final OneLinerStubTestClass mock =
+                when(mock(OneLinerStubTestClass.class).getStuff()).thenReturn(returnValue).getMock();
+            assertEquals(returnValue, mock.getStuff());
+        }
+    }
+}