Do not require full type reflection when listing methods/fields

Prior to this commit, the `RuntimeHintsAgent` and its testing
infrastructure would assume that calling `MyClass.class.getMethods()`
requires a reflection hint on the class for introspecting public/private
methods.

GraalVM does not require this, in fact this call only returns methods
that have reflection hints in the native image.

This commit refines the agent behavior for `Class.getMethods()`,
`Class.getDeclaredMethods()`, `Class.getFields()` and
`Class.getDeclaredFields()`. With this change, registering at least one
method/field for reflection is enough to match.

During the execution of Java tests, all methods and fields will be
provided, regardless of hints being registered or not. This could cause
false negatives where we're missing reflection hints on methods or
fields.
This risk is mitigated thanks to additional instrumentation on
`Method.getAnnotations()`, `Method.getParameterTypes()` and
`Method.invoke()`. If a method is found reflectively, chances are it
will be used for further reflection.

Closes gh-29091
This commit is contained in:
Brian Clozel
2022-09-06 22:07:20 +02:00
parent 6cce47176a
commit 323d1907c1
6 changed files with 288 additions and 54 deletions

View File

@@ -212,6 +212,29 @@ public class ReflectionHintsPredicates {
.anyMatch(memberCategory -> getTypeHint(hints).getMemberCategories().contains(memberCategory)));
}
/**
* Refine the current predicate to only match if a hint is present for any of its constructors.
* @return the refined {@link RuntimeHints} predicate
*/
public Predicate<RuntimeHints> withAnyConstructor() {
return this.and(hints -> getTypeHint(hints).constructors().findAny().isPresent());
}
/**
* Refine the current predicate to only match if a hint is present for any of its methods.
* @return the refined {@link RuntimeHints} predicate
*/
public Predicate<RuntimeHints> withAnyMethod() {
return this.and(hints -> getTypeHint(hints).methods().findAny().isPresent());
}
/**
* Refine the current predicate to only match if a hint is present for any of its fields.
* @return the refined {@link RuntimeHints} predicate
*/
public Predicate<RuntimeHints> withAnyField() {
return this.and(hints -> getTypeHint(hints).fields().findAny().isPresent());
}
}
public abstract static class ExecutableHintPredicate<T extends Executable> implements Predicate<RuntimeHints> {

View File

@@ -18,6 +18,8 @@ package org.springframework.aot.hint.predicate;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.function.Predicate;
@@ -45,6 +47,14 @@ class ReflectionHintsPredicatesTests {
private static Constructor<?> publicConstructor;
private static Method privateMethod;
private static Method publicMethod;
private static Field privateField;
private static Field publicField;
private final ReflectionHintsPredicates reflection = new ReflectionHintsPredicates();
private final RuntimeHints runtimeHints = new RuntimeHints();
@@ -54,6 +64,10 @@ class ReflectionHintsPredicatesTests {
static void setupAll() throws Exception {
privateConstructor = SampleClass.class.getDeclaredConstructor(String.class);
publicConstructor = SampleClass.class.getConstructor();
privateMethod = SampleClass.class.getDeclaredMethod("privateMethod");
publicMethod = SampleClass.class.getMethod("publicMethod");
privateField = SampleClass.class.getDeclaredField("privateField");
publicField = SampleClass.class.getField("publicField");
}
@Nested
@@ -289,6 +303,18 @@ class ReflectionHintsPredicatesTests {
assertPredicateMatches(reflection.onConstructor(privateConstructor).invoke());
}
@Test
void reflectionOnAnyConstructorDoesNotMatchTypeReflection() {
runtimeHints.reflection().registerType(SampleClass.class);
assertPredicateDoesNotMatch(reflection.onType(SampleClass.class).withAnyConstructor());
}
@Test
void reflectionOnAnyConstructorMatchesConstructorReflection() {
runtimeHints.reflection().registerConstructor(publicConstructor);
assertPredicateMatches(reflection.onType(SampleClass.class).withAnyConstructor());
}
}
@Nested
@@ -432,6 +458,18 @@ class ReflectionHintsPredicatesTests {
assertPredicateMatches(reflection.onMethod(SampleClass.class, "privateMethod").invoke());
}
@Test
void reflectionOnAnyMethodDoesNotMatchTypeReflection() {
runtimeHints.reflection().registerType(SampleClass.class);
assertPredicateDoesNotMatch(reflection.onType(SampleClass.class).withAnyMethod());
}
@Test
void reflectionOnAnyMethodMatchesMethodReflection() {
runtimeHints.reflection().registerMethod(publicMethod);
assertPredicateMatches(reflection.onType(SampleClass.class).withAnyMethod());
}
}
@Nested
@@ -505,6 +543,18 @@ class ReflectionHintsPredicatesTests {
assertPredicateMatches(reflection.onField(SampleClass.class, "privateField"));
}
@Test
void reflectionOnAnyFieldDoesNotMatchTypeReflection() {
runtimeHints.reflection().registerType(SampleClass.class);
assertPredicateDoesNotMatch(reflection.onType(SampleClass.class).withAnyField());
}
@Test
void reflectionOnAnyFieldMatchesFieldReflection() {
runtimeHints.reflection().registerField(publicField);
assertPredicateMatches(reflection.onType(SampleClass.class).withAnyField());
}
}
private void assertPredicateMatches(Predicate<RuntimeHints> predicate) {