From dbbebf541d3470dd66bfa11c31cb3b6141c9052e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Wed, 1 Mar 2023 10:47:58 +0100 Subject: [PATCH] Refine reflection hints handling for anonymous class Before this commit, anonymous classes could throw an unexpected NullPointerException in ReflectionsHint#registerType and lambdas entries could be created in the related generated reflect-config.json. This commit refines how anonymous classes are handled by explicitly checking for null class and canonical name in ReflectionTypeReference#of, while skipping such class in ReflectionHints#registerType in order to keep a lenient behavior. Closes gh-29774 --- .../hint/BindingReflectionHintsRegistrar.java | 2 +- .../aot/hint/ReflectionHints.java | 16 +++++++++++++--- .../aot/hint/ReflectionTypeReference.java | 6 +++++- .../springframework/aot/hint/TypeReference.java | 4 +++- .../aot/hint/ReflectionHintsTests.java | 11 +++++++++++ .../aot/hint/ReflectionTypeReferenceTests.java | 16 +++++++++++++++- .../aot/nativex/ReflectionHintsWriterTests.java | 10 +++++++++- 7 files changed, 57 insertions(+), 8 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java b/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java index d408198c1bf9..41e10ea576fe 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/BindingReflectionHintsRegistrar.java @@ -74,7 +74,7 @@ private boolean shouldSkipType(Class type) { } private boolean shouldSkipMembers(Class type) { - return (type.getCanonicalName() != null && type.getCanonicalName().startsWith("java.")) || type.isArray(); + return type.getCanonicalName().startsWith("java.") || type.isArray(); } private void registerReflectionHints(ReflectionHints hints, Set seen, Type type) { diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java index 5d5beb7b7c8e..c819ae35519b 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -28,6 +28,7 @@ import org.springframework.aot.hint.TypeHint.Builder; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** @@ -36,6 +37,7 @@ * @author Stephane Nicoll * @author Phillip Webb * @author Andy Wilkinson + * @author Sebastien Deleuze * @since 6.0 */ public class ReflectionHints { @@ -106,7 +108,11 @@ public ReflectionHints registerType(TypeReference type, MemberCategory... member * @see #registerType(Class, MemberCategory...) */ public ReflectionHints registerType(Class type, Consumer typeHint) { - return registerType(TypeReference.of(type), typeHint); + Assert.notNull(type, "'type' must not be null"); + if (type.getCanonicalName() != null) { + registerType(TypeReference.of(type), typeHint); + } + return this; } /** @@ -117,7 +123,11 @@ public ReflectionHints registerType(Class type, Consumer ty * @return {@code this}, to facilitate method chaining */ public ReflectionHints registerType(Class type, MemberCategory... memberCategories) { - return registerType(TypeReference.of(type), memberCategories); + Assert.notNull(type, "'type' must not be null"); + if (type.getCanonicalName() != null) { + registerType(TypeReference.of(type), memberCategories); + } + return this; } /** diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionTypeReference.java b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionTypeReference.java index ea0a2788098f..310f29512d22 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionTypeReference.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionTypeReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -17,11 +17,13 @@ package org.springframework.aot.hint; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * A {@link TypeReference} based on a {@link Class}. * * @author Stephane Nicoll + * @author Sebastien Deleuze * @since 6.0 */ final class ReflectionTypeReference extends AbstractTypeReference { @@ -41,6 +43,8 @@ private static TypeReference getEnclosingClass(Class type) { } static ReflectionTypeReference of(Class type) { + Assert.notNull(type, "'type' must not be null"); + Assert.notNull(type.getCanonicalName(), "'type.getCanonicalName()' must not be null"); return new ReflectionTypeReference(type); } diff --git a/spring-core/src/main/java/org/springframework/aot/hint/TypeReference.java b/spring-core/src/main/java/org/springframework/aot/hint/TypeReference.java index 38008cf3ef0f..6e222de0be11 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/TypeReference.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/TypeReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -26,6 +26,7 @@ * a {@link Class} yet. * * @author Stephane Nicoll + * @author Sebastien Deleuze * @since 6.0 */ public interface TypeReference { @@ -68,6 +69,7 @@ public interface TypeReference { * Create an instance based on the specified type. * @param type the type to wrap * @return a type reference for the specified type + * @throws IllegalArgumentException if the specified type {@linkplain Class#getCanonicalName() canonical name} is {@code null} */ static TypeReference of(Class type) { return ReflectionTypeReference.of(type); diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java index 56e0471c4e52..591bcf953129 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java @@ -34,6 +34,7 @@ * Tests for {@link ReflectionHints}. * * @author Stephane Nicoll + * @author Sebastien Deleuze */ class ReflectionHintsTests { @@ -132,6 +133,16 @@ void registerField() { assertThat(fieldHint.getName()).isEqualTo("field")); } + @Test + void registerTypeIgnoresLambda() { + Runnable lambda = () -> { }; + Consumer hintBuilder = mock(); + this.reflectionHints.registerType(lambda.getClass()); + this.reflectionHints.registerType(lambda.getClass(), hintBuilder); + assertThat(this.reflectionHints.typeHints()).isEmpty(); + verifyNoInteractions(hintBuilder); + } + private void assertTestTypeFieldHint(Consumer fieldHint) { assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> { assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName()); diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionTypeReferenceTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionTypeReferenceTests.java index 4e933074e685..79fb0f899785 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionTypeReferenceTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionTypeReferenceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -18,11 +18,13 @@ import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.junit.jupiter.params.provider.Arguments.arguments; /** @@ -30,9 +32,21 @@ * * @author Stephane Nicoll * @author Moritz Halbritter + * @author Sebastien Deleuze */ class ReflectionTypeReferenceTests { + @Test + void typeReferenceWithNullClass() { + assertThatIllegalArgumentException().isThrownBy(() -> ReflectionTypeReference.of(null)); + } + + @Test + void typeReferenceWithLambda() { + Runnable lambda = () -> { }; + assertThatIllegalArgumentException().isThrownBy(() -> ReflectionTypeReference.of(lambda.getClass())); + } + @ParameterizedTest @MethodSource("reflectionTargetNames") void typeReferenceFromClassHasSuitableReflectionTargetName(Class clazz, String binaryName) { diff --git a/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java b/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java index 9aff60ece09d..bb17e5a832ce 100644 --- a/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java +++ b/spring-core/src/test/java/org/springframework/aot/nativex/ReflectionHintsWriterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * 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. @@ -201,6 +201,14 @@ void methodAndQueriedMethods() throws JSONException { """, hints); } + @Test + void ignoreLambda() throws JSONException { + Runnable anonymousRunnable = () -> { }; + ReflectionHints hints = new ReflectionHints(); + hints.registerType(anonymousRunnable.getClass()); + assertEquals("[]", hints); + } + private void assertEquals(String expectedString, ReflectionHints hints) throws JSONException { StringWriter out = new StringWriter(); BasicJsonWriter writer = new BasicJsonWriter(out, "\t");