Repository: mockito/mockito
Issue: StackOverflow while mocking a ThreadLocal on Mockito 5.1.1

Hello Folks,

I'm upgrading Mockito in a Java application from 4.9.0 to 5.1.1. We are on JDK 11 (I personally use Temurin 11.0.13).

So, I've a test failing due to a StackOverflow

```
java.lang.StackOverflowError
	at org.mockito.internal.creation.bytebuddy.inject.MockMethodDispatcher.get(MockMethodDispatcher.java:34)
	at java.base/java.lang.ThreadLocal.get(ThreadLocal.java:162)
	at org.mockito.internal.creation.bytebuddy.MockMethodAdvice$SelfCallInfo.checkSelfCall(MockMethodAdvice.java:368)
	at org.mockito.internal.creation.bytebuddy.MockMethodAdvice.isMocked(MockMethodAdvice.java:171)
```
This happens when we mock a class that extends `ThreadLocal`. But I verified that it happens also by mocking `ThreadLocal` as well.

Sounds like a bug, but you are the experts here.

Best regards,
Roberto

Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java
index 1dd744171f..6074eef038 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java
@@ -168,7 +168,7 @@ public boolean isMock(Object instance) {
 
     @Override
     public boolean isMocked(Object instance) {
-        return selfCallInfo.checkSelfCall(instance) && isMock(instance);
+        return isMock(instance) && selfCallInfo.checkSelfCall(instance);
     }
 
     @Override


Test patch:
diff --git a/src/test/java/org/mockitousage/bugs/ThreadLocalTest.java b/src/test/java/org/mockitousage/bugs/ThreadLocalTest.java
new file mode 100644
index 0000000000..8c113d90a6
--- /dev/null
+++ b/src/test/java/org/mockitousage/bugs/ThreadLocalTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2007 Mockito contributors
+ * This program is made available under the terms of the MIT License.
+ */
+package org.mockitousage.bugs;
+
+import static org.mockito.Mockito.RETURNS_MOCKS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.mockitoutil.TestBase;
+
+/**
+ * This was an issue reported in #2905. Mocking {@link ThreadLocal} or classes extending {@link ThreadLocal} was
+ * throwing a {@link StackOverflowError}.
+ */
+public class ThreadLocalTest extends TestBase {
+
+    @Test
+    public void mock_ThreadLocal_does_not_raise_StackOverflowError() {
+        StackOverflowError stackOverflowError =
+                Assertions.catchThrowableOfType(
+                        () -> {
+                            mock(ThreadLocal.class, RETURNS_MOCKS);
+                        },
+                        StackOverflowError.class);
+        Assertions.assertThat(stackOverflowError).isNull();
+    }
+
+    @Test
+    public void mock_class_extending_ThreadLocal_does_not_raise_StackOverflowError() {
+        StackOverflowError stackOverflowError =
+                Assertions.catchThrowableOfType(
+                        () -> {
+                            mock(SomeThreadLocal.class, RETURNS_MOCKS);
+                        },
+                        StackOverflowError.class);
+        Assertions.assertThat(stackOverflowError).isNull();
+    }
+
+    @Test
+    public void spy_ThreadLocal_does_not_raise_StackOverflowError() {
+        StackOverflowError stackOverflowError =
+                Assertions.catchThrowableOfType(
+                        () -> {
+                            spy(ThreadLocal.class);
+                        },
+                        StackOverflowError.class);
+        Assertions.assertThat(stackOverflowError).isNull();
+    }
+
+    @Test
+    public void spy_class_extending_ThreadLocal_does_not_raise_StackOverflowError() {
+        StackOverflowError stackOverflowError =
+                Assertions.catchThrowableOfType(
+                        () -> {
+                            spy(SomeThreadLocal.class);
+                        },
+                        StackOverflowError.class);
+        Assertions.assertThat(stackOverflowError).isNull();
+    }
+
+    static class SomeThreadLocal<T> extends ThreadLocal<T> {}
+}