Skip to content

Commit fbb5ffe

Browse files
committed
Avoid infinite cycle resolving generic type that refers itself
This commit improves type resolution for a unresolved generic type that uses itself in its upper bound declaration. Closes gh-16451
1 parent 8a04e2c commit fbb5ffe

File tree

4 files changed

+112
-4
lines changed

4 files changed

+112
-4
lines changed

spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/TypeUtils.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,15 +248,28 @@ public String visitTypeVariable(TypeVariable t, TypeDescriptor descriptor) {
248248
TypeMirror typeMirror = descriptor.resolveGeneric(t);
249249
if (typeMirror != null) {
250250
if (typeMirror instanceof TypeVariable) {
251-
// Still unresolved, let's use upper bound
252-
return visit(((TypeVariable) typeMirror).getUpperBound(), descriptor);
251+
TypeVariable typeVariable = (TypeVariable) typeMirror;
252+
// Still unresolved, let's use the upper bound, checking first if
253+
// a cycle may exist
254+
if (!hasCycle(typeVariable)) {
255+
return visit(typeVariable.getUpperBound(), descriptor);
256+
}
253257
}
254258
else {
255259
return visit(typeMirror, descriptor);
256260
}
257261
}
258-
// Unresolved generics, use upper bound
259-
return visit(t.getUpperBound(), descriptor);
262+
// Fallback to simple representation of the upper bound
263+
return defaultAction(t.getUpperBound(), descriptor);
264+
}
265+
266+
private boolean hasCycle(TypeVariable variable) {
267+
TypeMirror upperBound = variable.getUpperBound();
268+
if (upperBound instanceof DeclaredType) {
269+
return ((DeclaredType) upperBound).getTypeArguments().stream()
270+
.anyMatch((candidate) -> candidate.equals(variable));
271+
}
272+
return false;
260273
}
261274

262275
@Override

spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,10 @@
4646
import org.springframework.boot.configurationsample.endpoint.SpecificEndpoint;
4747
import org.springframework.boot.configurationsample.endpoint.incremental.IncrementalEndpoint;
4848
import org.springframework.boot.configurationsample.generic.AbstractGenericProperties;
49+
import org.springframework.boot.configurationsample.generic.ComplexGenericProperties;
4950
import org.springframework.boot.configurationsample.generic.SimpleGenericProperties;
5051
import org.springframework.boot.configurationsample.generic.UnresolvedGenericProperties;
52+
import org.springframework.boot.configurationsample.generic.UpperBoundGenericPojo;
5153
import org.springframework.boot.configurationsample.incremental.BarProperties;
5254
import org.springframework.boot.configurationsample.incremental.FooProperties;
5355
import org.springframework.boot.configurationsample.incremental.RenamedBarProperties;
@@ -524,6 +526,21 @@ public void simpleGenericProperties() {
524526
assertThat(metadata.getItems()).hasSize(3);
525527
}
526528

529+
@Test
530+
public void complexGenericProperties() {
531+
ConfigurationMetadata metadata = compile(ComplexGenericProperties.class);
532+
assertThat(metadata).has(
533+
Metadata.withGroup("generic").fromSource(ComplexGenericProperties.class));
534+
assertThat(metadata).has(
535+
Metadata.withGroup("generic.test").ofType(UpperBoundGenericPojo.class)
536+
.fromSource(ComplexGenericProperties.class));
537+
assertThat(metadata).has(Metadata
538+
.withProperty("generic.test.mappings",
539+
"java.util.Map<java.lang.Enum<T>,java.lang.String>")
540+
.fromSource(UpperBoundGenericPojo.class));
541+
assertThat(metadata.getItems()).hasSize(3);
542+
}
543+
527544
@Test
528545
public void unresolvedGenericProperties() {
529546
ConfigurationMetadata metadata = compile(AbstractGenericProperties.class,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.configurationsample.generic;
18+
19+
import org.springframework.boot.configurationsample.ConfigurationProperties;
20+
import org.springframework.boot.configurationsample.NestedConfigurationProperty;
21+
22+
/**
23+
* More advanced generic setup.
24+
*
25+
* @author Stephane Nicoll
26+
*/
27+
@ConfigurationProperties("generic")
28+
public class ComplexGenericProperties {
29+
30+
@NestedConfigurationProperty
31+
private UpperBoundGenericPojo<Test> test = new UpperBoundGenericPojo<>();
32+
33+
public UpperBoundGenericPojo<Test> getTest() {
34+
return this.test;
35+
}
36+
37+
public enum Test {
38+
39+
ONE, TWO, THREE
40+
41+
}
42+
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.configurationsample.generic;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
22+
/**
23+
* A pojo with a complex generic signature.
24+
*
25+
* @author Stephane Nicoll
26+
*/
27+
public class UpperBoundGenericPojo<T extends Enum<T>> {
28+
29+
private final Map<T, String> mappings = new HashMap<>();
30+
31+
public Map<T, String> getMappings() {
32+
return this.mappings;
33+
}
34+
35+
}

0 commit comments

Comments
 (0)