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 gh-18365
This commit is contained in:
		
							parent
							
								
									8b62f448ba
								
							
						
					
					
						commit
						59bc3c5602
					
				|  | @ -26,6 +26,7 @@ import java.util.LinkedList; | |||
| 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; | ||||
|  | @ -258,8 +259,10 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor | |||
| 		Map<String, Object> fieldValues = members.getFieldValues(); | ||||
| 		processSimpleTypes(prefix, element, source, members, fieldValues); | ||||
| 		processSimpleLombokTypes(prefix, element, source, members, fieldValues); | ||||
| 		processNestedTypes(prefix, element, source, members); | ||||
| 		processNestedLombokTypes(prefix, element, source, members); | ||||
| 		Stack<TypeElement> seen = new Stack<>(); | ||||
| 		seen.push(element); | ||||
| 		processNestedTypes(prefix, element, source, members, seen); | ||||
| 		processNestedLombokTypes(prefix, element, source, members, seen); | ||||
| 	} | ||||
| 
 | ||||
| 	private void processSimpleTypes(String prefix, TypeElement element, ExecutableElement source, | ||||
|  | @ -324,19 +327,19 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor | |||
| 	} | ||||
| 
 | ||||
| 	private void processNestedTypes(String prefix, TypeElement element, ExecutableElement source, | ||||
| 			TypeElementMembers members) { | ||||
| 			TypeElementMembers members, Stack<TypeElement> seen) { | ||||
| 		members.getPublicGetters().forEach((name, getter) -> { | ||||
| 			VariableElement field = members.getFields().get(name); | ||||
| 			processNestedType(prefix, element, source, name, getter, field, getter.getReturnType()); | ||||
| 			processNestedType(prefix, element, source, name, getter, field, getter.getReturnType(), seen); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	private void processNestedLombokTypes(String prefix, TypeElement element, ExecutableElement source, | ||||
| 			TypeElementMembers members) { | ||||
| 			TypeElementMembers members, Stack<TypeElement> seen) { | ||||
| 		members.getFields().forEach((name, field) -> { | ||||
| 			if (isLombokField(field, element)) { | ||||
| 				ExecutableElement getter = members.getPublicGetter(name, field.asType()); | ||||
| 				processNestedType(prefix, element, source, name, getter, field, field.asType()); | ||||
| 				processNestedType(prefix, element, source, name, getter, field, field.asType(), seen); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | @ -378,7 +381,7 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor | |||
| 	} | ||||
| 
 | ||||
| 	private void processNestedType(String prefix, TypeElement element, ExecutableElement source, String name, | ||||
| 			ExecutableElement getter, VariableElement field, TypeMirror returnType) { | ||||
| 			ExecutableElement getter, VariableElement field, TypeMirror returnType, Stack<TypeElement> seen) { | ||||
| 		Element returnElement = this.processingEnv.getTypeUtils().asElement(returnType); | ||||
| 		boolean isNested = isNested(returnElement, field, element); | ||||
| 		AnnotationMirror annotation = getAnnotation(getter, configurationPropertiesAnnotation()); | ||||
|  | @ -387,7 +390,12 @@ public class ConfigurationMetadataAnnotationProcessor extends AbstractProcessor | |||
| 			this.metadataCollector | ||||
| 					.add(ItemMetadata.newGroup(nestedPrefix, this.typeUtils.getQualifiedName(returnElement), | ||||
| 							this.typeUtils.getQualifiedName(element), (getter != null) ? getter.toString() : null)); | ||||
| 			processTypeElement(nestedPrefix, (TypeElement) returnElement, source); | ||||
| 			TypeElement nestedElement = (TypeElement) returnElement; | ||||
| 			if (!seen.contains(nestedElement)) { | ||||
| 				seen.push(nestedElement); | ||||
| 				processTypeElement(nestedPrefix, nestedElement, source); | ||||
| 				seen.pop(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -68,6 +68,7 @@ import org.springframework.boot.configurationsample.method.EmptyTypeMethodConfig | |||
| import org.springframework.boot.configurationsample.method.InvalidMethodConfig; | ||||
| import org.springframework.boot.configurationsample.method.MethodAndClassConfig; | ||||
| import org.springframework.boot.configurationsample.method.SimpleMethodConfig; | ||||
| 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; | ||||
|  | @ -970,6 +971,11 @@ public class ConfigurationMetadataAnnotationProcessorTests { | |||
| 				.has(Metadata.withProperty("bar.counter").withDefaultValue(0).fromSource(RenamedBarProperties.class)); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void recursivePropertiesDoNotCauseAStackOverflow() { | ||||
| 		compile(RecursiveProperties.class); | ||||
| 	} | ||||
| 
 | ||||
| 	private void assertSimpleLombokProperties(ConfigurationMetadata metadata, Class<?> source, String prefix) { | ||||
| 		assertThat(metadata).has(Metadata.withGroup(prefix).fromSource(source)); | ||||
| 		assertThat(metadata).doesNotHave(Metadata.withProperty(prefix + ".id")); | ||||
|  |  | |||
|  | @ -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; | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
		Loading…
	
		Reference in New Issue