Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
public class AddFieldAccessorMethods extends AccessorMethods {
private static final ClassDesc CD_FIELD = ClassDesc.of("java.lang.reflect.Field");
private static final ClassDesc CD_CRITTER = ClassDesc.of("dev.morphia.critter.Critter");
private static final ClassDesc CD_RUNTIME_EXCEPTION = ClassDesc.of("java.lang.RuntimeException");
private final List<FieldInfo> fields;

/**
Expand Down Expand Up @@ -123,15 +124,32 @@ private static void writeFinalField(ClassBuilder classBuilder, ClassDesc entityD
cod.dup();
cod.putstatic(entityDesc, cacheName, CD_FIELD);
cod.labelBinding(cached);
cod.aload(0);
cod.loadLocal(kind, 1);
if (kind != TypeKind.REFERENCE) {
ClassDesc wrapper = ClassDesc.of(GenerationUtils.PRIMITIVE_TO_WRAPPER.get(fieldDesc.displayName()));
cod.invokestatic(wrapper, "valueOf", MethodTypeDesc.of(wrapper, fieldDesc));
}
cod.invokevirtual(CD_FIELD, "set", MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_Object,
ConstantDescs.CD_Object));
cod.return_();
int fieldSlot = cod.allocateLocal(TypeKind.REFERENCE);
cod.astore(fieldSlot);
// Wrap failures the same way the runtime accessor (NestmateAccessorGenerator) does, so both tiers fail alike
// and Field.set's checked IllegalAccessException doesn't escape undeclared.
cod.trying(tryBody -> {
tryBody.aload(fieldSlot);
tryBody.aload(0);
tryBody.loadLocal(kind, 1);
if (kind != TypeKind.REFERENCE) {
ClassDesc wrapper = ClassDesc.of(GenerationUtils.PRIMITIVE_TO_WRAPPER.get(fieldDesc.displayName()));
tryBody.invokestatic(wrapper, "valueOf", MethodTypeDesc.of(wrapper, fieldDesc));
}
tryBody.invokevirtual(CD_FIELD, "set", MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_Object,
ConstantDescs.CD_Object));
tryBody.return_();
}, catches -> catches.catching(ConstantDescs.CD_Exception, catchBody -> {
int exceptionSlot = catchBody.allocateLocal(TypeKind.REFERENCE);
catchBody.astore(exceptionSlot);
catchBody.new_(CD_RUNTIME_EXCEPTION);
catchBody.dup();
catchBody.ldc("Failed to set final field '%s'".formatted(field.name()));
catchBody.aload(exceptionSlot);
catchBody.invokespecial(CD_RUNTIME_EXCEPTION, "<init>",
MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_String, ConstantDescs.CD_Throwable));
catchBody.athrow();
}));
});
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.morphia.critter.parser.generator;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
Expand Down Expand Up @@ -171,6 +172,26 @@ public void testGeneratorFinalFields() throws Exception {
assertRoundTrip(model, entity, "count", 42);
}

@Test
@SuppressWarnings({ "unchecked", "rawtypes" })
public void testGeneratorFinalFieldWriteFailureIsWrapped() throws Exception {
EntityModel model = generateModel(FinalFieldsExample.class);
Class<?> woven = critterClassLoader.loadClass(FinalFieldsExample.class.getName());
Object entity = woven.getConstructor(String.class, int.class).newInstance("initial", 1);

// Seed the woven writer's Field cache with a copy that was never made accessible, so Field.set throws the
// checked IllegalAccessException.
Field cache = woven.getDeclaredField("__fieldCode");
cache.setAccessible(true);
cache.set(null, woven.getDeclaredField("code"));

PropertyAccessor accessor = model.getProperty("code").getAccessor();
Throwable thrown = Assertions.assertThrows(Throwable.class, () -> accessor.set(entity, "updated"));
Assertions.assertEquals(RuntimeException.class, thrown.getClass());
Assertions.assertEquals("Failed to set final field 'code'", thrown.getMessage());
Assertions.assertInstanceOf(IllegalAccessException.class, thrown.getCause());
}

@Test
public void testRuntimeGeneratorFinalFields() throws Exception {
// A fresh loader: runtime mode leaves the entity class alone, so it must resolve to the application's copy.
Expand Down
Loading