Add FieldMode for field hints and ensure that it cannot be downgraded
Add a `FieldMode` enum analogous to `ExecutableHint` and update `FieldHint` to ensure that registration cannot downgrade `WRITE` to `READ`. Fixes gh-29055
This commit is contained in:
@@ -48,6 +48,7 @@ import org.springframework.aot.generate.GenerationContext;
|
||||
import org.springframework.aot.generate.MethodReference;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.FieldHint;
|
||||
import org.springframework.aot.hint.FieldMode;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
@@ -914,7 +915,7 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
|
||||
private static final String INSTANCE_PARAMETER = "instance";
|
||||
|
||||
private static final Consumer<FieldHint.Builder> ALLOW_WRITE = builder -> builder
|
||||
.allowWrite(true);
|
||||
.withMode(FieldMode.WRITE);
|
||||
|
||||
|
||||
private final Class<?> target;
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.aot.hint;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A hint that describes the need of reflection on a {@link Field}.
|
||||
@@ -28,23 +29,33 @@ import org.springframework.lang.Nullable;
|
||||
*/
|
||||
public final class FieldHint extends MemberHint {
|
||||
|
||||
private final boolean allowWrite;
|
||||
private final FieldMode mode;
|
||||
|
||||
private final boolean allowUnsafeAccess;
|
||||
|
||||
|
||||
private FieldHint(Builder builder) {
|
||||
super(builder.name);
|
||||
this.allowWrite = (builder.allowWrite != null) ? builder.allowWrite : true;
|
||||
this.mode = (builder.mode != null ? builder.mode : FieldMode.WRITE);
|
||||
this.allowUnsafeAccess = builder.allowUnsafeAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether setting the value of the field should be allowed.
|
||||
* @return {@code true} to allow {@link Field#set(Object, Object)}.
|
||||
* @deprecated in favor of {@link #getMode()}
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean isAllowWrite() {
|
||||
return this.allowWrite;
|
||||
return this.mode == FieldMode.WRITE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@linkplain FieldMode mode} that apply to this hint.
|
||||
* @return the mode
|
||||
*/
|
||||
public FieldMode getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +75,7 @@ public final class FieldHint extends MemberHint {
|
||||
private final String name;
|
||||
|
||||
@Nullable
|
||||
private Boolean allowWrite;
|
||||
private FieldMode mode;
|
||||
|
||||
private boolean allowUnsafeAccess;
|
||||
|
||||
@@ -77,9 +88,26 @@ public final class FieldHint extends MemberHint {
|
||||
* Specify if setting the value of the field should be allowed.
|
||||
* @param allowWrite {@code true} to allow {@link Field#set(Object, Object)}
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
* @deprecated in favor of {@link #withMode(FieldMode)}
|
||||
*/
|
||||
@Deprecated
|
||||
public Builder allowWrite(boolean allowWrite) {
|
||||
this.allowWrite = allowWrite;
|
||||
if (allowWrite) {
|
||||
return withMode(FieldMode.WRITE);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that the {@linkplain FieldMode mode} is required.
|
||||
* @param mode the required mode
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public Builder withMode(FieldMode mode) {
|
||||
Assert.notNull(mode, "'mode' must not be null");
|
||||
if ((this.mode == null || !this.mode.includes(mode))) {
|
||||
this.mode = mode;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.hint;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the need of reflection for a given {@link Field}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
* @see ReflectionHints
|
||||
*/
|
||||
public enum FieldMode {
|
||||
|
||||
/**
|
||||
* Only field read is required.
|
||||
*/
|
||||
READ,
|
||||
|
||||
/**
|
||||
* Full field read and write is required.
|
||||
*/
|
||||
WRITE;
|
||||
|
||||
/**
|
||||
* Specify if this mode already includes the specified {@code other} mode.
|
||||
* @param other the other mode to check
|
||||
* @return {@code true} if this mode includes the other mode
|
||||
*/
|
||||
boolean includes(@Nullable FieldMode other) {
|
||||
return (other == null || this.ordinal() >= other.ordinal());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -127,12 +127,12 @@ public class ReflectionHints {
|
||||
|
||||
/**
|
||||
* Register the need for reflection on the specified {@link Field},
|
||||
* enabling write access.
|
||||
* enabling {@link FieldMode#WRITE}.
|
||||
* @param field the field that requires reflection
|
||||
* @return {@code this}, to facilitate method chaining
|
||||
*/
|
||||
public ReflectionHints registerField(Field field) {
|
||||
return registerField(field, fieldHint -> fieldHint.allowWrite(true));
|
||||
return registerField(field, fieldHint -> fieldHint.withMode(FieldMode.WRITE));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,7 +146,6 @@ public class ReflectionHints {
|
||||
typeHint -> typeHint.withField(field.getName(), fieldHint));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register the need for reflection on the specified {@link Constructor},
|
||||
* enabling {@link ExecutableMode#INVOKE}.
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.util.function.Predicate;
|
||||
import org.springframework.aot.hint.ExecutableHint;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.FieldHint;
|
||||
import org.springframework.aot.hint.FieldMode;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
@@ -401,7 +402,7 @@ public class ReflectionHintsPredicates {
|
||||
private boolean exactMatch(TypeHint typeHint) {
|
||||
return typeHint.fields().anyMatch(fieldHint ->
|
||||
this.field.getName().equals(fieldHint.getName())
|
||||
&& (!this.allowWrite || this.allowWrite == fieldHint.isAllowWrite())
|
||||
&& (!this.allowWrite || fieldHint.getMode() == FieldMode.WRITE)
|
||||
&& (!this.allowUnsafeAccess || this.allowUnsafeAccess == fieldHint.isAllowUnsafeAccess()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.stream.Stream;
|
||||
import org.springframework.aot.hint.ExecutableHint;
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.FieldHint;
|
||||
import org.springframework.aot.hint.FieldMode;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.TypeHint;
|
||||
@@ -74,8 +75,8 @@ class ReflectionHintsWriter {
|
||||
private Map<String, Object> toAttributes(FieldHint hint) {
|
||||
Map<String, Object> attributes = new LinkedHashMap<>();
|
||||
attributes.put("name", hint.getName());
|
||||
if (hint.isAllowWrite()) {
|
||||
attributes.put("allowWrite", hint.isAllowWrite());
|
||||
if (hint.getMode() == FieldMode.WRITE) {
|
||||
attributes.put("allowWrite", true);
|
||||
}
|
||||
if (hint.isAllowUnsafeAccess()) {
|
||||
attributes.put("allowUnsafeAccess", hint.isAllowUnsafeAccess());
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.hint;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link FieldMode}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @since 6.0
|
||||
*/
|
||||
class FieldModeTests {
|
||||
|
||||
@Test
|
||||
void writeIncludesNullMode() {
|
||||
assertThat(FieldMode.WRITE.includes(null)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeIncludesRead() {
|
||||
assertThat(FieldMode.WRITE.includes(FieldMode.READ)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeIncludesWrite() {
|
||||
assertThat(FieldMode.WRITE.includes(FieldMode.WRITE)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void readIncludesNullMode() {
|
||||
assertThat(FieldMode.READ.includes(null)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void readIncludesRead() {
|
||||
assertThat(FieldMode.READ.includes(FieldMode.READ)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void readDoesNotIncludeWrite() {
|
||||
assertThat(FieldMode.READ.includes(FieldMode.WRITE)).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* 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.
|
||||
@@ -127,7 +127,7 @@ class ReflectionHintsTests {
|
||||
this.reflectionHints.registerField(field);
|
||||
assertTestTypeFieldHint(fieldHint -> {
|
||||
assertThat(fieldHint.getName()).isEqualTo("field");
|
||||
assertThat(fieldHint.isAllowWrite()).isTrue();
|
||||
assertThat(fieldHint.getMode()).isEqualTo(FieldMode.WRITE);
|
||||
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
|
||||
});
|
||||
}
|
||||
@@ -139,7 +139,7 @@ class ReflectionHintsTests {
|
||||
this.reflectionHints.registerField(field, fieldHint -> {});
|
||||
assertTestTypeFieldHint(fieldHint -> {
|
||||
assertThat(fieldHint.getName()).isEqualTo("field");
|
||||
assertThat(fieldHint.isAllowWrite()).isTrue();
|
||||
assertThat(fieldHint.getMode()).isEqualTo(FieldMode.WRITE);
|
||||
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
|
||||
});
|
||||
}
|
||||
@@ -149,14 +149,26 @@ class ReflectionHintsTests {
|
||||
Field field = ReflectionUtils.findField(TestType.class, "field");
|
||||
assertThat(field).isNotNull();
|
||||
this.reflectionHints.registerField(field, fieldHint ->
|
||||
fieldHint.allowWrite(false).allowUnsafeAccess(true));
|
||||
fieldHint.withMode(FieldMode.READ).allowUnsafeAccess(true));
|
||||
assertTestTypeFieldHint(fieldHint -> {
|
||||
assertThat(fieldHint.getName()).isEqualTo("field");
|
||||
assertThat(fieldHint.isAllowWrite()).isFalse();
|
||||
assertThat(fieldHint.getMode()).isEqualTo(FieldMode.READ);
|
||||
assertThat(fieldHint.isAllowUnsafeAccess()).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test // gh-29055
|
||||
void registerFieldWithCustomizersCannotDowngradeWrite() {
|
||||
Field field = ReflectionUtils.findField(TestType.class, "field");
|
||||
assertThat(field).isNotNull();
|
||||
this.reflectionHints.registerField(field, fieldHint -> fieldHint.withMode(FieldMode.WRITE));
|
||||
this.reflectionHints.registerField(field, fieldHint -> fieldHint.withMode(FieldMode.READ));
|
||||
assertTestTypeFieldHint(fieldHint -> {
|
||||
assertThat(fieldHint.getName()).isEqualTo("field");
|
||||
assertThat(fieldHint.getMode()).isEqualTo(FieldMode.WRITE);
|
||||
});
|
||||
}
|
||||
|
||||
private void assertTestTypeFieldHint(Consumer<FieldHint> fieldHint) {
|
||||
assertThat(this.reflectionHints.typeHints()).singleElement().satisfies(typeHint -> {
|
||||
assertThat(typeHint.getType().getCanonicalName()).isEqualTo(TestType.class.getCanonicalName());
|
||||
|
||||
@@ -59,7 +59,7 @@ class TypeHintTests {
|
||||
assertFieldHint(TypeHint.of(TypeReference.of(String.class))
|
||||
.withField("value"), fieldHint -> {
|
||||
assertThat(fieldHint.getName()).isEqualTo("value");
|
||||
assertThat(fieldHint.isAllowWrite()).isTrue();
|
||||
assertThat(fieldHint.getMode()).isEqualTo(FieldMode.WRITE);
|
||||
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
|
||||
});
|
||||
}
|
||||
@@ -69,7 +69,7 @@ class TypeHintTests {
|
||||
assertFieldHint(TypeHint.of(TypeReference.of(String.class))
|
||||
.withField("value", fieldHint -> {}), fieldHint -> {
|
||||
assertThat(fieldHint.getName()).isEqualTo("value");
|
||||
assertThat(fieldHint.isAllowWrite()).isTrue();
|
||||
assertThat(fieldHint.getMode()).isEqualTo(FieldMode.WRITE);
|
||||
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
|
||||
});
|
||||
}
|
||||
@@ -78,11 +78,11 @@ class TypeHintTests {
|
||||
void createWithFieldAndCustomizerAppliesCustomization() {
|
||||
assertFieldHint(TypeHint.of(TypeReference.of(String.class))
|
||||
.withField("value", fieldHint -> {
|
||||
fieldHint.allowWrite(false);
|
||||
fieldHint.withMode(FieldMode.READ);
|
||||
fieldHint.allowUnsafeAccess(true);
|
||||
}), fieldHint -> {
|
||||
assertThat(fieldHint.getName()).isEqualTo("value");
|
||||
assertThat(fieldHint.isAllowWrite()).isFalse();
|
||||
assertThat(fieldHint.getMode()).isEqualTo(FieldMode.READ);
|
||||
assertThat(fieldHint.isAllowUnsafeAccess()).isTrue();
|
||||
});
|
||||
}
|
||||
@@ -92,12 +92,12 @@ class TypeHintTests {
|
||||
Builder builder = TypeHint.of(TypeReference.of(String.class));
|
||||
builder.withField("value", fieldHint -> fieldHint.allowUnsafeAccess(true));
|
||||
builder.withField("value", fieldHint -> {
|
||||
fieldHint.allowWrite(true);
|
||||
fieldHint.withMode(FieldMode.WRITE);
|
||||
fieldHint.allowUnsafeAccess(false);
|
||||
});
|
||||
assertFieldHint(builder, fieldHint -> {
|
||||
assertThat(fieldHint.getName()).isEqualTo("value");
|
||||
assertThat(fieldHint.isAllowWrite()).isTrue();
|
||||
assertThat(fieldHint.getMode()).isEqualTo(FieldMode.WRITE);
|
||||
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.FieldMode;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
@@ -471,7 +472,7 @@ class ReflectionHintsPredicatesTests {
|
||||
@Test
|
||||
void fieldWriteReflectionDoesNotMatchFieldHint() {
|
||||
runtimeHints.reflection().registerType(SampleClass.class, typeHint -> typeHint.withField("publicField",
|
||||
fieldHint -> fieldHint.allowWrite(false)));
|
||||
fieldHint -> fieldHint.withMode(FieldMode.WRITE)));
|
||||
assertPredicateDoesNotMatch(reflection.onField(SampleClass.class, "publicField").allowWrite());
|
||||
}
|
||||
|
||||
@@ -485,7 +486,7 @@ class ReflectionHintsPredicatesTests {
|
||||
@Test
|
||||
void fieldWriteReflectionMatchesFieldHintWithWrite() {
|
||||
runtimeHints.reflection().registerType(SampleClass.class, typeHint ->
|
||||
typeHint.withField("publicField", fieldHint -> fieldHint.allowWrite(true)));
|
||||
typeHint.withField("publicField", fieldHint -> fieldHint.withMode(FieldMode.WRITE)));
|
||||
assertPredicateMatches(reflection.onField(SampleClass.class, "publicField").allowWrite());
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode;
|
||||
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.FieldMode;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.ProxyHints;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
@@ -106,9 +107,9 @@ public class FileNativeConfigurationWriterTests {
|
||||
MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS,
|
||||
MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS,
|
||||
MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES)
|
||||
.withField("DEFAULT_CHARSET", fieldBuilder -> fieldBuilder.allowWrite(false))
|
||||
.withField("DEFAULT_CHARSET", fieldBuilder -> fieldBuilder.withMode(FieldMode.READ))
|
||||
.withField("defaultCharset", fieldBuilder -> {
|
||||
fieldBuilder.allowWrite(true);
|
||||
fieldBuilder.withMode(FieldMode.WRITE);
|
||||
fieldBuilder.allowUnsafeAccess(true);
|
||||
})
|
||||
.withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT)
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import org.skyscreamer.jsonassert.JSONCompareMode;
|
||||
|
||||
import org.springframework.aot.hint.ExecutableMode;
|
||||
import org.springframework.aot.hint.FieldMode;
|
||||
import org.springframework.aot.hint.MemberCategory;
|
||||
import org.springframework.aot.hint.ReflectionHints;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
@@ -57,9 +58,9 @@ public class ReflectionHintsWriterTests {
|
||||
MemberCategory.INTROSPECT_PUBLIC_METHODS, MemberCategory.INTROSPECT_DECLARED_METHODS,
|
||||
MemberCategory.INVOKE_PUBLIC_METHODS, MemberCategory.INVOKE_DECLARED_METHODS,
|
||||
MemberCategory.PUBLIC_CLASSES, MemberCategory.DECLARED_CLASSES)
|
||||
.withField("DEFAULT_CHARSET", fieldBuilder -> fieldBuilder.allowWrite(false))
|
||||
.withField("DEFAULT_CHARSET", fieldBuilder -> fieldBuilder.withMode(FieldMode.READ))
|
||||
.withField("defaultCharset", fieldBuilder -> {
|
||||
fieldBuilder.allowWrite(true);
|
||||
fieldBuilder.withMode(FieldMode.WRITE);
|
||||
fieldBuilder.allowUnsafeAccess(true);
|
||||
})
|
||||
.withConstructor(TypeReference.listOf(List.class, boolean.class, MimeType.class), ExecutableMode.INTROSPECT)
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.FieldMode;
|
||||
import org.springframework.aot.hint.TypeReference;
|
||||
import org.springframework.aot.test.generator.compile.CompileWithTargetClassAccess;
|
||||
import org.springframework.aot.test.generator.compile.Compiled;
|
||||
@@ -132,7 +133,7 @@ class PersistenceAnnotationBeanPostProcessorAotContributionTests {
|
||||
.satisfies(fieldHint -> {
|
||||
assertThat(fieldHint.getName())
|
||||
.isEqualTo("entityManager");
|
||||
assertThat(fieldHint.isAllowWrite()).isTrue();
|
||||
assertThat(fieldHint.getMode()).isEqualTo(FieldMode.WRITE);
|
||||
assertThat(fieldHint.isAllowUnsafeAccess()).isFalse();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user