Skip to content

Commit 59bc3c5

Browse files
committed
Prevent recursive config props from causing a stack overflow
Previously, when the configuration properties annotation processor encountered a property that was the same as an outer type that had already been processed, it would fail with a stack overflow error. This commit introduces the use of a stack to track the types that have been processed. Types that have been seen before are skipped, thereby preventing a failure from occurring. We do not fail upon encountering a recursive type to allow metadata generation to complete. At runtime, the recursive property will not cause a problem if it is not bound. Fixes spring-projectsgh-18365
1 parent 8b62f44 commit 59bc3c5

File tree

3 files changed

+56
-8
lines changed

3 files changed

+56
-8
lines changed

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

+16-8
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.List;
2727
import java.util.Map;
2828
import java.util.Set;
29+
import java.util.Stack;
2930

3031
import javax.annotation.processing.AbstractProcessor;
3132
import javax.annotation.processing.ProcessingEnvironment;
@@ -258,8 +259,10 @@ private void processTypeElement(String prefix, TypeElement element, ExecutableEl
258259
Map<String, Object> fieldValues = members.getFieldValues();
259260
processSimpleTypes(prefix, element, source, members, fieldValues);
260261
processSimpleLombokTypes(prefix, element, source, members, fieldValues);
261-
processNestedTypes(prefix, element, source, members);
262-
processNestedLombokTypes(prefix, element, source, members);
262+
Stack<TypeElement> seen = new Stack<>();
263+
seen.push(element);
264+
processNestedTypes(prefix, element, source, members, seen);
265+
processNestedLombokTypes(prefix, element, source, members, seen);
263266
}
264267

265268
private void processSimpleTypes(String prefix, TypeElement element, ExecutableElement source,
@@ -324,19 +327,19 @@ private void processSimpleLombokTypes(String prefix, TypeElement element, Execut
324327
}
325328

326329
private void processNestedTypes(String prefix, TypeElement element, ExecutableElement source,
327-
TypeElementMembers members) {
330+
TypeElementMembers members, Stack<TypeElement> seen) {
328331
members.getPublicGetters().forEach((name, getter) -> {
329332
VariableElement field = members.getFields().get(name);
330-
processNestedType(prefix, element, source, name, getter, field, getter.getReturnType());
333+
processNestedType(prefix, element, source, name, getter, field, getter.getReturnType(), seen);
331334
});
332335
}
333336

334337
private void processNestedLombokTypes(String prefix, TypeElement element, ExecutableElement source,
335-
TypeElementMembers members) {
338+
TypeElementMembers members, Stack<TypeElement> seen) {
336339
members.getFields().forEach((name, field) -> {
337340
if (isLombokField(field, element)) {
338341
ExecutableElement getter = members.getPublicGetter(name, field.asType());
339-
processNestedType(prefix, element, source, name, getter, field, field.asType());
342+
processNestedType(prefix, element, source, name, getter, field, field.asType(), seen);
340343
}
341344
});
342345
}
@@ -378,7 +381,7 @@ private boolean isAccessLevelPublic(AnnotationMirror lombokAnnotation) {
378381
}
379382

380383
private void processNestedType(String prefix, TypeElement element, ExecutableElement source, String name,
381-
ExecutableElement getter, VariableElement field, TypeMirror returnType) {
384+
ExecutableElement getter, VariableElement field, TypeMirror returnType, Stack<TypeElement> seen) {
382385
Element returnElement = this.processingEnv.getTypeUtils().asElement(returnType);
383386
boolean isNested = isNested(returnElement, field, element);
384387
AnnotationMirror annotation = getAnnotation(getter, configurationPropertiesAnnotation());
@@ -387,7 +390,12 @@ private void processNestedType(String prefix, TypeElement element, ExecutableEle
387390
this.metadataCollector
388391
.add(ItemMetadata.newGroup(nestedPrefix, this.typeUtils.getQualifiedName(returnElement),
389392
this.typeUtils.getQualifiedName(element), (getter != null) ? getter.toString() : null));
390-
processTypeElement(nestedPrefix, (TypeElement) returnElement, source);
393+
TypeElement nestedElement = (TypeElement) returnElement;
394+
if (!seen.contains(nestedElement)) {
395+
seen.push(nestedElement);
396+
processTypeElement(nestedPrefix, nestedElement, source);
397+
seen.pop();
398+
}
391399
}
392400
}
393401

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

+6
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import org.springframework.boot.configurationsample.method.InvalidMethodConfig;
6969
import org.springframework.boot.configurationsample.method.MethodAndClassConfig;
7070
import org.springframework.boot.configurationsample.method.SimpleMethodConfig;
71+
import org.springframework.boot.configurationsample.recursive.RecursiveProperties;
7172
import org.springframework.boot.configurationsample.simple.ClassWithNestedProperties;
7273
import org.springframework.boot.configurationsample.simple.DeprecatedFieldSingleProperty;
7374
import org.springframework.boot.configurationsample.simple.DeprecatedSingleProperty;
@@ -970,6 +971,11 @@ public void incrementalBuildTypeRenamed() throws Exception {
970971
.has(Metadata.withProperty("bar.counter").withDefaultValue(0).fromSource(RenamedBarProperties.class));
971972
}
972973

974+
@Test
975+
public void recursivePropertiesDoNotCauseAStackOverflow() {
976+
compile(RecursiveProperties.class);
977+
}
978+
973979
private void assertSimpleLombokProperties(ConfigurationMetadata metadata, Class<?> source, String prefix) {
974980
assertThat(metadata).has(Metadata.withGroup(prefix).fromSource(source));
975981
assertThat(metadata).doesNotHave(Metadata.withProperty(prefix + ".id"));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2012-2018 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.recursive;
18+
19+
import org.springframework.boot.configurationsample.ConfigurationProperties;
20+
21+
@ConfigurationProperties("prefix")
22+
public class RecursiveProperties {
23+
24+
private RecursiveProperties recursive;
25+
26+
public RecursiveProperties getRecursive() {
27+
return this.recursive;
28+
}
29+
30+
public void setRecursive(RecursiveProperties recursive) {
31+
this.recursive = recursive;
32+
}
33+
34+
}

0 commit comments

Comments
 (0)