OPENJPA-2911 fix findField

This commit is contained in:
Mark Struberg 2023-06-10 00:01:42 +02:00
parent f0fe08baaa
commit 810f85ff9f
2 changed files with 53 additions and 346 deletions

View File

@ -120,12 +120,10 @@ import org.apache.xbean.asm9.tree.VarInsnNode;
import serp.bytecode.BCClass; import serp.bytecode.BCClass;
import serp.bytecode.BCField; import serp.bytecode.BCField;
import serp.bytecode.BCMethod; import serp.bytecode.BCMethod;
import serp.bytecode.ClassInstruction;
import serp.bytecode.Code; import serp.bytecode.Code;
import serp.bytecode.Constants; import serp.bytecode.Constants;
import serp.bytecode.Exceptions; import serp.bytecode.Exceptions;
import serp.bytecode.FieldInstruction; import serp.bytecode.FieldInstruction;
import serp.bytecode.GetFieldInstruction;
import serp.bytecode.IfInstruction; import serp.bytecode.IfInstruction;
import serp.bytecode.Instruction; import serp.bytecode.Instruction;
import serp.bytecode.JumpInstruction; import serp.bytecode.JumpInstruction;
@ -600,14 +598,14 @@ public class PCEnhancer {
_log.trace(_loc.get("enhance-start", type)); _log.trace(_loc.get("enhance-start", type));
} }
final ClassNode classNode = AsmHelper.readClassNode(_managedType.toByteArray()); pc = AsmHelper.toClassNode(_pc);
configureBCs(classNode); configureBCs();
// validate properties before replacing field access so that // validate properties before replacing field access so that
// we build up a record of backing fields, etc // we build up a record of backing fields, etc
if (isPropertyAccess(_meta)) { if (isPropertyAccess(_meta)) {
validateProperties(classNode); validateProperties();
if (getCreateSubclass()) { if (getCreateSubclass()) {
addAttributeTranslation(); addAttributeTranslation();
} }
@ -644,7 +642,8 @@ public class PCEnhancer {
} }
} }
private void configureBCs(final ClassNode classNode) { private void configureBCs() {
final ClassNode classNode = pc.getClassNode();
if (!_bcsConfigured) { if (!_bcsConfigured) {
if (getRedefine()) { if (getRedefine()) {
if (_managedType.getAttribute(REDEFINED_ATTRIBUTE) == null) { if (_managedType.getAttribute(REDEFINED_ATTRIBUTE) == null) {
@ -722,25 +721,13 @@ public class PCEnhancer {
} }
} }
/**
* DELTEME
* Check if the fields are the same
*
* @deprecated should get removed after migration to ASM is done!
*/
public static void assertSameField(Field field, BCField bcField) {
if (field == null && bcField != null || field != null && bcField == null ||
field != null && bcField != null && !field.getName().equals(bcField.getName())) {
throw new IllegalStateException("MSX ASMTODO " + bcField + " " + field);
}
}
/** /**
* Validate that the methods that use a property-access instance are * Validate that the methods that use a property-access instance are
* written correctly. This method also gathers information on each * written correctly. This method also gathers information on each
* property's backing field. * property's backing field.
*/ */
private void validateProperties(ClassNode classNode) { private void validateProperties() {
final ClassNode classNode = pc.getClassNode();
FieldMetaData[] fmds; FieldMetaData[] fmds;
if (getCreateSubclass()) { if (getCreateSubclass()) {
fmds = _meta.getFields(); fmds = _meta.getFields();
@ -751,7 +738,7 @@ public class PCEnhancer {
Method meth; Method meth;
BCMethod bcGetter, bcSetter; BCMethod bcGetter, bcSetter;
BCField bcReturned, bcAssigned = null; BCField bcReturned = null;
Method getter, setter; Method getter, setter;
Field returned, assigned = null; Field returned, assigned = null;
@ -782,60 +769,58 @@ public class PCEnhancer {
true); true);
continue; continue;
} }
bcReturned = getReturnedField_old(bcGetter);
getter = meth; getter = meth;
returned = getReturnedField(classNode, getter); returned = getReturnedField(classNode, getter);
//X TODO remove
PCEnhancer.assertSameField(returned, bcReturned);
if (returned != null) { if (returned != null) {
registerBackingFieldInfo(fmd, bcGetter, bcReturned); registerBackingFieldInfo(fmd, getter, returned);
} }
bcSetter = declaringType.getDeclaredMethod(getSetterName(fmd), setter = getMethod(meth.getDeclaringClass(), getSetterName(fmd), fmd.getDeclaredType());
new Class[]{fmd.getDeclaredType()});
if (bcSetter == null) { if (setter == null) {
if (bcReturned == null) { if (returned == null) {
addViolation("property-no-setter", addViolation("property-no-setter",
new Object[]{fmd}, true); new Object[]{fmd}, true);
continue; continue;
} }
else if (!getRedefine()) { else if (!getRedefine()) {
// create synthetic setter // create synthetic setter
bcSetter = _managedType.declareMethod(getSetterName(fmd), MethodNode setterNode = new MethodNode(Opcodes.ACC_PRIVATE,
void.class, new Class[]{fmd.getDeclaredType()}); getSetterName(fmd),
bcSetter.makePrivate(); Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(fmd.getDeclaredType())),
Code code = bcSetter.getCode(true); null, null);
code.aload().setThis(); //X TODO: pc or managedType?
code.xload().setParam(0); pc.getClassNode().methods.add(setterNode);
code.putfield().setField(bcReturned); InsnList instructions = setterNode.instructions;
code.vreturn(); instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
code.calculateMaxStack(); instructions.add(new VarInsnNode(AsmHelper.getLoadInsn(fmd.getDeclaredType()), 1)); // param1
code.calculateMaxLocals(); instructions.add(new FieldInsnNode(Opcodes.PUTFIELD,
Type.getInternalName(returned.getDeclaringClass()),
returned.getName(),
Type.getDescriptor(fmd.getDeclaredType())));
instructions.add(new InsnNode(Opcodes.RETURN));
} }
} }
if (bcSetter != null) { if (setter != null) {
bcAssigned = getAssignedField_old(bcSetter);
assigned = getAssignedField(classNode, getMethod(fmd.getDeclaringType(), fmd.getSetterName(), new Class[]{fmd.getDeclaredType()})); assigned = getAssignedField(classNode, getMethod(fmd.getDeclaringType(), fmd.getSetterName(), new Class[]{fmd.getDeclaredType()}));
assertSameField(assigned, bcAssigned);
} }
if (bcAssigned != null) { if (assigned != null) {
if (bcSetter != null) if (setter != null)
registerBackingFieldInfo(fmd, bcSetter, bcAssigned); registerBackingFieldInfo(fmd, setter, assigned);
if (bcAssigned != bcReturned) if (assigned != returned)
addViolation("property-setter-getter-mismatch", new Object[] addViolation("property-setter-getter-mismatch", new Object[]
{fmd, bcAssigned.getName(), (bcReturned == null) {fmd, assigned.getName(), (returned == null)
? null : bcReturned.getName()}, false); ? null : returned.getName()}, false);
} }
} }
} }
private void registerBackingFieldInfo(FieldMetaData fmd, BCMethod method, BCField field) { private void registerBackingFieldInfo(FieldMetaData fmd, Method method, Field field) {
if (_backingFields == null) { if (_backingFields == null) {
_backingFields = new HashMap(); _backingFields = new HashMap();
} }
@ -852,6 +837,7 @@ public class PCEnhancer {
_fieldsToAttrs.put(field.getName(), fmd.getName()); _fieldsToAttrs.put(field.getName(), fmd.getName());
} }
private void addAttributeTranslation() { private void addAttributeTranslation() {
// Get all field metadata // Get all field metadata
@ -933,12 +919,6 @@ public class PCEnhancer {
* Return the field returned by the given method, or null if none. * Return the field returned by the given method, or null if none.
* Package-protected and static for testing. * Package-protected and static for testing.
*/ */
@Deprecated
static BCField getReturnedField_old(BCMethod meth) {
return findField_old(meth, new Code().xreturn()
.setType(meth.getReturnType()), false);
}
static Field getReturnedField(ClassNode classNode, Method meth) { static Field getReturnedField(ClassNode classNode, Method meth) {
return findField(classNode, meth, (ain) -> ain.getOpcode() == AsmHelper.getReturnInsn(meth.getReturnType()), false); return findField(classNode, meth, (ain) -> ain.getOpcode() == AsmHelper.getReturnInsn(meth.getReturnType()), false);
} }
@ -948,12 +928,6 @@ public class PCEnhancer {
* Return the field assigned in the given method, or null if none. * Return the field assigned in the given method, or null if none.
* Package-protected and static for testing. * Package-protected and static for testing.
*/ */
@Deprecated
static BCField getAssignedField_old(BCMethod meth) {
return findField_old(meth, (AccessController.doPrivileged(
J2DoPrivHelper.newCodeAction())).putfield(), true);
}
static Field getAssignedField(ClassNode classNode, Method meth) { static Field getAssignedField(ClassNode classNode, Method meth) {
return findField(classNode, meth, (ain) -> ain.getOpcode() == Opcodes.PUTFIELD, true); return findField(classNode, meth, (ain) -> ain.getOpcode() == Opcodes.PUTFIELD, true);
} }
@ -963,75 +937,6 @@ public class PCEnhancer {
* null if non-fields (methods, literals, parameters, variables) are * null if non-fields (methods, literals, parameters, variables) are
* returned, or if non-parameters are assigned to fields. * returned, or if non-parameters are assigned to fields.
*/ */
@Deprecated
private static BCField findField_old(BCMethod meth, Instruction template, boolean findAccessed) {
// ignore any static methods. OpenJPA only currently supports
// non-static setters and getters
if (meth.isStatic())
return null;
Code code = meth.getCode(false);
if (code == null)
return null;
code.beforeFirst();
BCField field = null, cur;
Instruction templateIns, prevIns, earlierIns;
while (code.searchForward(template)) {
int backupCount = 3;
templateIns = code.previous();
if (!code.hasPrevious())
return null;
prevIns = code.previous();
if (prevIns instanceof ClassInstruction
&& code.hasPrevious()) {
prevIns = code.previous();
backupCount++;
}
if (!code.hasPrevious())
return null;
earlierIns = code.previous();
// if the opcode two before the template was an aload_0, check
// against the middle instruction based on what type of find
// we're doing
if (!(earlierIns instanceof LoadInstruction)
|| !((LoadInstruction) earlierIns).isThis())
return null;
// if the middle instruction was a getfield, then it's the
// field that's being accessed
if (!findAccessed && prevIns instanceof GetFieldInstruction) {
final FieldInstruction fPrevIns = (FieldInstruction) prevIns;
cur = AccessController.doPrivileged(
J2DoPrivHelper.getFieldInstructionFieldAction(fPrevIns));
// if the middle instruction was an xload_1, then the
// matched instruction is the field that's being set.
}
else if (findAccessed && prevIns instanceof LoadInstruction
&& ((LoadInstruction) prevIns).getParam() == 0) {
final FieldInstruction fTemplateIns =
(FieldInstruction) templateIns;
cur = fTemplateIns.getField();
}
else
return null;
if (field != null && cur != field)
return null;
field = cur;
// ready for next search iteration
while (backupCount > 0) {
code.next();
backupCount--;
}
}
return field;
}
private static Field findField(ClassNode classNode, Method meth, Predicate<AbstractInsnNode> ain, boolean findAccessed) { private static Field findField(ClassNode classNode, Method meth, Predicate<AbstractInsnNode> ain, boolean findAccessed) {
// ignore any static methods. OpenJPA only currently supports // ignore any static methods. OpenJPA only currently supports
// non-static setters and getters // non-static setters and getters
@ -1100,7 +1005,7 @@ public class PCEnhancer {
} }
if (field != null && cur != field) { if (field != null && !cur.equals(field)) {
return null; return null;
} }
field = cur; field = cur;
@ -1780,57 +1685,6 @@ public class PCEnhancer {
addMultipleFieldsMethodVersion(classNode, copyFieldMeth, true); addMultipleFieldsMethodVersion(classNode, copyFieldMeth, true);
} }
/**
* Adds the {@link PersistenceCapable#pcCopyFields} method to the bytecode.
*/
@Deprecated
private void addCopyFieldsMethod()
throws NoSuchMethodException {
// public void pcCopyField (Object pc, int field)
BCMethod method = _pc.declareMethod(PRE + "CopyField",
void.class.getName(),
new String[]{_managedType.getName(), int.class.getName()});
method.makeProtected();
Code code = method.getCode(true);
// adds everything through the switch ()
int relLocal = beginSwitchMethod(PRE + "CopyField", code, true);
// if no fields in this inst, just throw exception
FieldMetaData[] fmds = getCreateSubclass() ? _meta.getFields()
: _meta.getDeclaredFields();
if (fmds.length == 0)
throwException(code, IllegalArgumentException.class);
else {
// switch (val)
code.iload().setLocal(relLocal);
TableSwitchInstruction tabins = code.tableswitch();
tabins.setLow(0);
tabins.setHigh(fmds.length - 1);
for (FieldMetaData fmd : fmds) {
// <field> = other.<field>;
// or set<field> (other.get<field>);
tabins.addTarget(loadManagedInstance(code, false, fmd));
code.aload().setParam(0);
addGetManagedValueCode(code, fmd, false);
addSetManagedValueCode(code, fmd);
// break;
code.vreturn();
}
// default: throw new IllegalArgumentException ()
tabins.setDefaultTarget(throwException
(code, IllegalArgumentException.class));
}
code.calculateMaxStack();
code.calculateMaxLocals();
addMultipleFieldsMethodVersion(method, true);
}
/** /**
* Helper method to add the code common to the beginning of both the * Helper method to add the code common to the beginning of both the
* pcReplaceField method and the pcProvideField method. This includes * pcReplaceField method and the pcProvideField method. This includes
@ -1885,59 +1739,6 @@ public class PCEnhancer {
return relLocal; return relLocal;
} }
/**
* Helper method to add the code common to the beginning of both the
* pcReplaceField method and the pcProvideField method. This includes
* calculating the relative field number of the desired field and calling
* the superclass if necessary.
*
* @return the index in which the local variable holding the relative
* field number is stored
*/
@Deprecated
private int beginSwitchMethod(String name, Code code, boolean copy) {
int fieldNumber = (copy) ? 1 : 0;
int relLocal = code.getNextLocalsIndex();
if (getCreateSubclass()) {
code.iload().setParam(fieldNumber);
code.istore().setLocal(relLocal);
return relLocal;
}
// int rel = fieldNumber - pcInheritedFieldCount
code.iload().setParam(fieldNumber);
code.getstatic().setField(INHERIT, int.class);
code.isub();
code.istore().setLocal(relLocal);
code.iload().setLocal(relLocal);
// super: if (rel < 0) super.pcReplaceField (fieldNumber); return;
// no super: if (rel < 0) throw new IllegalArgumentException ();
JumpInstruction ifins = code.ifge();
if (_meta.getPCSuperclass() != null) {
loadManagedInstance(code, false);
String[] args;
if (copy) {
args = new String[]{getType(_meta.getPCSuperclassMetaData()).
getName(), int.class.getName()};
code.aload().setParam(0);
}
else
args = new String[]{int.class.getName()};
code.iload().setParam(fieldNumber);
code.invokespecial().setMethod(getType(_meta.
getPCSuperclassMetaData()).getName(), name,
void.class.getName(), args);
code.vreturn();
}
else
throwException(code, IllegalArgumentException.class);
ifins.setTarget(code.nop());
return relLocal;
}
/** /**
* This helper method, given the pcReplaceField or pcProvideField * This helper method, given the pcReplaceField or pcProvideField
* method, adds the bytecode for the corresponding 'plural' version * method, adds the bytecode for the corresponding 'plural' version
@ -2061,110 +1862,6 @@ public class PCEnhancer {
instructions.add(new InsnNode(Opcodes.RETURN)); instructions.add(new InsnNode(Opcodes.RETURN));
} }
/**
* This helper method, given the pcReplaceField or pcProvideField
* method, adds the bytecode for the corresponding 'plural' version
* of the method -- the version that takes an int[] of fields to
* to access rather than a single field. The multiple fields version
* simply loops through the provided indexes and delegates to the
* singular version for each one.
*/
@Deprecated
private void addMultipleFieldsMethodVersion(BCMethod single, boolean copy) {
// public void <method>s (int[] fields)
Class[] args = (copy) ? new Class[]{Object.class, int[].class}
: new Class[]{int[].class};
BCMethod method = _pc.declareMethod(single.getName() + "s",
void.class, args);
Code code = method.getCode(true);
int fieldNumbers = 0;
int inst = 0;
if (copy) {
fieldNumbers = 1;
if (getCreateSubclass()) {
// get the managed instance into the local variable table
code.aload().setParam(0);
code.invokestatic().setMethod(ImplHelper.class,
"getManagedInstance", Object.class,
new Class[]{Object.class});
code.checkcast().setType(_managedType);
inst = code.getNextLocalsIndex();
code.astore().setLocal(inst);
// there might be a difference between the classes of 'this'
// vs 'other' in this context; use the PC methods to get the SM
code.aload().setParam(0);
code.aload().setThis();
code.getfield().setField(SM, SMTYPE);
code.invokestatic().setMethod(ImplHelper.class,
"toPersistenceCapable", PersistenceCapable.class,
new Class[]{Object.class, Object.class});
code.invokeinterface().setMethod(PersistenceCapable.class,
"pcGetStateManager", StateManager.class, null);
}
else {
// XXX other = (XXX) pc;
code.aload().setParam(0);
code.checkcast().setType(_pc);
inst = code.getNextLocalsIndex();
code.astore().setLocal(inst);
// access the other's sm field directly
code.aload().setLocal(inst);
code.getfield().setField(SM, SMTYPE);
}
// if (other.pcStateManager != pcStateManager)
// throw new IllegalArgumentException
loadManagedInstance(code, false);
code.getfield().setField(SM, SMTYPE);
JumpInstruction ifins = code.ifacmpeq();
throwException(code, IllegalArgumentException.class);
ifins.setTarget(code.nop());
// if (pcStateManager == null)
// throw new IllegalStateException
loadManagedInstance(code, false);
code.getfield().setField(SM, SMTYPE);
ifins = code.ifnonnull();
throwException(code, IllegalStateException.class);
ifins.setTarget(code.nop());
}
// for (int i = 0;
code.constant().setValue(0);
int idx = code.getNextLocalsIndex();
code.istore().setLocal(idx);
JumpInstruction testins = code.go2();
// <method> (fields[i]);
Instruction bodyins = loadManagedInstance(code, false);
if (copy)
code.aload().setLocal(inst);
code.aload().setParam(fieldNumbers);
code.iload().setLocal(idx);
code.iaload();
code.invokevirtual().setMethod(single);
// i++;
code.iinc().setIncrement(1).setLocal(idx);
// i < fields.length
testins.setTarget(code.iload().setLocal(idx));
code.aload().setParam(fieldNumbers);
code.arraylength();
code.ificmplt().setTarget(bodyins);
code.vreturn();
code.calculateMaxStack();
code.calculateMaxLocals();
}
/** /**
* Adds the 'stock' methods to the bytecode; these include methods * Adds the 'stock' methods to the bytecode; these include methods
* like {@link PersistenceCapable#pcFetchObjectId} * like {@link PersistenceCapable#pcFetchObjectId}
@ -5893,7 +5590,7 @@ public class PCEnhancer {
boolean skipEnhance(BCMethod m); boolean skipEnhance(BCMethod m);
} }
private void addGetIDOwningClass() throws NoSuchMethodException { private void addGetIDOwningClass() {
BCMethod method = _pc.declareMethod(PRE + "GetIDOwningClass", BCMethod method = _pc.declareMethod(PRE + "GetIDOwningClass",
Class.class, null); Class.class, null);
Code code = method.getCode(true); Code code = method.getCode(true);

View File

@ -18,6 +18,15 @@
*/ */
package org.apache.openjpa.enhance; package org.apache.openjpa.enhance;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.apache.openjpa.util.asm.AsmHelper;
import org.apache.openjpa.util.asm.ClassNodeTracker;
import org.apache.xbean.asm9.ClassReader;
import org.apache.xbean.asm9.Opcodes;
import org.apache.xbean.asm9.tree.ClassNode;
import junit.framework.TestCase; import junit.framework.TestCase;
import serp.bytecode.BCClass; import serp.bytecode.BCClass;
import serp.bytecode.BCField; import serp.bytecode.BCField;
@ -36,11 +45,12 @@ public class TestPCEnhancerFindField
return field; return field;
} }
public void testPCEnhancerFindField() { public void testPCEnhancerFindField() throws Exception {
Project proj = new Project(); ClassNode classNode = AsmHelper.readClassNode(this.getClass().getClassLoader(), TestPCEnhancerFindField.class.getName());
BCClass bc = proj.loadClass(getClass());
BCMethod meth = bc.getMethods("myMethod")[0]; Method meth = TestPCEnhancerFindField.class.getMethod("myMethod");
BCField field = PCEnhancer.getReturnedField_old(meth);
Field field = PCEnhancer.getReturnedField(classNode, meth);
assertEquals("field", field.getName()); assertEquals("field", field.getName());
} }
} }