OPENJPA-2911 addProvidedFieldsMethod in ASM

This commit is contained in:
Mark Struberg 2023-06-08 19:11:23 +02:00
parent 03f2d6ae88
commit d8d829b76b
1 changed files with 144 additions and 36 deletions

View File

@ -1368,10 +1368,10 @@ public class PCEnhancer {
addManagedFieldCountMethod(classNodeTracker.getClassNode()); addManagedFieldCountMethod(classNodeTracker.getClassNode());
addReplaceFieldsMethods(classNodeTracker.getClassNode()); addReplaceFieldsMethods(classNodeTracker.getClassNode());
addProvideFieldsMethods(classNodeTracker.getClassNode());
AsmHelper.readIntoBCClass(classNodeTracker, _pc); AsmHelper.readIntoBCClass(classNodeTracker, _pc);
addProvideFieldsMethods();
addCopyFieldsMethod(); addCopyFieldsMethod();
if (_meta.getPCSuperclass() == null || getCreateSubclass()) { if (_meta.getPCSuperclass() == null || getCreateSubclass()) {
@ -1575,51 +1575,63 @@ public class PCEnhancer {
* Adds the {@link PersistenceCapable#pcProvideField} and * Adds the {@link PersistenceCapable#pcProvideField} and
* {@link PersistenceCapable#pcProvideFields} methods to the bytecode. * {@link PersistenceCapable#pcProvideFields} methods to the bytecode.
*/ */
private void addProvideFieldsMethods() private void addProvideFieldsMethods(ClassNode classNode) throws NoSuchMethodException {
throws NoSuchMethodException { MethodNode provideFieldsMeth = new MethodNode(Opcodes.ACC_PUBLIC,
// public void pcProvideField (int fieldNumber) PRE + "ProvideField",
BCMethod method = _pc.declareMethod(PRE + "ProvideField", void.class, Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE),
new Class[]{int.class}); null, null);
Code code = method.getCode(true); classNode.methods.add(provideFieldsMeth);
final InsnList instructions = provideFieldsMeth.instructions;
// adds everything through the switch () final int relLocal = beginSwitchMethod(classNode, PRE + "ProvideField", instructions, false);
int relLocal = beginSwitchMethod(PRE + "ProvideField", code, false);
// if no fields in this inst, just throw exception // if no fields in this inst, just throw exception
FieldMetaData[] fmds = getCreateSubclass() ? _meta.getFields() FieldMetaData[] fmds = getCreateSubclass() ? _meta.getFields()
: _meta.getDeclaredFields(); : _meta.getDeclaredFields();
if (fmds.length == 0) if (fmds.length == 0) {
throwException(code, IllegalArgumentException.class); instructions.add(throwException(IllegalArgumentException.class));
}
else { else {
// switch (val) // switch (val)
code.iload().setLocal(relLocal); instructions.add(new VarInsnNode(Opcodes.ILOAD, relLocal));
TableSwitchInstruction tabins = code.tableswitch();
tabins.setLow(0);
tabins.setHigh(fmds.length - 1);
// <field> = pcStateManager.provided<type>Field LabelNode defaultCase = new LabelNode();
// (this, fieldNumber); TableSwitchInsnNode ts = new TableSwitchInsnNode(0, fmds.length - 1, defaultCase);
instructions.add(ts);
// <field> = pcStateManager.provided<type>Field(this, fieldNumber);
for (FieldMetaData fmd : fmds) { for (FieldMetaData fmd : fmds) {
tabins.addTarget(loadManagedInstance(code, false)); // case xxx:
code.getfield().setField(SM, SMTYPE); LabelNode caseLabel = new LabelNode();
loadManagedInstance(code, false); instructions.add(caseLabel);
code.iload().setParam(0); ts.labels.add(caseLabel);
loadManagedInstance(code, false);
addGetManagedValueCode(code, fmd); // load pcStateManager to stack
code.invokeinterface().setMethod(getStateManagerMethod instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
(fmd.getDeclaredType(), "provided", false, false)); instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, SM, Type.getDescriptor(SMTYPE)));
code.vreturn();
// invoke StateManager#provided
final Method smProvidedMeth = getStateManagerMethod(fmd.getDeclaredType(), "provided", false, false);
instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
instructions.add(new VarInsnNode(Opcodes.ILOAD, 1)); // fieldNr int
instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this for the getfield
addGetManagedValueCode(classNode, instructions, fmd, true);
instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
Type.getInternalName(SMTYPE),
smProvidedMeth.getName(),
Type.getMethodDescriptor(smProvidedMeth)));
instructions.add(new InsnNode(Opcodes.RETURN));
} }
// default: throw new IllegalArgumentException () instructions.add(defaultCase);
tabins.setDefaultTarget(throwException instructions.add(throwException(IllegalArgumentException.class));
(code, IllegalArgumentException.class));
} }
code.calculateMaxStack(); addMultipleFieldsMethodVersion(classNode, provideFieldsMeth, false);
code.calculateMaxLocals();
addMultipleFieldsMethodVersion(method, false);
} }
/** /**
@ -1649,13 +1661,13 @@ public class PCEnhancer {
TableSwitchInsnNode ts = new TableSwitchInsnNode(0, fmds.length - 1, defaultCase); TableSwitchInsnNode ts = new TableSwitchInsnNode(0, fmds.length - 1, defaultCase);
instructions.add(ts); instructions.add(ts);
// <field> = pcStateManager.replace<type>Field // <field> = pcStateManager.replace<type>Field(this, fieldNumber);
// (this, fieldNumber);
for (FieldMetaData fmd : fmds) { for (FieldMetaData fmd : fmds) {
// case xxx: // case xxx:
LabelNode caseLabel = new LabelNode(); LabelNode caseLabel = new LabelNode();
instructions.add(caseLabel); instructions.add(caseLabel);
ts.labels.add(caseLabel); ts.labels.add(caseLabel);
instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
// load pcStateManager to stack // load pcStateManager to stack
@ -1665,7 +1677,6 @@ public class PCEnhancer {
// invoke StateManager#replace // invoke StateManager#replace
instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
instructions.add(new VarInsnNode(Opcodes.ILOAD, 1)); // fieldNr int instructions.add(new VarInsnNode(Opcodes.ILOAD, 1)); // fieldNr int
final Method rmReplaceMeth = getStateManagerMethod(fmd.getDeclaredType(), "replace", true, false); final Method rmReplaceMeth = getStateManagerMethod(fmd.getDeclaredType(), "replace", true, false);
instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE,
Type.getInternalName(SMTYPE), Type.getInternalName(SMTYPE),
@ -4552,6 +4563,54 @@ public class PCEnhancer {
* The instance to access must already be on the top of the * The instance to access must already be on the top of the
* stack when this is invoked. * stack when this is invoked.
*/ */
private void getfield(ClassNode classNode, InsnList instructions, Class declarer, String attrName, Class fieldType) {
// first, see if we can convert the attribute name to a field name
String fieldName = toBackingFieldName(attrName);
FieldNode field = findField(classNode, declarer, fieldName);
if (getCreateSubclass() && (field == null || !((field.access & Opcodes.ACC_PUBLIC) > 0))) {
// we're creating the subclass, not redefining the user type.
// Reflection.getXXX(this, Reflection.findField(...));
throw new UnsupportedOperationException("MSX TODO IMPLEMENT!");
}
else {
instructions.add(new FieldInsnNode(Opcodes.GETFIELD, Type.getInternalName(declarer), fieldName, Type.getDescriptor(fieldType)));
}
}
private FieldNode findField(ClassNode classNode, Class clazz, String fieldName) {
if (classNode != null) {
final Optional<FieldNode> field = classNode.fields.stream()
.filter(f -> f.name.equals(fieldName))
.findFirst();
if (field.isPresent()) {
return field.get();
}
}
try {
final Field field = clazz.getDeclaredField(fieldName);
return new FieldNode(Opcodes.ACC_PRIVATE, field.getName(), Type.getDescriptor(field.getType()), null, null);
}
catch (NoSuchFieldException e) {
if (clazz.getSuperclass() != Object.class) {
return findField(null, clazz.getSuperclass(), fieldName);
}
}
return null;
}
/**
* Adds to <code>code</code> the instructions to get field
* <code>attrName</code> declared in type <code>declarer</code>
* onto the top of the stack.
* <p>
* The instance to access must already be on the top of the
* stack when this is invoked.
*/
@Deprecated
private void getfield(Code code, BCClass declarer, String attrName) { private void getfield(Code code, BCClass declarer, String attrName) {
if (declarer == null) if (declarer == null)
declarer = _managedType; declarer = _managedType;
@ -5115,6 +5174,7 @@ public class PCEnhancer {
addGetManagedValueCode(code, fmd, true); addGetManagedValueCode(code, fmd, true);
} }
/** /**
* Load the field value specified by <code>fmd</code> onto the stack. * Load the field value specified by <code>fmd</code> onto the stack.
* Before this method is called, the object that the data should be loaded * Before this method is called, the object that the data should be loaded
@ -5125,6 +5185,54 @@ public class PCEnhancer {
* context. If <code>false</code>, then the instance on the top of the stack * context. If <code>false</code>, then the instance on the top of the stack
* might be a superclass of the current execution context's 'this' instance. * might be a superclass of the current execution context's 'this' instance.
*/ */
private void addGetManagedValueCode(ClassNode classNode, InsnList instructions, FieldMetaData fmd, boolean fromSameClass) {
// if redefining, then we must always reflect (or access the field
// directly if accessible), since the redefined methods will always
// trigger method calls to StateManager, even from internal direct-
// access usage. We could work around this by not redefining, and
// just do a subclass approach instead. But this is not a good option,
// since it would sacrifice lazy loading and efficient dirty tracking.
if (getRedefine() || isFieldAccess(fmd)) {
getfield(classNode, instructions, getType(_meta), fmd.getName(), fmd.getDeclaredType());
}
else if (getCreateSubclass()) {
// property access, and we're not redefining. If we're operating
// on an instance that is definitely the same type as 'this', then
// call superclass method to bypass tracking. Otherwise, reflect
// to both bypass tracking and avoid class verification errors.
if (fromSameClass) {
Method meth = (Method) fmd.getBackingMember();
instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
Type.getInternalName(meth.getDeclaringClass()),
meth.getName(),
Type.getMethodDescriptor(meth)));
}
else {
getfield(classNode, instructions, getType(_meta), fmd.getName(), fmd.getDeclaredType());
}
}
else {
// regular enhancement + property access
Method meth = (Method) fmd.getBackingMember();
instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
classNode.name,
PRE + meth.getName(),
Type.getMethodDescriptor(meth)));
}
}
/**
* Load the field value specified by <code>fmd</code> onto the stack.
* Before this method is called, the object that the data should be loaded
* from will be on the top of the stack.
*
* @param fromSameClass if <code>true</code>, then <code>fmd</code> is
* being loaded from an instance of the same class as the current execution
* context. If <code>false</code>, then the instance on the top of the stack
* might be a superclass of the current execution context's 'this' instance.
*/
@Deprecated
private void addGetManagedValueCode(Code code, FieldMetaData fmd, private void addGetManagedValueCode(Code code, FieldMetaData fmd,
boolean fromSameClass) boolean fromSameClass)
throws NoSuchMethodException { throws NoSuchMethodException {