OPENJPA-2911 addSerialization in ASM

This commit is contained in:
Mark Struberg 2023-07-13 14:11:14 +02:00
parent 62b14584fb
commit 4d505987c3

View File

@ -603,10 +603,10 @@ public class PCEnhancer {
addPCMethods(); addPCMethods();
addAccessors(pc); addAccessors(pc);
addAttachDetachCode(); addAttachDetachCode();
addSerializationCode();
AsmHelper.readIntoBCClass(pc, _pc); AsmHelper.readIntoBCClass(pc, _pc);
addSerializationCode();
addCloningCode(); addCloningCode();
runAuxiliaryEnhancers(); runAuxiliaryEnhancers();
return ENHANCE_PC; return ENHANCE_PC;
@ -3545,19 +3545,18 @@ public class PCEnhancer {
* as well as creating a custom <code>writeObject</code> method if the * as well as creating a custom <code>writeObject</code> method if the
* class is Serializable and does not define them. * class is Serializable and does not define them.
*/ */
@Deprecated
private void addSerializationCode() { private void addSerializationCode() {
if (externalizeDetached() if (externalizeDetached() || !Serializable.class.isAssignableFrom(_meta.getDescribedType())) {
|| !Serializable.class.isAssignableFrom(_meta.getDescribedType()))
return; return;
}
if (getCreateSubclass()) { if (getCreateSubclass()) {
// ##### what should happen if a type is Externalizable? It looks // ##### what should happen if a type is Externalizable? It looks
// ##### like Externalizable classes will not be serialized as PCs // ##### like Externalizable classes will not be serialized as PCs
// ##### based on this logic. // ##### based on this logic.
if (!Externalizable.class.isAssignableFrom( if (!Externalizable.class.isAssignableFrom(_meta.getDescribedType())) {
_meta.getDescribedType()))
addSubclassSerializationCode(); addSubclassSerializationCode();
}
return; return;
} }
@ -3565,105 +3564,121 @@ public class PCEnhancer {
// is detachable and uses detached state without a declared field, // is detachable and uses detached state without a declared field,
// can't add a serial version UID because we'll be adding extra fields // can't add a serial version UID because we'll be adding extra fields
// to the enhanced version // to the enhanced version
BCField field = _pc.getDeclaredField("serialVersionUID"); final Optional<FieldNode> serialVersionUIDNode = pc.getClassNode().fields.stream()
if (field == null) { .filter(f -> f.name.equals("serialVersionUID"))
.findFirst();
if (serialVersionUIDNode.isEmpty()) {
Long uid = null; Long uid = null;
try { try {
uid = ObjectStreamClass.lookup uid = ObjectStreamClass.lookup(_meta.getDescribedType()).getSerialVersionUID();
(_meta.getDescribedType()).getSerialVersionUID();
} }
catch (Throwable t) { catch (Throwable t) {
// last-chance catch for bug #283 (which can happen // last-chance catch for bug #283 (which can happen
// in a variety of ClassLoading environments) // in a variety of ClassLoading environments)
if (_log.isTraceEnabled()) if (_log.isTraceEnabled()) {
_log.warn(_loc.get("enhance-uid-access", _meta), t); _log.warn(_loc.get("enhance-uid-access", _meta), t);
else }
else {
_log.warn(_loc.get("enhance-uid-access", _meta)); _log.warn(_loc.get("enhance-uid-access", _meta));
} }
}
// if we couldn't access the serialVersionUID, we will have to // if we couldn't access the serialVersionUID, we will have to
// skip the override of that field and not be serialization // skip the override of that field and not be serialization
// compatible with non-enhanced classes // compatible with non-enhanced classes
if (uid != null) { if (uid != null) {
field = _pc.declareField("serialVersionUID", long.class); FieldNode serVersField = new FieldNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL,
field.makePrivate(); "serialVersionUID",
field.setStatic(true); Type.LONG_TYPE.getDescriptor(),
field.setFinal(true); null, uid);
pc.getClassNode().fields.add(serVersField);
/* should be done by ASM FieldNode already
Code code = getOrCreateClassInitCode(false); Code code = getOrCreateClassInitCode(false);
code.beforeFirst(); code.beforeFirst();
code.constant().setValue(uid.longValue()); code.constant().setValue(uid.longValue());
code.putstatic().setField(field); code.putstatic().setField(field);
*/
}
}
code.calculateMaxStack(); MethodNode writeObjectMeth = AsmHelper.getMethodNode(pc.getClassNode(), "writeObject",
} void.class, ObjectOutputStream.class)
} .orElse(null);
boolean full = writeObjectMeth == null;
// add write object method // add write object method
BCMethod write = _pc.getDeclaredMethod("writeObject",
new Class[]{ObjectOutputStream.class});
boolean full = write == null;
if (full) { if (full) {
// private void writeObject (ObjectOutputStream out) // private void writeObject (ObjectOutputStream out)
write = _pc.declareMethod("writeObject", void.class, writeObjectMeth = new MethodNode(Opcodes.ACC_PRIVATE,
new Class[]{ObjectOutputStream.class}); "writeObject",
write.getExceptions(true).addException(IOException.class); Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ObjectOutputStream.class)),
write.makePrivate(); null,
new String[] {Type.getInternalName(IOException.class)});
pc.getClassNode().methods.add(writeObjectMeth);
} }
modifyWriteObjectMethod(write, full); modifyWriteObjectMethod(pc.getClassNode(), writeObjectMeth, full);
// and read object // and read object
BCMethod read = _pc.getDeclaredMethod("readObject", MethodNode readObjectMeth = AsmHelper.getMethodNode(pc.getClassNode(), "readObject",
new Class[]{ObjectInputStream.class}); void.class, ObjectInputStream.class)
full = read == null; .orElse(null);
full = readObjectMeth == null;
if (full) { if (full) {
// private void readObject (ObjectInputStream in) // private void readObject (ObjectInputStream in)
read = _pc.declareMethod("readObject", void.class, readObjectMeth = new MethodNode(Opcodes.ACC_PRIVATE,
new Class[]{ObjectInputStream.class}); "readObject",
read.getExceptions(true).addException(IOException.class); Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(ObjectInputStream.class)),
read.getExceptions(true).addException null,
(ClassNotFoundException.class); new String[] {Type.getInternalName(IOException.class),
read.makePrivate(); Type.getInternalName(ClassNotFoundException.class)});
pc.getClassNode().methods.add(readObjectMeth);
} }
modifyReadObjectMethod(read, full); modifyReadObjectMethod(pc.getClassNode(), readObjectMeth, full);
} }
@Deprecated
private void addSubclassSerializationCode() { private void addSubclassSerializationCode() {
// for generated subclasses, serialization must write an instance of // for generated subclasses, serialization must write an instance of
// the superclass instead of the subclass, so that the client VM can // the superclass instead of the subclass, so that the client VM can
// deserialize successfully. // deserialize successfully.
// private Object writeReplace() throws ObjectStreamException // private Object writeReplace() throws ObjectStreamException
BCMethod method = _pc.declareMethod("writeReplace", Object.class, null); MethodNode writeReplaceMeth = new MethodNode(Opcodes.ACC_PRIVATE,
method.getExceptions(true).addException(ObjectStreamException.class); "writeReplace",
Code code = method.getCode(true); Type.getMethodDescriptor(TYPE_OBJECT),
null,
new String[] {Type.getInternalName(ObjectStreamException.class)});
final ClassNode classNode = pc.getClassNode();
classNode.methods.add(writeReplaceMeth);
InsnList instructions = writeReplaceMeth.instructions;
// Object o = new <managed-type>() // Object o = new <managed-type>()
code.anew().setType(_managedType); // for return instructions.add(new TypeInsnNode(Opcodes.NEW, managedType.getClassNode().name));
code.dup(); // for post-<init> work instructions.add(new InsnNode(Opcodes.DUP)); // for post-<init> work
code.dup(); // for <init> instructions.add(new InsnNode(Opcodes.DUP)); // for <init>
code.invokespecial().setMethod(_managedType.getType(), "<init>", instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
void.class, null); managedType.getClassNode().name,
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE)));
// copy all the fields. // copy all the fields.
// ##### limiting to JPA @Transient limitations // ##### limiting to JPA @Transient limitations
FieldMetaData[] fmds = _meta.getFields(); FieldMetaData[] fmds = _meta.getFields();
for (FieldMetaData fmd : fmds) { for (FieldMetaData fmd : fmds) {
if (fmd.isTransient()) if (fmd.isTransient()) {
continue; continue;
// o.<field> = this.<field> (or reflective analog)
code.dup(); // for putfield
code.aload().setThis(); // for getfield
getfield(code, _managedType, fmd.getName());
putfield(code, _managedType, fmd.getName(),
fmd.getDeclaredType());
} }
// o.<field> = this.<field> (or reflective analog)
code.areturn().setType(Object.class); instructions.add(new InsnNode(Opcodes.DUP)); // for putfield
instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this for getfield
code.calculateMaxLocals(); getfield(classNode, instructions, _meta.getDescribedType(), fmd.getName(), fmd.getDeclaredType());
code.calculateMaxStack(); putfield(classNode, instructions, _meta.getDescribedType(), fmd.getName(), fmd.getDeclaredType());
}
instructions.add(new InsnNode(Opcodes.ARETURN));
} }
/** /**
@ -3682,76 +3697,93 @@ public class PCEnhancer {
* {@link ObjectOutputStream#defaultWriteObject} method, * {@link ObjectOutputStream#defaultWriteObject} method,
* but only after calling the internal <code>pcSerializing</code> method. * but only after calling the internal <code>pcSerializing</code> method.
*/ */
@Deprecated private void modifyWriteObjectMethod(ClassNode classNode, MethodNode method, boolean full) {
private void modifyWriteObjectMethod(BCMethod method, boolean full) { InsnList instructions = new InsnList();
Code code = method.getCode(true);
code.beforeFirst();
// bool clear = pcSerializing (); // bool clear = pcSerializing ();
loadManagedInstance(code, false); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
code.invokevirtual().setMethod(PRE + "Serializing", instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
boolean.class, null); classNode.name,
int clear = code.getNextLocalsIndex(); PRE + "Serializing",
code.istore().setLocal(clear); Type.getMethodDescriptor(Type.BOOLEAN_TYPE)));
int clearVarPos = full ? 2 : method.maxLocals+1;
instructions.add(new VarInsnNode(Opcodes.ISTORE, clearVarPos));
if (full) { if (full) {
// out.defaultWriteObject (); // out.defaultWriteObject ();
code.aload().setParam(0); instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param
code.invokevirtual().setMethod(ObjectOutputStream.class, instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
"defaultWriteObject", void.class, null); Type.getInternalName(ObjectOutputStream.class),
code.vreturn(); "defaultWriteObject",
Type.getMethodDescriptor(Type.VOID_TYPE)));
instructions.add(new InsnNode(Opcodes.RETURN));
method.instructions.insert(instructions);
instructions.clear();
} }
Instruction tmplate = (AccessController.doPrivileged( AbstractInsnNode insn = method.instructions.getFirst();
J2DoPrivHelper.newCodeAction())).vreturn(); do {
JumpInstruction toret; // skip to the next RETURN instruction
Instruction ret; while (insn != null && insn.getOpcode() != Opcodes.RETURN) {
code.beforeFirst(); insn = insn.getNext();
while (code.searchForward(tmplate)) {
ret = code.previous();
// if (clear) pcSetDetachedState (null);
code.iload().setLocal(clear);
toret = code.ifeq();
loadManagedInstance(code, false);
code.constant().setNull();
code.invokevirtual().setMethod(PRE + "SetDetachedState",
void.class, new Class[]{Object.class});
toret.setTarget(ret);
code.next(); // jump over return
} }
code.calculateMaxStack();
code.calculateMaxLocals(); if (insn != null) {
InsnList insns = new InsnList();
insns.add(new VarInsnNode(Opcodes.ILOAD, clearVarPos));
LabelNode lblEndIf = new LabelNode();
insns.add(new JumpInsnNode(Opcodes.IFEQ, lblEndIf));
insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
insns.add(new InsnNode(Opcodes.ACONST_NULL));
insns.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
classNode.name,
PRE + "SetDetachedState",
Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Object.class))));
insns.add(lblEndIf);
method.instructions.insertBefore(insn, insns);
insn = insn.getNext();
}
} while (insn != null);
method.instructions.insert(instructions);
} }
/** /**
* Adds a custom readObject method that delegates to the * Adds a custom readObject method that delegates to the
* {@link ObjectInputStream#readObject()} method. * {@link ObjectInputStream#readObject()} method.
*/ */
@Deprecated private void modifyReadObjectMethod(ClassNode classNode, MethodNode method, boolean full) {
private void modifyReadObjectMethod(BCMethod method, boolean full) { InsnList instructions = new InsnList();
Code code = method.getCode(true);
code.beforeFirst();
// if this instance uses synthetic detached state, note that it has // if this instance uses synthetic detached state, note that it has
// been deserialized // been deserialized
if (ClassMetaData.SYNTHETIC.equals(_meta.getDetachedState())) { if (ClassMetaData.SYNTHETIC.equals(_meta.getDetachedState())) {
loadManagedInstance(code, false);
code.getstatic().setField(PersistenceCapable.class, instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
"DESERIALIZED", Object.class); instructions.add(new FieldInsnNode(Opcodes.GETSTATIC,
code.invokevirtual().setMethod(PRE + "SetDetachedState", Type.getInternalName(PersistenceCapable.class),
void.class, new Class[]{Object.class}); "DESERIALIZED",
Type.getDescriptor(Object.class)));
instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
classNode.name,
PRE + "SetDetachedState",
Type.getMethodDescriptor(Type.VOID_TYPE, TYPE_OBJECT)));
} }
if (full) { if (full) {
// in.defaultReadObject (); // in.defaultReadObject ();
code.aload().setParam(0); instructions.add(new VarInsnNode(Opcodes.ALOAD, 1)); // 1st param
code.invokevirtual().setMethod(ObjectInputStream.class, instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
"defaultReadObject", void.class, null); Type.getInternalName(ObjectInputStream.class),
code.vreturn(); "defaultReadObject",
Type.getMethodDescriptor(Type.VOID_TYPE)));
instructions.add(new InsnNode(Opcodes.RETURN));
} }
code.calculateMaxStack(); method.instructions.insert(instructions);
code.calculateMaxLocals();
} }
/** /**
@ -4085,41 +4117,6 @@ public class PCEnhancer {
} }
} }
/**
* Helper method to get the code for the class initializer method,
* creating the method if it does not already exist.
*/
@Deprecated
private Code getOrCreateClassInitCode(boolean replaceLast) {
BCMethod clinit = _pc.getDeclaredMethod("<clinit>");
Code code;
if (clinit != null) {
code = clinit.getCode(true);
if (replaceLast) {
Code template = AccessController.doPrivileged(
J2DoPrivHelper.newCodeAction());
code.searchForward(template.vreturn());
code.previous();
code.set(template.nop());
code.next();
}
return code;
}
// add static initializer method if non exists
clinit = _pc.declareMethod("<clinit>", void.class, null);
clinit.makePackage();
clinit.setStatic(true);
clinit.setFinal(true);
code = clinit.getCode(true);
if (!replaceLast) {
code.vreturn();
code.previous();
}
return code;
}
/** /**
* Adds bytecode modifying the cloning behavior of the class being * Adds bytecode modifying the cloning behavior of the class being
* enhanced to correctly replace the <code>pcStateManager</code> * enhanced to correctly replace the <code>pcStateManager</code>