diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java index 0f0a8dfcde6..e619f75bb7d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessor.java @@ -25,6 +25,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.Stack; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; @@ -180,10 +181,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor if (annotation != null) { String prefix = getPrefix(annotation); if (element instanceof TypeElement) { - processAnnotatedTypeElement(prefix, (TypeElement) element); + processAnnotatedTypeElement(prefix, (TypeElement) element, new Stack()); } else if (element instanceof ExecutableElement) { - processExecutableElement(prefix, (ExecutableElement) element); + processExecutableElement(prefix, (ExecutableElement) element, new Stack()); } } } @@ -192,13 +193,13 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor } } - private void processAnnotatedTypeElement(String prefix, TypeElement element) { + private void processAnnotatedTypeElement(String prefix, TypeElement element, Stack seen) { String type = this.metadataEnv.getTypeUtils().getQualifiedName(element); this.metadataCollector.add(ItemMetadata.newGroup(prefix, type, type, null)); - processTypeElement(prefix, element, null); + processTypeElement(prefix, element, null, seen); } - private void processExecutableElement(String prefix, ExecutableElement element) { + private void processExecutableElement(String prefix, ExecutableElement element, Stack seen) { if ((!element.getModifiers().contains(Modifier.PRIVATE)) && (TypeKind.VOID != element.getReturnType().getKind())) { Element returns = this.processingEnv.getTypeUtils().asElement(element.getReturnType()); @@ -213,22 +214,27 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor } else { this.metadataCollector.add(group); - processTypeElement(prefix, (TypeElement) returns, element); + processTypeElement(prefix, (TypeElement) returns, element, seen); } } } } - private void processTypeElement(String prefix, TypeElement element, ExecutableElement source) { - new PropertyDescriptorResolver(this.metadataEnv).resolve(element, source).forEach((descriptor) -> { - this.metadataCollector.add(descriptor.resolveItemMetadata(prefix, this.metadataEnv)); - if (descriptor.isNested(this.metadataEnv)) { - TypeElement nestedTypeElement = (TypeElement) this.metadataEnv.getTypeUtils() - .asElement(descriptor.getType()); - String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, descriptor.getName()); - processTypeElement(nestedPrefix, nestedTypeElement, source); - } - }); + private void processTypeElement(String prefix, TypeElement element, ExecutableElement source, + Stack seen) { + if (!seen.contains(element)) { + seen.push(element); + new PropertyDescriptorResolver(this.metadataEnv).resolve(element, source).forEach((descriptor) -> { + this.metadataCollector.add(descriptor.resolveItemMetadata(prefix, this.metadataEnv)); + if (descriptor.isNested(this.metadataEnv)) { + TypeElement nestedTypeElement = (TypeElement) this.metadataEnv.getTypeUtils() + .asElement(descriptor.getType()); + String nestedPrefix = ConfigurationMetadata.nestedPrefix(prefix, descriptor.getName()); + processTypeElement(nestedPrefix, nestedTypeElement, source, seen); + } + }); + seen.pop(); + } } private void processEndpoint(Element element, List annotations) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index dce5779a8f4..c1fee73ad3a 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; import org.springframework.boot.configurationprocessor.metadata.Metadata; +import org.springframework.boot.configurationsample.recursive.RecursiveProperties; import org.springframework.boot.configurationsample.simple.ClassWithNestedProperties; import org.springframework.boot.configurationsample.simple.DeprecatedFieldSingleProperty; import org.springframework.boot.configurationsample.simple.DeprecatedSingleProperty; @@ -361,4 +362,9 @@ class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGene .withMessageContaining("Compilation failed"); } + @Test + void recursivePropertiesDoNotCauseAStackOverflow() { + compile(RecursiveProperties.class); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/recursive/RecursiveProperties.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/recursive/RecursiveProperties.java new file mode 100644 index 00000000000..2d2d63c97e4 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationsample/recursive/RecursiveProperties.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2018 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.configurationsample.recursive; + +import org.springframework.boot.configurationsample.ConfigurationProperties; + +@ConfigurationProperties("prefix") +public class RecursiveProperties { + + private RecursiveProperties recursive; + + public RecursiveProperties getRecursive() { + return this.recursive; + } + + public void setRecursive(RecursiveProperties recursive) { + this.recursive = recursive; + } + +}