Detect use of deprecated API

This commit is a best effort attempt at identifying the members that
code generation invokes and might be deprecated. It introduces
a CodeWarnings helper class that records warnings, with special
handling for `@Deprecated`.

See gh-29597
This commit is contained in:
Stéphane Nicoll
2023-10-10 14:52:18 +02:00
parent 35372e5e72
commit ea66883d79
12 changed files with 614 additions and 3 deletions

View File

@@ -164,11 +164,14 @@ class BeanDefinitionMethodGenerator {
this.aotContributions.forEach(aotContribution -> aotContribution.applyTo(generationContext, codeGenerator));
CodeWarnings codeWarnings = new CodeWarnings();
codeWarnings.detectDeprecation(this.registeredBean.getBeanClass());
return generatedMethods.add("getBeanDefinition", method -> {
method.addJavadoc("Get the $L definition for '$L'.",
(this.registeredBean.isInnerBean() ? "inner-bean" : "bean"),
getName());
method.addModifiers(modifier, Modifier.STATIC);
codeWarnings.suppress(method);
method.returns(BeanDefinition.class);
method.addCode(codeGenerator.generateCode(generationContext));
});

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2002-2023 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.beans.factory.aot;
import java.lang.reflect.AnnotatedElement;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Stream;
import org.springframework.javapoet.AnnotationSpec;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.MethodSpec;
import org.springframework.lang.Nullable;
/**
* Helper class to register warnings that the compiler may trigger on
* generated code.
*
* @author Stephane Nicoll
* @see SuppressWarnings
*/
class CodeWarnings {
private final Set<String> warnings = new LinkedHashSet<>();
/**
* Register a warning to be included for this block. Does nothing if
* the warning is already registered.
* @param warning the warning to register, if it hasn't been already
*/
public void register(String warning) {
this.warnings.add(warning);
}
/**
* Detect the presence of {@link Deprecated} on the specified elements.
* @param elements the elements to check
* @return {@code this} instance
*/
public CodeWarnings detectDeprecation(AnnotatedElement... elements) {
for (AnnotatedElement element : elements) {
register(element.getAnnotation(Deprecated.class));
}
return this;
}
/**
* Detect the presence of {@link Deprecated} on the specified elements.
* @param elements the elements to check
* @return {@code this} instance
*/
public CodeWarnings detectDeprecation(Stream<AnnotatedElement> elements) {
elements.forEach(element -> register(element.getAnnotation(Deprecated.class)));
return this;
}
/**
* Include a {@link SuppressWarnings} on the specified method if necessary.
* @param method the method to update
*/
public void suppress(MethodSpec.Builder method) {
if (this.warnings.isEmpty()) {
return;
}
method.addAnnotation(buildAnnotationSpec());
}
/**
* Return the currently registered warnings.
* @return the warnings
*/
protected Set<String> getWarnings() {
return Collections.unmodifiableSet(this.warnings);
}
private void register(@Nullable Deprecated annotation) {
if (annotation != null) {
if (annotation.forRemoval()) {
register("removal");
}
else {
register("deprecation");
}
}
}
private AnnotationSpec buildAnnotationSpec() {
return AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", generateValueCode()).build();
}
private CodeBlock generateValueCode() {
if (this.warnings.size() == 1) {
return CodeBlock.of("$S", this.warnings.iterator().next());
}
CodeBlock values = CodeBlock.join(this.warnings.stream()
.map(warning -> CodeBlock.of("$S", warning)).toList(), ", ");
return CodeBlock.of("{ $L }", values);
}
@Override
public String toString() {
return new StringJoiner(", ", CodeWarnings.class.getSimpleName(), "")
.add(this.warnings.toString())
.toString();
}
}

View File

@@ -21,6 +21,7 @@ import java.lang.reflect.Executable;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.function.Consumer;
@@ -189,11 +190,15 @@ public class InstanceSupplierCodeGenerator {
private CodeBlock generateCodeForInaccessibleConstructor(String beanName, Class<?> beanClass,
Constructor<?> constructor, boolean dependsOnBean, Consumer<ReflectionHints> hints) {
CodeWarnings codeWarnings = new CodeWarnings();
codeWarnings.detectDeprecation(beanClass, constructor)
.detectDeprecation(Arrays.stream(constructor.getParameters()).map(Parameter::getType));
hints.accept(this.generationContext.getRuntimeHints().reflection());
GeneratedMethod generatedMethod = generateGetInstanceSupplierMethod(method -> {
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
method.addModifiers(PRIVATE_STATIC);
codeWarnings.suppress(method);
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass));
int parameterOffset = (!dependsOnBean) ? 0 : 1;
method.addStatement(generateResolverForConstructor(beanClass, constructor, parameterOffset));
@@ -206,8 +211,12 @@ public class InstanceSupplierCodeGenerator {
String beanName, Class<?> beanClass, Constructor<?> constructor, Class<?> declaringClass,
boolean dependsOnBean, javax.lang.model.element.Modifier... modifiers) {
CodeWarnings codeWarnings = new CodeWarnings();
codeWarnings.detectDeprecation(beanClass, constructor, declaringClass)
.detectDeprecation(Arrays.stream(constructor.getParameters()).map(Parameter::getType));
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
method.addModifiers(modifiers);
codeWarnings.suppress(method);
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, beanClass));
int parameterOffset = (!dependsOnBean) ? 0 : 1;
@@ -300,9 +309,13 @@ public class InstanceSupplierCodeGenerator {
String factoryMethodName = factoryMethod.getName();
Class<?> suppliedType = ClassUtils.resolvePrimitiveIfNecessary(factoryMethod.getReturnType());
CodeWarnings codeWarnings = new CodeWarnings();
codeWarnings.detectDeprecation(declaringClass, factoryMethod, suppliedType)
.detectDeprecation(Arrays.stream(factoryMethod.getParameters()).map(Parameter::getType));
method.addJavadoc("Get the bean instance supplier for '$L'.", beanName);
method.addModifiers(modifiers);
codeWarnings.suppress(method);
method.returns(ParameterizedTypeName.get(BeanInstanceSupplier.class, suppliedType));
CodeBlock.Builder code = CodeBlock.builder();