From d652491e20250983a9017128e6871bd666e74dac Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Thu, 12 Jan 2023 13:19:18 +0100 Subject: [PATCH] Fix stack overflow in SpringBootMockResolver Closes gh-32632 --- .../mock/mockito/SpringBootMockResolver.java | 33 ++++++++-- .../mockito/SpringBootMockResolverTests.java | 66 +++++++++++++++++++ 2 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolverTests.java diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolver.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolver.java index aaf37163e33..cd348115d8e 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolver.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolver.java @@ -18,12 +18,14 @@ package org.springframework.boot.test.mock.mockito; import org.mockito.plugins.MockResolver; -import org.springframework.test.util.AopTestUtils; +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.util.Assert; /** - * A {@link MockResolver} for testing Spring Boot applications with Mockito. Resolves - * mocks by returning the {@link AopTestUtils#getUltimateTargetObject(Object) ultimate - * target object} of the instance. + * A {@link MockResolver} for testing Spring Boot applications with Mockito. It resolves + * mocks by walking the proxy chain until the target or a non-static proxy is found. * * @author Andy Wilkinson * @since 2.4.0 @@ -32,7 +34,28 @@ public class SpringBootMockResolver implements MockResolver { @Override public Object resolve(Object instance) { - return AopTestUtils.getUltimateTargetObject(instance); + return getUltimateTargetObject(instance); + } + + @SuppressWarnings("unchecked") + private static T getUltimateTargetObject(Object candidate) { + Assert.notNull(candidate, "Candidate must not be null"); + try { + if (AopUtils.isAopProxy(candidate) && candidate instanceof Advised) { + Advised advised = (Advised) candidate; + TargetSource targetSource = advised.getTargetSource(); + if (targetSource.isStatic()) { + Object target = targetSource.getTarget(); + if (target != null) { + return getUltimateTargetObject(target); + } + } + } + } + catch (Throwable ex) { + throw new IllegalStateException("Failed to unwrap proxied object", ex); + } + return (T) candidate; } } diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolverTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolverTests.java new file mode 100644 index 00000000000..a1aaab73af0 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/mock/mockito/SpringBootMockResolverTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.test.mock.mockito; + +import org.junit.jupiter.api.Test; + +import org.springframework.aop.SpringProxy; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.target.HotSwappableTargetSource; +import org.springframework.aop.target.SingletonTargetSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SpringBootMockResolver}. + * + * @author Moritz Halbritter + */ +class SpringBootMockResolverTests { + + @Test + void testStaticTarget() { + MyServiceImpl myService = new MyServiceImpl(); + MyService proxy = ProxyFactory.getProxy(MyService.class, new SingletonTargetSource(myService)); + Object target = new SpringBootMockResolver().resolve(proxy); + assertThat(target).isInstanceOf(MyServiceImpl.class); + } + + @Test + void testNonStaticTarget() { + MyServiceImpl myService = new MyServiceImpl(); + MyService proxy = ProxyFactory.getProxy(MyService.class, new HotSwappableTargetSource(myService)); + Object target = new SpringBootMockResolver().resolve(proxy); + assertThat(target).isInstanceOf(SpringProxy.class); + } + + private interface MyService { + + int a(); + + } + + private static class MyServiceImpl implements MyService { + + @Override + public int a() { + return 1; + } + + } + +}