You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by st...@apache.org on 2023/07/03 09:52:41 UTC
[openjpa] 23/33: OPENJPA-2911 replace Field access via ASM
This is an automated email from the ASF dual-hosted git repository.
struberg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openjpa.git
commit 92bb3b055ae4f882b10a197b012c0a1a3449a4cf
Author: Mark Struberg <st...@apache.org>
AuthorDate: Sat Jun 17 20:29:00 2023 +0200
OPENJPA-2911 replace Field access via ASM
---
.../org/apache/openjpa/enhance/PCEnhancer.java | 234 +++++++++++++--------
.../org/apache/openjpa/util/asm/AsmHelper.java | 168 +++++++++++++++
.../enhance/stats/FetchStatisticsAuxEnhancer.java | 6 +
3 files changed, 315 insertions(+), 93 deletions(-)
diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java
index 3b26af905..057627a5b 100644
--- a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java
+++ b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/PCEnhancer.java
@@ -109,16 +109,12 @@ import serp.bytecode.BCMethod;
import serp.bytecode.Code;
import serp.bytecode.Constants;
import serp.bytecode.Exceptions;
-import serp.bytecode.FieldInstruction;
import serp.bytecode.IfInstruction;
import serp.bytecode.Instruction;
import serp.bytecode.JumpInstruction;
import serp.bytecode.LoadInstruction;
-import serp.bytecode.LookupSwitchInstruction;
-import serp.bytecode.MethodInstruction;
import serp.bytecode.Project;
import serp.bytecode.PutFieldInstruction;
-import serp.bytecode.TableSwitchInstruction;
/**
* Bytecode enhancer used to enhance persistent classes from metadata. The
@@ -594,13 +590,11 @@ public class PCEnhancer {
if (getCreateSubclass()) {
addAttributeTranslation();
}
- AsmHelper.readIntoBCClass(pc, _pc);
}
replaceAndValidateFieldAccess();
processViolations();
if (_meta != null) {
- pc = AsmHelper.toClassNode(_pc);
enhanceClass(pc);
addFields(pc);
//X TODO finish addStaticInitializer(classNodeTracker);
@@ -1098,25 +1092,12 @@ public class PCEnhancer {
* pcGet/pcSet method. Note that this includes access to fields
* owned by PersistenceCapable classes other than this one.
*/
- private void replaceAndValidateFieldAccess() throws NoSuchMethodException {
- // create template putfield/getfield instructions to search for
- Code template = AccessController.doPrivileged(
- J2DoPrivHelper.newCodeAction());
- Instruction put = template.putfield();
- Instruction get = template.getfield();
- Instruction stat = template.invokestatic();
-
- // look through all methods; this is done before any methods are added
- // so we don't need to worry about excluding synthetic methods.
- BCMethod[] methods = _managedType.getDeclaredMethods();
- Code code;
- for (BCMethod method : methods) {
- code = method.getCode(false);
-
- // don't modify the methods specified by the auxiliary enhancers
- if (code != null && !skipEnhance(method)) {
- replaceAndValidateFieldAccess(code, get, true, stat);
- replaceAndValidateFieldAccess(code, put, false, stat);
+ private void replaceAndValidateFieldAccess() throws NoSuchMethodException, ClassNotFoundException {
+ final ClassNode classNode = pc.getClassNode();
+ for (MethodNode methodNode : classNode.methods) {
+ if (methodNode.instructions.size() > 0 && !skipEnhance(methodNode)) {
+ replaceAndValidateFieldAccess(classNode, methodNode, (a)-> a.getOpcode() == Opcodes.GETFIELD, true);
+ replaceAndValidateFieldAccess(classNode, methodNode, (a)-> a.getOpcode() == Opcodes.PUTFIELD, false);
}
}
}
@@ -1125,28 +1106,27 @@ public class PCEnhancer {
* Replaces all instructions matching the given template in the given
* code block with calls to the appropriate generated getter/setter.
*
- * @param code the code block to modify; the code iterator will
+ * @param methodNode the code block to modify; the code iterator will
* be placed before the first instruction on method start,
* and will be after the last instruction on method completion
- * @param ins the template instruction to search for; either a
+ * @param insnCheck the template instruction to search for; either a
* getfield or putfield instruction
* @param get boolean indicating if this is a get instruction
- * @param stat template invokestatic instruction to replace with
*/
- private void replaceAndValidateFieldAccess(Code code, Instruction ins,
- boolean get, Instruction stat) throws NoSuchMethodException {
- code.beforeFirst();
+ private void replaceAndValidateFieldAccess(ClassNode classNode, MethodNode methodNode, Predicate<AbstractInsnNode> insnCheck,
+ boolean get) throws NoSuchMethodException, ClassNotFoundException {
+ AbstractInsnNode currentInsn = methodNode.instructions.getFirst();
- FieldInstruction fi;
- MethodInstruction mi;
- ClassMetaData owner;
- String name, typeName, methodName;
- while (code.searchForward(ins)) {
- // back up to the matched instruction
- fi = (FieldInstruction) code.previous();
- name = fi.getFieldName();
- typeName = fi.getFieldTypeName();
- owner = getPersistenceCapableOwner(name, fi.getFieldDeclarerType());
+ // skip to the next instruction we are looking for
+ while ((currentInsn = searchNextInstruction(currentInsn, insnCheck)) != null) {
+ FieldInsnNode fi = (FieldInsnNode) currentInsn;
+ String name = fi.name;
+
+ ClassMetaData owner = null;
+ if (fi.owner != null) {
+ final Class<?> declarerType = AsmHelper.getDescribedClass(managedType.getClassLoader(), fi.owner);
+ owner = getPersistenceCapableOwner(name, declarerType);
+ }
FieldMetaData fmd = owner == null ? null : owner.getField(name);
if (isPropertyAccess(fmd)) {
// if we're directly accessing a field in another class
@@ -1155,74 +1135,99 @@ public class PCEnhancer {
_meta != null && !owner.getDescribedType()
.isAssignableFrom(_meta.getDescribedType()))
throw new UserException(_loc.get("property-field-access",
- new Object[]{_meta, owner, name,
- code.getMethod().getName()}));
+ new Object[]{_meta, owner, name, methodNode.name}));
// if we're directly accessing a property-backing field outside
// the property in our own class, notify user
- if (isBackingFieldOfAnotherProperty(name, code))
- addViolation("property-field-access", new Object[]{_meta,
- owner, name, code.getMethod().getName()}, false);
+ if (isBackingFieldOfAnotherProperty(methodNode, name)) {
+ addViolation("property-field-access", new Object[]{_meta, owner, name, methodNode.name}, false);
+ }
}
- if (owner == null ||
- owner.getDeclaredField(fromBackingFieldName(name)) == null) {
- // not persistent field?
- code.next();
- continue;
+ if (owner == null || owner.getDeclaredField(fromBackingFieldName(name)) == null) {
+ // not a persistent field?
}
- else if (!getRedefine() && !getCreateSubclass()
- && isFieldAccess(fmd)) {
- // replace the instruction with a call to the generated access
- // method
- mi = (MethodInstruction) code.set(stat);
-
- // invoke the proper access method, whether getter or setter
- String prefix = (get) ? PRE + "Get" : PRE + "Set";
- methodName = prefix + name;
+ else if (!getRedefine() && !getCreateSubclass() && isFieldAccess(fmd)) {
+ // replace the instruction with a call to the generated access method
+ Type ownerType = Type.getType(getType(owner));
+ MethodInsnNode pcCall;
if (get) {
- mi.setMethod(getType(owner).getName(),
- methodName, typeName, new String[]
- {getType(owner).getName()});
+ pcCall = new MethodInsnNode(Opcodes.INVOKESTATIC,
+ ownerType.getInternalName(),
+ PRE + "Get" + name,
+ Type.getMethodDescriptor(Type.getType(fi.desc), ownerType));
}
else {
- mi.setMethod(getType(owner).getName(),
- methodName, "void", new String[]
- {getType(owner).getName(), typeName});
+ pcCall = new MethodInsnNode(Opcodes.INVOKESTATIC,
+ ownerType.getInternalName(),
+ PRE + "Set" + name,
+ Type.getMethodDescriptor(Type.VOID_TYPE, ownerType, Type.getType(fi.desc)));
}
- code.next();
+ methodNode.instructions.insertBefore(currentInsn, pcCall);
+ // and now delete the direct field access
+ methodNode.instructions.remove(currentInsn);
+
+ // next iteration will be started here.
+ currentInsn = pcCall;
}
else if (getRedefine()) {
name = fromBackingFieldName(name);
if (get) {
- addNotifyAccess(code, owner.getField(name));
- code.next();
+ addNotifyAccess(methodNode, currentInsn, owner.getField(name));
}
else {
// insert the set operations after the field mutation, but
// first load the old value for use in the
// StateManager.settingXXX method.
- loadManagedInstance(code, false);
- final FieldInstruction fFi = fi;
- code.getfield().setField(
- AccessController.doPrivileged(J2DoPrivHelper
- .getFieldInstructionFieldAction(fFi)));
- int val = code.getNextLocalsIndex();
- code.xstore().setLocal(val).setType(fi.getFieldType());
-
- // move past the putfield
- code.next();
- addNotifyMutation(code, owner.getField(name), val, -1);
+
+ InsnList insns = new InsnList();
+ insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
+
+ int valVarPos = methodNode.maxLocals++;
+ insns.add(new VarInsnNode(AsmHelper.getCorrespondingLoadInsn(fi.getOpcode()), valVarPos));
+
+ currentInsn = addNotifyMutation(classNode, methodNode, currentInsn, owner.getField(name), valVarPos, -1);
}
+
}
- else {
- code.next();
- }
- code.calculateMaxLocals();
- code.calculateMaxStack();
+
+ currentInsn = currentInsn.getNext();
}
}
+ /**
+ * Scan the instructions until you found any which fits the predicate.
+ * @param currentInsn the instruction to start searching from
+ * @param insnCheck the condition which has to be met
+ * @return the instruction node we did search for or {@code null} if there is no such instruction.
+ */
+ private AbstractInsnNode searchNextInstruction(AbstractInsnNode currentInsn, Predicate<AbstractInsnNode> insnCheck) {
+ while (currentInsn != null && !insnCheck.test(currentInsn)) {
+ currentInsn = currentInsn.getNext();
+ }
+
+ return currentInsn;
+ }
+
+ /**
+ * Add the following code to the code:
+ * <code>
+ * PCHelper.accessingField(this, <absolute-index>);
+ * </code>
+ */
+ private void addNotifyAccess(MethodNode methodNode, AbstractInsnNode currentInsn, FieldMetaData fmd) {
+ InsnList insns = new InsnList();
+
+ insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
+ insns.add(new LdcInsnNode(fmd.getIndex()));
+ insns.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
+ Type.getInternalName(RedefinitionHelper.class),
+ "accessingField",
+ Type.getMethodDescriptor(Type.VOID_TYPE, TYPE_OBJECT, Type.INT_TYPE)));
+
+ methodNode.instructions.insertBefore(currentInsn, insns);
+ }
+
private void addNotifyAccess(Code code, FieldMetaData fmd) {
// PCHelper.accessingField(this, <absolute-index>);
code.aload().setThis();
@@ -1232,6 +1237,45 @@ public class PCEnhancer {
new Class[]{Object.class, int.class});
}
+ /**
+ * This must be called after setting the value in the object.
+ *
+ * @param valVarPos the position in the local variable table where the
+ * old value is stored
+ * @param param the parameter position containing the new value, or
+ * -1 if the new value is unavailable and should therefore be looked
+ * up.
+ * @return the last inserted InsnNode
+ */
+ private AbstractInsnNode addNotifyMutation(ClassNode classNode, MethodNode methodNode, AbstractInsnNode currentInsn,
+ FieldMetaData fmd, int valVarPos, int param) {
+ // PCHelper.settingField(this, <absolute-index>, old, new);
+ InsnList insns = new InsnList();
+
+ insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
+ insns.add(new LdcInsnNode(fmd.getIndex()));
+
+ Class type = fmd.getDeclaredType();
+ // we only have special signatures for primitives and Strings
+ if (!type.isPrimitive() && type != String.class) {
+ type = Object.class;
+ }
+ insns.add(new VarInsnNode(AsmHelper.getLoadInsn(type), valVarPos));
+ if (param == -1) {
+ insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); // this
+ addGetManagedValueCode(classNode, insns, fmd, true);
+ }
+ else {
+ insns.add(new VarInsnNode(AsmHelper.getLoadInsn(type), param + 1));
+ }
+ insns.add(new MethodInsnNode(Opcodes.INVOKESTATIC,
+ Type.getInternalName(RedefinitionHelper.class),
+ "settingField",
+ Type.getMethodDescriptor(Type.VOID_TYPE, TYPE_OBJECT, Type.INT_TYPE, Type.getType(type))));
+
+ methodNode.instructions.insert(currentInsn, insns);
+ return insns.getLast();
+ }
/**
* This must be called after setting the value in the object.
*
@@ -1240,10 +1284,8 @@ public class PCEnhancer {
* @param param the parameter position containing the new value, or
* -1 if the new value is unavailable and should therefore be looked
* up.
- * @throws NoSuchMethodException
*/
- private void addNotifyMutation(Code code, FieldMetaData fmd, int val,
- int param)
+ private void addNotifyMutation(Code code, FieldMetaData fmd, int val, int param)
throws NoSuchMethodException {
// PCHelper.settingField(this, <absolute-index>, old, new);
code.aload().setThis();
@@ -1270,8 +1312,8 @@ public class PCEnhancer {
* Return true if the given instruction accesses a field that is a backing
* field of another property in this property-access class.
*/
- private boolean isBackingFieldOfAnotherProperty(String name, Code code) {
- String methName = code.getMethod().getName();
+ private boolean isBackingFieldOfAnotherProperty(MethodNode methodNode, String name) {
+ String methName = methodNode.name;
return !"<init>".equals(methName)
&& _backingFields != null
&& !name.equals(_backingFields.get(methName))
@@ -4093,13 +4135,15 @@ public class PCEnhancer {
* @return true if any of the auxiliary enhancers skips the given method,
* or if the method is a constructor
*/
- private boolean skipEnhance(BCMethod method) {
- if ("<init>".equals(method.getName()))
+ private boolean skipEnhance(MethodNode method) {
+ if ("<init>".equals(method.name) || "<clinit>".equals(method.name)) {
return true;
+ }
for (AuxiliaryEnhancer auxEnhancer : _auxEnhancers)
- if (auxEnhancer.skipEnhance(method))
+ if (auxEnhancer.skipEnhance(method)) {
return true;
+ }
return false;
}
@@ -5606,7 +5650,11 @@ public class PCEnhancer {
public interface AuxiliaryEnhancer
{
void run (BCClass bc, ClassMetaData meta);
+
+ @Deprecated
boolean skipEnhance(BCMethod m);
+
+ boolean skipEnhance(MethodNode m);
}
private void addGetIDOwningClass() {
diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/util/asm/AsmHelper.java b/openjpa-kernel/src/main/java/org/apache/openjpa/util/asm/AsmHelper.java
index 7e3298806..2a23a01c0 100644
--- a/openjpa-kernel/src/main/java/org/apache/openjpa/util/asm/AsmHelper.java
+++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/asm/AsmHelper.java
@@ -19,6 +19,7 @@ package org.apache.openjpa.util.asm;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -28,6 +29,7 @@ import org.apache.xbean.asm9.Opcodes;
import org.apache.xbean.asm9.Type;
import org.apache.xbean.asm9.tree.AbstractInsnNode;
import org.apache.xbean.asm9.tree.ClassNode;
+import org.apache.xbean.asm9.tree.FieldInsnNode;
import org.apache.xbean.asm9.tree.VarInsnNode;
import serp.bytecode.BCClass;
@@ -39,6 +41,8 @@ import serp.bytecode.Project;
* @author <a href="mailto:struberg@apache.org">Mark Struberg</a>
*/
public final class AsmHelper {
+ private static final char[] PRIMITIVE_DESCRIPTORS = {'V','Z','C','B','S','I','F','J','D'};
+
private AsmHelper() {
// utility class ct
}
@@ -313,4 +317,168 @@ public final class AsmHelper {
&& insn.getOpcode() == Opcodes.ALOAD
&& ((VarInsnNode)insn).var == 0;
}
+
+ /**
+ * Get the corresponding LOAD instruction for the given STORE instruction.
+ *
+ * @param storeInsnOpcode e.g. {@link Opcodes#ISTORE}
+ * @throws IllegalArgumentException if the given opcode is not a STORE instruction
+ */
+ public static int getCorrespondingLoadInsn(int storeInsnOpcode) {
+ switch (storeInsnOpcode) {
+ case Opcodes.ISTORE:
+ return Opcodes.ILOAD;
+ case Opcodes.LSTORE:
+ return Opcodes.LLOAD;
+ case Opcodes.FSTORE:
+ return Opcodes.FLOAD;
+ case Opcodes.DSTORE:
+ return Opcodes.DLOAD;
+ case Opcodes.ASTORE:
+ return Opcodes.ALOAD;
+ case Opcodes.IASTORE:
+ return Opcodes.IALOAD;
+ case Opcodes.LASTORE:
+ return Opcodes.LALOAD;
+ case Opcodes.FASTORE:
+ return Opcodes.FALOAD;
+ case Opcodes.DASTORE:
+ return Opcodes.DALOAD;
+ case Opcodes.AASTORE:
+ return Opcodes.AALOAD;
+ case Opcodes.BASTORE:
+ return Opcodes.BALOAD;
+ case Opcodes.CASTORE:
+ return Opcodes.CALOAD;
+ case Opcodes.SASTORE:
+ return Opcodes.SALOAD;
+ default:
+ throw new IllegalArgumentException("Not a STORE instruction: " + storeInsnOpcode);
+ }
+ }
+
+ /**
+ * Get the corresponding STORE instruction for the given LOAD instruction.
+ *
+ * @param loadInsnOpcode e.g. {@link Opcodes#FLOAD}
+ * @throws IllegalArgumentException if the given opcode is not a STORE instruction
+ */
+ public static int getCorrespondingStoreInsn(int loadInsnOpcode) {
+ switch (loadInsnOpcode) {
+ case Opcodes.ILOAD:
+ return Opcodes.ISTORE;
+ case Opcodes.LLOAD:
+ return Opcodes.LSTORE;
+ case Opcodes.FLOAD:
+ return Opcodes.FSTORE;
+ case Opcodes.DLOAD:
+ return Opcodes.DSTORE;
+ case Opcodes.ALOAD:
+ return Opcodes.ASTORE;
+ case Opcodes.IALOAD:
+ return Opcodes.IASTORE;
+ case Opcodes.LALOAD:
+ return Opcodes.LASTORE;
+ case Opcodes.FALOAD:
+ return Opcodes.FASTORE;
+ case Opcodes.DALOAD:
+ return Opcodes.DASTORE;
+ case Opcodes.AALOAD:
+ return Opcodes.AASTORE;
+ case Opcodes.BALOAD:
+ return Opcodes.BASTORE;
+ case Opcodes.CALOAD:
+ return Opcodes.CASTORE;
+ case Opcodes.SALOAD:
+ return Opcodes.SASTORE;
+ default:
+ throw new IllegalArgumentException("Not a LOAD instruction: " + loadInsnOpcode);
+ }
+ }
+
+ /**
+ * @param typeDesc the internal type descriptor from the bytecode. See {@link ClassNode#name}
+ * @param includeVoid if the Void.class type also counts as primitive
+ */
+ public static boolean isPrimitive(String typeDesc, boolean includeVoid) {
+ if (typeDesc != null && typeDesc.length() == 1) {
+ char typeChar = typeDesc.charAt(0);
+ for (int i = includeVoid ? 0: 1; i<PRIMITIVE_DESCRIPTORS.length; i++) {
+ if (PRIMITIVE_DESCRIPTORS[i] == typeChar) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get the class from the described type
+ * @param typeDesc
+ * @return described class or {@code null} if it could not be loaded
+ */
+ public static Class<?> getDescribedClass(ClassLoader classLoader, String typeDesc) {
+ if (typeDesc == null || typeDesc.isEmpty()) {
+ return null;
+ }
+ if (typeDesc.charAt(0) == '[') {
+ switch (typeDesc.charAt(1)) {
+ case 'V':
+ throw new IllegalArgumentException("There is no such thing as a void array");
+ case 'Z':
+ return boolean[].class;
+ case 'C':
+ return char[].class;
+ case 'B':
+ return byte[].class;
+ case 'S':
+ return short[].class;
+ case 'I':
+ return int[].class;
+ case 'F':
+ return float[].class;
+ case 'J':
+ return long[].class;
+ case 'D':
+ return double[].class;
+ default:
+ // some object array
+ Class<?> clazz = getClass(classLoader, typeDesc.substring(1));
+ return Array.newInstance(clazz, 0).getClass();
+ }
+ }
+
+ switch (typeDesc.charAt(0)) {
+ case 'V':
+ return void.class;
+ case 'Z':
+ return boolean.class;
+ case 'C':
+ return char.class;
+ case 'B':
+ return byte.class;
+ case 'S':
+ return short.class;
+ case 'I':
+ return int.class;
+ case 'F':
+ return float.class;
+ case 'J':
+ return long.class;
+ case 'D':
+ return double.class;
+ default:
+ // some kind of class
+ return getClass(classLoader, typeDesc);
+ }
+ }
+
+ private static Class<?> getClass(ClassLoader classLoader, String typeName) {
+ try {
+ return Class.forName(typeName.replace("/", "."), false, classLoader);
+ }
+ catch (NoClassDefFoundError | ClassNotFoundException e) {
+ return null;
+ }
+ }
}
diff --git a/openjpa-tools/openjpa-fetch-statistics/src/main/java/org/apache/openjpa/enhance/stats/FetchStatisticsAuxEnhancer.java b/openjpa-tools/openjpa-fetch-statistics/src/main/java/org/apache/openjpa/enhance/stats/FetchStatisticsAuxEnhancer.java
index 7ec6af585..07989ea61 100644
--- a/openjpa-tools/openjpa-fetch-statistics/src/main/java/org/apache/openjpa/enhance/stats/FetchStatisticsAuxEnhancer.java
+++ b/openjpa-tools/openjpa-fetch-statistics/src/main/java/org/apache/openjpa/enhance/stats/FetchStatisticsAuxEnhancer.java
@@ -28,6 +28,7 @@ import org.apache.openjpa.meta.AccessCode;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
+import org.apache.xbean.asm9.tree.MethodNode;
import serp.bytecode.BCClass;
import serp.bytecode.BCMethod;
import serp.bytecode.Code;
@@ -51,6 +52,11 @@ public class FetchStatisticsAuxEnhancer implements AuxiliaryEnhancer {
return false;
}
+ @Override
+ public boolean skipEnhance(MethodNode m) {
+ return false;
+ }
+
private void addEnhancement(BCClass bcc, ClassMetaData cmd) {
Log log = cmd.getRepository().getConfiguration().getLog(OpenJPAConfiguration.LOG_RUNTIME);
FetchStatsCollector.setlogger(log);