OPENJPA-2911 getIdClassConstructorParmOrder in ASM

This commit is contained in:
Mark Struberg 2023-07-16 17:24:42 +02:00
parent 0ab02d6965
commit a25ad4fc95
3 changed files with 101 additions and 111 deletions

View File

@ -31,6 +31,7 @@ import java.io.ObjectStreamClass;
import java.io.ObjectStreamException;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@ -3592,19 +3593,11 @@ public class PCEnhancer {
Type.LONG_TYPE.getDescriptor(),
null, uid);
pc.getClassNode().fields.add(serVersField);
/* should be done by ASM FieldNode already
Code code = getOrCreateClassInitCode(false);
code.beforeFirst();
code.constant().setValue(uid.longValue());
code.putstatic().setField(field);
*/
}
}
MethodNode writeObjectMeth = AsmHelper.getMethodNode(pc.getClassNode(), "writeObject",
void.class, ObjectOutputStream.class)
.orElse(null);
MethodNode writeObjectMeth = AsmHelper.getMethodNode(pc.getClassNode(), "writeObject",void.class, ObjectOutputStream.class)
.orElse(null);
boolean full = writeObjectMeth == null;
@ -4320,32 +4313,6 @@ public class PCEnhancer {
return false;
}
private boolean setVisibilityToSuperMethod(BCMethod method) {
BCMethod[] methods = _managedType.getMethods(method.getName(),
method.getParamTypes());
if (methods.length == 0)
throw new UserException(_loc.get("no-accessor",
_managedType.getName(), method.getName()));
BCMethod superMeth = methods[0];
if (superMeth.isPrivate()) {
method.makePrivate();
return true;
}
else if (superMeth.isPackage()) {
method.makePackage();
return true;
}
else if (superMeth.isProtected()) {
method.makeProtected();
return true;
}
else if (superMeth.isPublic()) {
method.makePublic();
return true;
}
return false;
}
/**
* Adds a non-static getter that delegates to the super methods, and
* performs any necessary field tracking.
@ -5363,22 +5330,6 @@ public class PCEnhancer {
}
}
/**
* Add the {@link Instruction}s to load the instance to modify onto the
* stack, and return it. If <code>forStatic</code> is set, then
* <code>code</code> is in an accessor method or another static method;
* otherwise, it is in one of the PC-specified methods.
*
* @return the first instruction added to <code>code</code>.
*/
@Deprecated
private Instruction loadManagedInstance(Code code, boolean forStatic, FieldMetaData fmd) {
if (forStatic && isFieldAccess(fmd))
return code.aload().setParam(0);
return code.aload().setThis();
}
/**
* Add the {@link Instruction}s to load the instance to modify onto the
* stack, and return it. If <code>forStatic</code> is set, then
@ -5395,18 +5346,6 @@ public class PCEnhancer {
return new VarInsnNode(Opcodes.ALOAD, 0); // this
}
/**
* Add the {@link Instruction}s to load the instance to modify onto the
* stack, and return it. This method should not be used to load static
* fields.
*
* @return the first instruction added to <code>code</code>.
*/
@Deprecated
private Instruction loadManagedInstance(Code code, boolean forStatic) {
return loadManagedInstance(code, forStatic, null);
}
private int getAccessorParameterOffset(FieldMetaData fmd) {
return isFieldAccess(fmd) ? 1 : 0;
}
@ -5840,67 +5779,78 @@ public class PCEnhancer {
* a matching constructor for the provided pk fields. If a match is found, it returns
* the order (relative to the field metadata) of the constructor parameters. If a match
* is not found, returns null.
*
* We use byte code analysis to find the fields the ct works on.
*/
private int[] getIdClassConstructorParmOrder(Class<?> oidType, ArrayList<Integer> pkfields,
FieldMetaData[] fmds) {
Project project = new Project();
BCClass bc = project.loadClass(oidType);
BCMethod[] methods = bc.getDeclaredMethods("<init>");
if (methods == null || methods.length == 0) {
private int[] getIdClassConstructorParmOrder(Class<?> oidType, List<Integer> pkfields, FieldMetaData[] fmds) {
final ClassNode classNode = AsmHelper.readClassNode(oidType);
final List<MethodNode> cts = classNode.methods.stream()
.filter(m -> "<init>".equals(m.name))
.collect(Collectors.toList());
if (cts.isEmpty()) {
return null;
}
int parmOrder[] = new int[pkfields.size()];
for (BCMethod method : methods) {
// constructor must be public
if (!method.isPublic()) {
for (MethodNode ct : cts) {
if ((ct.access & Opcodes.ACC_PUBLIC) == 0) {
// ignore non public constructors
continue;
}
Class<?>[] parmTypes = method.getParamTypes();
Type[] argTypes = Type.getArgumentTypes(ct.desc);
// make sure the constructors have the same # of parms as
// the number of pk fields
if (parmTypes.length != pkfields.size()) {
if (listSize(pkfields) != argTypes.length) {
continue;
}
int parmOrderIndex = 0;
Code code = method.getCode(false);
Instruction[] ins = code.getInstructions();
for (int i = 0; i < ins.length; i++) {
if (ins[i] instanceof PutFieldInstruction) {
PutFieldInstruction pfi = (PutFieldInstruction)ins[i];
for (int j = 0; j < pkfields.size(); j++) {
int fieldNum = pkfields.get(j);
// Compare the field being set with the current pk field
String parmName = fmds[fieldNum].getName();
Class<?> parmType = fmds[fieldNum].getType();
if (parmName.equals(pfi.getFieldName())) {
// backup and examine the load instruction parm
if (i > 0 && ins[i-1] instanceof LoadInstruction) {
LoadInstruction li = (LoadInstruction)ins[i-1];
// Get the local index from the instruction. This will be the index
// of the constructor parameter. must be less than or equal to the
// max parm index to prevent from picking up locals that could have
// been produced within the constructor. Also make sure the parm type
// matches the fmd type
int parm = li.getLocal();
if (parm <= pkfields.size() && parmTypes[parm-1].equals(parmType)) {
parmOrder[parmOrderIndex] = fieldNum;
parmOrderIndex++;
}
} else {
// Some other instruction found. can't make a determination of which local/parm
// is being used on the putfield.
break;
AbstractInsnNode insn = ct.instructions.getFirst();
// skip to the next PUTFIELD instruction
while ((insn = searchNextInstruction(insn, i -> i.getOpcode() == Opcodes.PUTFIELD)) != null) {
FieldInsnNode putField = (FieldInsnNode) insn;
for (int i = 0; i < pkfields.size(); i++) {
int fieldNum = pkfields.get(i);
// Compare the field being set with the current pk field
String parmName = fmds[fieldNum].getName();
Class<?> parmType = fmds[fieldNum].getType();
if (parmName.equals(putField.name)) {
// backup and examine the load instruction parm
if (AsmHelper.isLoadInsn(insn.getPrevious())) {
// Get the local index from the instruction. This will be the index
// of the constructor parameter. must be less than or equal to the
// max parm index to prevent from picking up locals that could have
// been produced within the constructor. Also make sure the parm type
// matches the fmd type
VarInsnNode loadInsn = (VarInsnNode) insn.getPrevious();
int parm = AsmHelper.getParamIndex(ct, loadInsn.var);
if (parm < pkfields.size() && argTypes[parm].equals(Type.getType(parmType))) {
parmOrder[parmOrderIndex] = fieldNum;
parmOrderIndex++;
}
} else {
// Some other instruction found. can't make a determination of which local/parm
// is being used on the putfield.
break;
}
}
}
insn = insn.getNext();
}
if (parmOrderIndex == pkfields.size()) {
return parmOrder;
}
}
return null;
}
private int listSize(Collection<?> coll) {
return coll == null ? 0 : coll.size();
}
}

View File

@ -57,15 +57,22 @@ public final class AsmHelper {
/**
* Read the binary bytecode from the class with the given name
* @param classBytes the binary of the class
* @param clazz the class to read into the ClassNode
* @return the ClassNode constructed from that class
*/
public static ClassNode readClassNode(byte[] classBytes) {
ClassReader cr = new ClassReader(classBytes);
ClassNode classNode = new ClassNode();
cr.accept(classNode, 0);
public static ClassNode readClassNode(Class<?> clazz) {
int extPos = clazz.getName().lastIndexOf('.') + 1;
String className = clazz.getName().substring(extPos);
try (InputStream in = clazz.getResourceAsStream(className + ".class")) {
ClassReader cr = new ClassReader(in);
ClassNode classNode = new ClassNode();
cr.accept(classNode, 0);
return classNode;
return classNode;
}
catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
/**
@ -432,6 +439,30 @@ public final class AsmHelper {
return types;
}
/**
* Determine the 0-based index of the parameter of LOAD or STORE position varPos
* @param methodNode
* @param varPos the position on the stack
* @return the index of the parameter which corresponds to this varPos
*/
public static int getParamIndex(MethodNode methodNode, int varPos) {
boolean isStatic = (methodNode.access & Opcodes.ACC_STATIC) > 0;
if (!isStatic) {
// only static methods start with 0
// non-static have the this* on pos 0.
varPos--;
}
final Type[] paramTypes = Type.getArgumentTypes(methodNode.desc);
int pos = 0;
for (int i=0; i<paramTypes.length; i++) {
if (pos==varPos) {
return i;
}
pos += paramTypes[i].getSize();
}
throw new IllegalArgumentException("Cannot determine parameter position " + varPos + " for method " + methodNode.name);
}
/**
* @return true if the instruction is an LOAD instruction
*/
@ -448,10 +479,9 @@ public final class AsmHelper {
|| insn.getOpcode() == Opcodes.BALOAD
|| insn.getOpcode() == Opcodes.CALOAD
|| insn.getOpcode() == Opcodes.SALOAD;
}
/**
* @return true if the instruction is an ALOAD_0
*/
@ -627,4 +657,5 @@ public final class AsmHelper {
return null;
}
}
}

View File

@ -19,6 +19,7 @@
package org.apache.openjpa.enhance;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@ -26,10 +27,18 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.openjpa.util.QueryException;
import org.apache.openjpa.util.asm.AsmHelper;
import org.apache.xbean.asm9.tree.ClassNode;
import org.junit.Test;
public class TestAsmAdaptor
{
@Test
public void testAsmHelper() throws Exception {
final ClassNode classNode = AsmHelper.readClassNode(QueryException.class);
assertNotNull(classNode);
}
@Test
public void isEnhanced()
{