Skip to content

Commit

Permalink
Fix RecordedInvocation instance resolution
Browse files Browse the repository at this point in the history
This commit fixes `RecordedInvocation` and
`RuntimeHintsInvocationsAssert` so that they don't refer to the recorded
instance for static calls.

This also consistently resolves the `TypeReference` of recorded
instances.

Fixes gh-28907
  • Loading branch information
bclozel committed Aug 1, 2022
1 parent e4a7e35 commit 42b3339
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ enum InstrumentedMethod {
*/
CLASS_GETCLASSES(Class.class, "getClasses", HintType.REFLECTION,
invocation -> {
TypeReference thisType = invocation.getInstanceTypeReference();
return RuntimeHintsPredicates.reflection().onType(thisType)
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass))
.withAnyMemberCategory(MemberCategory.DECLARED_CLASSES, MemberCategory.PUBLIC_CLASSES);
}
),
Expand All @@ -87,8 +87,8 @@ enum InstrumentedMethod {
*/
CLASS_GETCONSTRUCTORS(Class.class, "getConstructors", HintType.REFLECTION,
invocation -> {
TypeReference thisType = invocation.getInstanceTypeReference();
return RuntimeHintsPredicates.reflection().onType(thisType).withAnyMemberCategory(
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)).withAnyMemberCategory(
MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS, MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}
Expand All @@ -99,8 +99,8 @@ enum InstrumentedMethod {
*/
CLASS_GETDECLAREDCLASSES(Class.class, "getDeclaredClasses", HintType.REFLECTION,
invocation -> {
TypeReference thisType = invocation.getInstanceTypeReference();
return RuntimeHintsPredicates.reflection().onType(thisType).withMemberCategory(MemberCategory.DECLARED_CLASSES);
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)).withMemberCategory(MemberCategory.DECLARED_CLASSES);
}
),

Expand All @@ -124,8 +124,8 @@ enum InstrumentedMethod {
*/
CLASS_GETDECLAREDCONSTRUCTORS(Class.class, "getDeclaredConstructors", HintType.REFLECTION,
invocation -> {
TypeReference thisType = invocation.getInstanceTypeReference();
return RuntimeHintsPredicates.reflection().onType(thisType)
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass))
.withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
}),

Expand All @@ -149,8 +149,8 @@ enum InstrumentedMethod {
*/
CLASS_GETDECLAREDFIELDS(Class.class, "getDeclaredFields", HintType.REFLECTION,
invocation -> {
TypeReference thisType = invocation.getInstanceTypeReference();
return RuntimeHintsPredicates.reflection().onType(thisType).withMemberCategory(MemberCategory.DECLARED_FIELDS);
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)).withMemberCategory(MemberCategory.DECLARED_FIELDS);
}
),

Expand All @@ -175,8 +175,8 @@ enum InstrumentedMethod {
*/
CLASS_GETDECLAREDMETHODS(Class.class, "getDeclaredMethods", HintType.REFLECTION,
invocation -> {
TypeReference thisType = invocation.getInstanceTypeReference();
return RuntimeHintsPredicates.reflection().onType(thisType)
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass))
.withAnyMemberCategory(MemberCategory.INTROSPECT_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_METHODS);
}
),
Expand All @@ -202,8 +202,8 @@ enum InstrumentedMethod {
*/
CLASS_GETFIELDS(Class.class, "getFields", HintType.REFLECTION,
invocation -> {
TypeReference thisType = invocation.getInstanceTypeReference();
return RuntimeHintsPredicates.reflection().onType(thisType)
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass))
.withAnyMemberCategory(MemberCategory.PUBLIC_FIELDS, MemberCategory.DECLARED_FIELDS);
}
),
Expand Down Expand Up @@ -231,8 +231,8 @@ enum InstrumentedMethod {
*/
CLASS_GETMETHODS(Class.class, "getMethods", HintType.REFLECTION,
invocation -> {
TypeReference thisType = invocation.getInstanceTypeReference();
return RuntimeHintsPredicates.reflection().onType(thisType).withAnyMemberCategory(
Class<?> thisClass = invocation.getInstance();
return RuntimeHintsPredicates.reflection().onType(TypeReference.of(thisClass)).withAnyMemberCategory(
MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS,
MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS);
}
Expand Down Expand Up @@ -297,9 +297,9 @@ enum InstrumentedMethod {
*/
CLASS_GETRESOURCE(Class.class, "getResource", HintType.RESOURCE_PATTERN,
invocation -> {
TypeReference thisType = invocation.getInstanceTypeReference();
Class<?> thisClass = invocation.getInstance();
String resourceName = invocation.getArgument(0);
return RuntimeHintsPredicates.resource().forResource(thisType, resourceName);
return RuntimeHintsPredicates.resource().forResource(TypeReference.of(thisClass), resourceName);
}),

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,17 @@ public <T> T getInstance() {
*/
public TypeReference getInstanceTypeReference() {
Assert.notNull(this.instance, "Cannot resolve 'this' for static invocations");
if (this.instance instanceof Class<?>) {
return TypeReference.of((Class<?>) this.instance);
}
return TypeReference.of(this.instance.getClass());
}

/**
* Return whether the current invocation is static.
* @return {@code true} if the invocation is static
*/
public boolean isStatic() {
return this.instance == null;
}

/**
* Return the argument values used for the current reflection invocation.
* @return the invocation arguments
Expand Down Expand Up @@ -172,8 +177,15 @@ public boolean matches(RuntimeHints hints) {

@Override
public String toString() {
return String.format("<%s> invocation of <%s> on type <%s> with arguments %s",
getHintType().hintClassName(), getMethodReference(), getInstanceTypeReference(), getArguments());
if(isStatic()) {
return String.format("<%s> invocation of <%s> with arguments %s",
getHintType().hintClassName(), getMethodReference(), getArguments());
}
else {
Class<?> instanceType = (getInstance() instanceof Class<?>) ? getInstance() : getInstance().getClass();
return String.format("<%s> invocation of <%s> on type <%s> with arguments %s",
getHintType().hintClassName(), getMethodReference(), instanceType.getCanonicalName(), getArguments());
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,18 @@ public ListAssert<RecordedInvocation> notMatching(RuntimeHints runtimeHints) {


private ErrorMessageFactory errorMessageForInvocation(RecordedInvocation invocation) {
return new BasicErrorMessageFactory("%nMissing <%s> for invocation <%s> on type <%s> %nwith arguments %s.%nStacktrace:%n<%s>",
invocation.getHintType().hintClassName(), invocation.getMethodReference(),
invocation.getInstanceTypeReference(), invocation.getArguments(),
formatStackTrace(invocation.getStackFrames()));
if (invocation.isStatic()) {
return new BasicErrorMessageFactory("%nMissing <%s> for invocation <%s>%nwith arguments %s.%nStacktrace:%n<%s>",
invocation.getHintType().hintClassName(), invocation.getMethodReference(),
invocation.getArguments(), formatStackTrace(invocation.getStackFrames()));
}
else {
Class<?> instanceType = (invocation.getInstance() instanceof Class<?>) ? invocation.getInstance() : invocation.getInstance().getClass();
return new BasicErrorMessageFactory("%nMissing <%s> for invocation <%s> on type <%s> %nwith arguments %s.%nStacktrace:%n<%s>",
invocation.getHintType().hintClassName(), invocation.getMethodReference(),
instanceType, invocation.getArguments(),
formatStackTrace(invocation.getStackFrames()));
}
}

private String formatStackTrace(Stream<StackWalker.StackFrame> stackTraceElements) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2002-2022 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.aot.agent;


import java.lang.reflect.Method;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import org.springframework.aot.hint.TypeReference;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

/**
* Tests for {@link RecordedInvocation}.
*
* @author Brian Clozel
*/
class RecordedInvocationTests {

private RecordedInvocation staticInvocation;

private RecordedInvocation instanceInvocation;

@BeforeEach
void setup() throws Exception {
staticInvocation = RecordedInvocation.of(InstrumentedMethod.CLASS_FORNAME)
.withArgument(String.class.getCanonicalName())
.returnValue(String.class)
.build();
instanceInvocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHOD)
.onInstance(String.class)
.withArguments("toString", new Class[0])
.returnValue(String.class.getMethod("toString"))
.build();
}

@Test
void buildValidStaticInvocation() {
assertThat(staticInvocation.getHintType()).isEqualTo(HintType.REFLECTION);
assertThat(staticInvocation.getMethodReference()).isEqualTo(InstrumentedMethod.CLASS_FORNAME.methodReference());
assertThat(staticInvocation.getArguments()).containsOnly(String.class.getCanonicalName());
assertThat(staticInvocation.getArgumentTypes()).containsOnly(TypeReference.of(String.class));
assertThat((Class<?>) staticInvocation.getReturnValue()).isEqualTo(String.class);
assertThat(staticInvocation.isStatic()).isTrue();
}

@Test
void staticInvocationShouldThrowWhenGetInstance() {
assertThatThrownBy(staticInvocation::getInstance).isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(staticInvocation::getInstanceTypeReference).isInstanceOf(IllegalArgumentException.class);
}

@Test
void staticInvocationToString() {
assertThat(staticInvocation.toString()).contains("ReflectionHints", "java.lang.Class#forName", "[java.lang.String]");
}

@Test
void buildValidInstanceInvocation() throws Exception {
assertThat(instanceInvocation.getHintType()).isEqualTo(HintType.REFLECTION);
assertThat(instanceInvocation.getMethodReference()).isEqualTo(InstrumentedMethod.CLASS_GETMETHOD.methodReference());
assertThat(instanceInvocation.getArguments()).containsOnly("toString", new Class[0]);
assertThat(instanceInvocation.getArgumentTypes()).containsOnly(TypeReference.of(String.class), TypeReference.of(Class[].class));
Method toString = String.class.getMethod("toString");
assertThat((Method) instanceInvocation.getReturnValue()).isEqualTo(toString);
assertThat(instanceInvocation.isStatic()).isFalse();
assertThat((Class<?>) instanceInvocation.getInstance()).isEqualTo(String.class);
assertThat(instanceInvocation.getInstanceTypeReference()).isEqualTo(TypeReference.of(Class.class));
}

@Test
void instanceInvocationToString() {
assertThat(instanceInvocation.toString()).contains("ReflectionHints", "", "java.lang.Class#getMethod",
"java.lang.String", "[toString, [Ljava.lang.Class;");
}

}

0 comments on commit 42b3339

Please sign in to comment.