Skip to content

Commit 019661a

Browse files
committed
Try to reproduce #117
1 parent e13d392 commit 019661a

File tree

1 file changed

+287
-0
lines changed

1 file changed

+287
-0
lines changed
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
package com.fasterxml.classmate;
2+
3+
import java.util.List;
4+
5+
/**
6+
* Test for [classmate#117]: StackOverflowError in 1.7.2 with recursive types
7+
*
8+
* NOTE: This test attempts to reproduce a StackOverflowError that occurs due to
9+
* infinite recursion in equals() methods when comparing recursive types:
10+
* - TypeBindings.equals() (line 221) -> ResolvedType.equals()
11+
* - ResolvedType.equals() (line 281) -> TypeBindings.equals()
12+
* - ResolvedRecursiveType.equals() (lines 157, 166) -> super.equals() + _referencedType.equals()
13+
*
14+
* The issue was introduced in commit 57fb93a which added support for resolving
15+
* raw generic types by binding type parameters to their bounds. This can create
16+
* circular dependencies in ResolvedRecursiveType instances.
17+
*/
18+
public class TestTypeResolver117 extends BaseTest
19+
{
20+
protected final TypeResolver RESOLVER = new TypeResolver();
21+
22+
// [classmate#117] StackOverflowError with recursive types
23+
// The classic recursive type pattern: T extends SelfBounded<T>
24+
// When resolving raw types, the fix for #53 creates TypeBindings by
25+
// resolving T to its bound (SelfBounded<T>), which creates infinite recursion
26+
// in equals() methods
27+
28+
// Test with a custom recursive type similar to Enum<E extends Enum<E>>
29+
static interface SelfReferential<T extends SelfReferential<T>> {
30+
T self();
31+
}
32+
33+
@SuppressWarnings("rawtypes")
34+
static abstract class RawSelfReferential implements SelfReferential {
35+
}
36+
37+
// Another recursive pattern with class
38+
static abstract class SelfBounded<T extends SelfBounded<T>> {
39+
}
40+
41+
@SuppressWarnings("rawtypes")
42+
static abstract class RawSelfBounded extends SelfBounded {
43+
}
44+
45+
// Real enum to test with
46+
static enum TestEnum {
47+
A, B, C
48+
}
49+
50+
// Class that uses raw Enum in its hierarchy
51+
@SuppressWarnings("rawtypes")
52+
static abstract class UsesRawComparable implements Comparable {
53+
}
54+
55+
// More complex recursive patterns that might trigger the issue
56+
57+
// Double-nested recursive type
58+
static abstract class DoublyRecursive<T extends DoublyRecursive<T, U>, U extends DoublyRecursive<U, T>> {
59+
}
60+
61+
@SuppressWarnings("rawtypes")
62+
static abstract class RawDoublyRecursive extends DoublyRecursive {
63+
}
64+
65+
// Recursive type that implements another recursive type
66+
static abstract class RecursiveChain<T extends RecursiveChain<T>>
67+
extends SelfBounded<RecursiveChain<T>> {
68+
}
69+
70+
@SuppressWarnings("rawtypes")
71+
static abstract class RawRecursiveChain extends RecursiveChain {
72+
}
73+
74+
/**
75+
* This test reproduces the StackOverflowError reported in issue #117.
76+
* When resolving a raw self-bounded type, the equals() comparison of recursive
77+
* types causes infinite recursion:
78+
* - ResolvedType.equals() calls TypeBindings.equals()
79+
* - TypeBindings.equals() calls ResolvedType.equals() on contained types
80+
* - For ResolvedRecursiveType, this calls both super.equals() AND
81+
* _referencedType.equals(), creating a cycle
82+
*/
83+
public void testRawSelfBoundedCausesStackOverflow() {
84+
// This should not throw StackOverflowError
85+
ResolvedType rt = RESOLVER.resolve(RawSelfBounded.class);
86+
assertNotNull(rt);
87+
// If we get here without StackOverflowError, the test passes
88+
}
89+
90+
public void testRawSelfReferentialInterface() {
91+
// Test with raw interface that has self-referential type parameter
92+
ResolvedType rt = RESOLVER.resolve(RawSelfReferential.class);
93+
assertNotNull(rt);
94+
}
95+
96+
public void testRealEnumType() {
97+
// Test with a real enum type (Enum<E extends Enum<E>>)
98+
ResolvedType rt = RESOLVER.resolve(TestEnum.class);
99+
assertNotNull(rt);
100+
}
101+
102+
public void testRawComparableViaEnum() {
103+
// Test with class implementing raw Comparable
104+
// (Comparable is the interface that Enum implements)
105+
ResolvedType rt = RESOLVER.resolve(UsesRawComparable.class);
106+
assertNotNull(rt);
107+
}
108+
109+
/**
110+
* This test checks that equals() on recursive types doesn't cause
111+
* infinite recursion
112+
*/
113+
public void testRecursiveTypeEquals() {
114+
ResolvedType rt1 = RESOLVER.resolve(RawSelfBounded.class);
115+
ResolvedType rt2 = RESOLVER.resolve(RawSelfBounded.class);
116+
117+
// This equals comparison should not cause StackOverflowError
118+
assertEquals(rt1, rt2);
119+
}
120+
121+
/**
122+
* Test equals with self-referential interface
123+
*/
124+
public void testRecursiveInterfaceEquals() {
125+
ResolvedType rt1 = RESOLVER.resolve(RawSelfReferential.class);
126+
ResolvedType rt2 = RESOLVER.resolve(RawSelfReferential.class);
127+
128+
// This equals comparison should not cause StackOverflowError
129+
assertEquals(rt1, rt2);
130+
}
131+
132+
/**
133+
* Test with doubly-recursive type (two type parameters that reference each other)
134+
*/
135+
public void testDoublyRecursiveType() {
136+
ResolvedType rt = RESOLVER.resolve(RawDoublyRecursive.class);
137+
assertNotNull(rt);
138+
}
139+
140+
/**
141+
* Test with recursive chain (recursive type extending another recursive type)
142+
*/
143+
public void testRecursiveChain() {
144+
ResolvedType rt = RESOLVER.resolve(RawRecursiveChain.class);
145+
assertNotNull(rt);
146+
}
147+
148+
/**
149+
* Test equals on doubly-recursive type
150+
*/
151+
public void testDoublyRecursiveEquals() {
152+
ResolvedType rt1 = RESOLVER.resolve(RawDoublyRecursive.class);
153+
ResolvedType rt2 = RESOLVER.resolve(RawDoublyRecursive.class);
154+
155+
// This equals comparison should not cause StackOverflowError
156+
assertEquals(rt1, rt2);
157+
}
158+
159+
/**
160+
* Direct test with java.lang.Enum
161+
* This is the most likely candidate for reproducing the issue
162+
* since Enum has the recursive pattern: Enum<E extends Enum<E>>
163+
*/
164+
public void testDirectEnumResolution() {
165+
// Try resolving Enum.class directly (as a raw type)
166+
@SuppressWarnings("rawtypes")
167+
Class enumClass = Enum.class;
168+
ResolvedType rt = RESOLVER.resolve(enumClass);
169+
assertNotNull(rt);
170+
}
171+
172+
/**
173+
* Test equals on Enum type
174+
*/
175+
public void testEnumTypeEquals() {
176+
@SuppressWarnings("rawtypes")
177+
Class enumClass = Enum.class;
178+
ResolvedType rt1 = RESOLVER.resolve(enumClass);
179+
ResolvedType rt2 = RESOLVER.resolve(enumClass);
180+
181+
// This equals comparison should not cause StackOverflowError
182+
assertEquals(rt1, rt2);
183+
}
184+
185+
/**
186+
* Stress test: resolve many recursive types to trigger caching/equality checks
187+
*/
188+
public void testMultipleRecursiveResolutions() {
189+
// Resolve the same types multiple times
190+
for (int i = 0; i < 10; i++) {
191+
ResolvedType rt1 = RESOLVER.resolve(RawSelfBounded.class);
192+
ResolvedType rt2 = RESOLVER.resolve(RawSelfReferential.class);
193+
ResolvedType rt3 = RESOLVER.resolve(RawDoublyRecursive.class);
194+
195+
// Force equals checks
196+
assertEquals(rt1, RESOLVER.resolve(RawSelfBounded.class));
197+
assertEquals(rt2, RESOLVER.resolve(RawSelfReferential.class));
198+
assertEquals(rt3, RESOLVER.resolve(RawDoublyRecursive.class));
199+
}
200+
}
201+
202+
/**
203+
* Test resolving type parameters directly
204+
*/
205+
public void testRecursiveTypeParameters() {
206+
@SuppressWarnings("rawtypes")
207+
Class enumClass = Enum.class;
208+
ResolvedType rt = RESOLVER.resolve(enumClass);
209+
210+
// Get the type parameters which should include the recursive bound
211+
List<ResolvedType> typeParams = rt.getTypeParameters();
212+
assertNotNull(typeParams);
213+
214+
// For raw Enum, the type parameter E should be resolved to its bound: Enum<E>
215+
// This creates a ResolvedRecursiveType which could trigger the equals issue
216+
if (!typeParams.isEmpty()) {
217+
ResolvedType param = typeParams.get(0);
218+
assertNotNull(param);
219+
220+
// Try to compare it
221+
assertEquals(param, param);
222+
223+
// Check if it's a recursive type
224+
ResolvedType selfRef = param.getSelfReferencedType();
225+
if (selfRef != null) {
226+
// This is a ResolvedRecursiveType - try comparing it
227+
assertEquals(param, param);
228+
}
229+
}
230+
}
231+
232+
/**
233+
* Deep inspection test: examine the structure of resolved recursive types
234+
* to understand if the cycle exists
235+
*/
236+
public void testRecursiveTypeStructure() {
237+
ResolvedType rt = RESOLVER.resolve(RawSelfBounded.class);
238+
239+
// Get parent class which should be SelfBounded with type parameters
240+
ResolvedType parent = rt.getParentClass();
241+
if (parent != null) {
242+
List<ResolvedType> parentParams = parent.getTypeParameters();
243+
if (!parentParams.isEmpty()) {
244+
ResolvedType param = parentParams.get(0);
245+
246+
// If this is recursive, it might have a self-reference
247+
ResolvedType selfRef = param.getSelfReferencedType();
248+
if (selfRef != null) {
249+
// Try to trigger the equals issue
250+
TypeBindings bindings1 = parent.getTypeBindings();
251+
TypeBindings bindings2 = parent.getTypeBindings();
252+
253+
// This should not cause StackOverflowError
254+
assertEquals(bindings1, bindings2);
255+
}
256+
}
257+
}
258+
}
259+
260+
/**
261+
* Test that explicitly verifies the issue is resolved or documents
262+
* that it could not be reproduced in unit tests
263+
*/
264+
public void testIssue117Summary() {
265+
// This test documents the investigation of issue #117
266+
267+
// Try all the patterns that should trigger the issue:
268+
// 1. Raw self-bounded type
269+
ResolvedType rt1 = RESOLVER.resolve(RawSelfBounded.class);
270+
assertEquals(rt1, rt1);
271+
272+
// 2. Raw Enum (the classic case)
273+
ResolvedType rt2 = RESOLVER.resolve(Enum.class);
274+
assertEquals(rt2, rt2);
275+
276+
// 3. Doubly recursive type
277+
ResolvedType rt3 = RESOLVER.resolve(RawDoublyRecursive.class);
278+
assertEquals(rt3, rt3);
279+
280+
// If we reach here without StackOverflowError, either:
281+
// a) The issue has been fixed in the current code
282+
// b) The issue requires a specific integration scenario not covered by unit tests
283+
// c) The issue manifests only with certain JDK versions or configurations
284+
285+
assertTrue("Issue #117 tests completed without StackOverflowError", true);
286+
}
287+
}

0 commit comments

Comments
 (0)