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 eba1637454cf1cfa25903f8ab6eb5dda14da3750 Author: Mark Struberg <strub...@apache.org> AuthorDate: Tue Jul 18 10:08:49 2023 +0200 OPENJPA-2911 move more BCClass usage to ASM --- .../org/apache/openjpa/enhance/PCEnhancer.java | 34 ++++++--- .../enhance/asm/EnhancementClassLoader.java | 76 +++++++++++++++++++ .../openjpa/enhance/asm/EnhancementProject.java | 85 ++++++++++++++++++++++ .../org/apache/openjpa/util/asm/AsmHelper.java | 12 ++- .../openjpa/util/asm/RedefinedAttribute.java | 46 ++++++++++++ 5 files changed, 237 insertions(+), 16 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 29f0d0c8a..1b12b0828 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 @@ -100,6 +100,8 @@ import org.apache.openjpa.util.StringId; import org.apache.openjpa.util.UserException; import org.apache.openjpa.util.asm.AsmHelper; import org.apache.openjpa.util.asm.ClassNodeTracker; +import org.apache.openjpa.util.asm.RedefinedAttribute; +import org.apache.xbean.asm9.Attribute; import org.apache.xbean.asm9.Opcodes; import org.apache.xbean.asm9.Type; import org.apache.xbean.asm9.tree.*; @@ -132,6 +134,7 @@ public class PCEnhancer { public static final String ISDETACHEDSTATEDEFINITIVE = PRE + "isDetachedStateDefinitive"; private static final Class<?> PCTYPE = PersistenceCapable.class; + private static final Type TYPE_PCTYPE = Type.getType(PersistenceCapable.class); private static final String SM = PRE + "StateManager"; private static final Class<?> SMTYPE = StateManager.class; private static final String INHERIT = PRE + "InheritedFieldCount"; @@ -146,7 +149,6 @@ public class PCEnhancer { private static final String VERSION_INIT_STR = PRE + "VersionInit"; private static final Localizer _loc = Localizer.forPackage(PCEnhancer.class); - private static final String REDEFINED_ATTRIBUTE = PCEnhancer.class.getName() + "#redefined-type"; private static final AuxiliaryEnhancer[] _auxEnhancers; @@ -536,30 +538,32 @@ public class PCEnhancer { * @return <code>ENHANCE_*</code> constant */ public int run() { - Class<?> type = _managedType.getType(); try { // if enum, skip, no need of any meta - if (type.isEnum()) + if ((managedType.getClassNode().access & Opcodes.ACC_ENUM) > 0) { return ENHANCE_NONE; + } // if managed interface, skip - if (type.isInterface()) + if ((managedType.getClassNode().access & Opcodes.ACC_INTERFACE) > 0) { return ENHANCE_INTERFACE; + } // check if already enhanced // we cannot simply use instanceof or isAssignableFrom as we have a temp ClassLoader inbetween - ClassLoader loader = AccessController.doPrivileged(J2DoPrivHelper.getClassLoaderAction(type)); - for (String iface : _managedType.getDeclaredInterfaceNames()) { - if (iface.equals(PCTYPE.getName())) { + ClassLoader loader = managedType.getClassLoader(); + for (String iface : managedType.getClassNode().interfaces) { + final String pctypeInternalName = TYPE_PCTYPE.getInternalName(); + if (iface.equals(pctypeInternalName)) { if (_log.isTraceEnabled()) { - _log.trace(_loc.get("pc-type", type, loader)); + _log.trace(_loc.get("pc-type", managedType.getClassNode().name, loader)); } return ENHANCE_NONE; } } if (_log.isTraceEnabled()) { - _log.trace(_loc.get("enhance-start", type)); + _log.trace(_loc.get("enhance-start", managedType.getClassNode().name)); } @@ -597,15 +601,21 @@ public class PCEnhancer { } catch (Exception e) { throw new GeneralException(_loc.get("enhance-error", - type.getName(), e.getMessage()), e); + managedType.getClassNode().name, e.getMessage()), e); } } private void configureBCs() { if (!_bcsConfigured) { if (getRedefine()) { - if (_managedType.getAttribute(REDEFINED_ATTRIBUTE) == null) { - _managedType.addAttribute(REDEFINED_ATTRIBUTE); + final boolean isRedefined = managedType.getClassNode().attrs != null && + managedType.getClassNode().attrs.stream().anyMatch(a -> a.isUnknown() && a.type.equals(RedefinedAttribute.ATTR_TYPE)); + + if (!isRedefined) { + if (managedType.getClassNode().attrs == null) { + managedType.getClassNode().attrs = new ArrayList<>(); + } + managedType.getClassNode().attrs.add(new RedefinedAttribute()); } else { _isAlreadyRedefined = true; diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/asm/EnhancementClassLoader.java b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/asm/EnhancementClassLoader.java new file mode 100644 index 000000000..8fac1c4f6 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/asm/EnhancementClassLoader.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.openjpa.enhance.asm; + +import org.apache.openjpa.util.asm.AsmHelper; +import org.apache.openjpa.util.asm.ClassNodeTracker; + + +/** + * A special ClassLoader to handle classes currently under bytecode enhancement. + * Inspired by the Serp BCClassLoader, but for ASM based enhancement. + * + * @author <a href="mailto:strub...@apache.org">Mark Struberg</a> + * @author: Abe White + */ +public class EnhancementClassLoader extends ClassLoader { + + private final EnhancementProject project; + + public EnhancementClassLoader(EnhancementProject project) { + this.project = project; + } + + public EnhancementClassLoader(ClassLoader parent, EnhancementProject project) { + super(parent); + this.project = project; + } + + public EnhancementProject getProject() { + return project; + } + + + protected Class findClass(String name) throws ClassNotFoundException { + byte[] bytes; + try { + ClassNodeTracker type; + if (!project.containsClass(name)) { + type = createClass(name); + } + else { + type = project.loadClass(name); + } + if (type == null) { + throw new ClassNotFoundException(name); + } + + bytes = AsmHelper.toByteArray(type); + } catch (RuntimeException re) { + throw new ClassNotFoundException(re.toString()); + } + return defineClass(name, bytes, 0, bytes.length); + } + + /** + * Override this method if unfound classes should be created on-the-fly. + * Returns null by default. + */ + protected ClassNodeTracker createClass(String name) { + return null; + } +} diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/asm/EnhancementProject.java b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/asm/EnhancementProject.java new file mode 100644 index 000000000..43edfc21f --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/asm/EnhancementProject.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.openjpa.enhance.asm; + +import java.util.HashMap; + +import org.apache.openjpa.util.asm.AsmHelper; +import org.apache.openjpa.util.asm.ClassNodeTracker; +import org.apache.xbean.asm9.tree.ClassNode; + + +/** + * Keep track of classes under enhancement. + * + * @author <a href="mailto:strub...@apache.org">Mark Struberg</a> + */ +public class EnhancementProject { + + private HashMap<String, ClassNodeTracker> classNodTrackers = new HashMap<>(); + + + /** + * Return true if the project already contains the given class. + */ + public boolean containsClass(String type) { + return classNodTrackers.containsKey(type); + } + + + /** + * Load a class with the given name. + * + * @see #loadClass(String,ClassLoader) + */ + public ClassNodeTracker loadClass(String name) { + return loadClass(name, null); + } + + /** + * Load the bytecode for the class with the given name. + * If a {@link ClassNodeTracker} with the given name already exists in this project, + * it will be returned. Otherwise, a new {@link ClassNodeTracker} will be created + * with the given name and returned. If the name represents an existing + * type, the returned instance will contain the parsed bytecode for + * that type. If the name is of a primitive or array type, the returned + * instance will act accordingly. + * + * @param name the name of the class, including package + * @param loader the class loader to use to search for an existing + * class with the given name; if null defaults to the + * context loader of the current thread + * @throws RuntimeException on parse error + */ + public ClassNodeTracker loadClass(String name, ClassLoader loader) { + ClassNodeTracker cached = classNodTrackers.get(name); + if (cached != null) { + return cached; + } + + // check for existing type + if (loader == null) { + loader = Thread.currentThread().getContextClassLoader(); + } + + final ClassNode classNode = AsmHelper.readClassNode(loader, name); + ClassNodeTracker cnt = new ClassNodeTracker(classNode, loader); + classNodTrackers.put(name, cnt); + return cnt; + } + +} 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 86e48a868..8bfcd10a0 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 @@ -27,6 +27,7 @@ import java.util.Arrays; import java.util.Optional; import org.apache.openjpa.enhance.asm.BCClassWriter; +import org.apache.xbean.asm9.Attribute; import org.apache.xbean.asm9.ClassReader; import org.apache.xbean.asm9.ClassWriter; import org.apache.xbean.asm9.Opcodes; @@ -50,6 +51,9 @@ import serp.bytecode.Project; */ public final class AsmHelper { private static final char[] PRIMITIVE_DESCRIPTORS = {'V','Z','C','B','S','I','F','J','D'}; + private static final Attribute[] ATTRS = new Attribute[] { + new RedefinedAttribute() + }; private AsmHelper() { // utility class ct @@ -67,7 +71,7 @@ public final class AsmHelper { try (InputStream in = clazz.getResourceAsStream(className + ".class")) { ClassReader cr = new ClassReader(in); ClassNode classNode = new ClassNode(); - cr.accept(classNode, 0); + cr.accept(classNode, ATTRS, 0); return classNode; } @@ -92,7 +96,7 @@ public final class AsmHelper { throw new RuntimeException(e); } ClassNode classNode = new ClassNode(); - cr.accept(classNode, 0); + cr.accept(classNode, ATTRS, 0); return classNode; } @@ -104,7 +108,7 @@ public final class AsmHelper { public static ClassWriterTracker toClassWriter(BCClass bcClass) { ClassReader cr = new ClassReader(bcClass.toByteArray()); ClassWriter cw = new BCClassWriter(ClassWriter.COMPUTE_FRAMES, bcClass.getClassLoader()); - cr.accept(cw, 0); // 0 -> don't skip anything + cr.accept(cw, ATTRS, 0); // 0 -> don't skip anything ClassWriterTracker cwt = new ClassWriterTracker(cw, bcClass.getClassLoader()); cwt.setName(bcClass.getName()); @@ -164,7 +168,7 @@ public final class AsmHelper { public static ClassNodeTracker toClassNode(BCClass bcClass) { ClassReader cr = new ClassReader(bcClass.toByteArray()); ClassNode classNode = new ClassNode(Opcodes.ASM9); - cr.accept(classNode, 0); + cr.accept(classNode, ATTRS, 0); if ((classNode.version & 0xffff) < 49) { classNode.version = 49; diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/util/asm/RedefinedAttribute.java b/openjpa-kernel/src/main/java/org/apache/openjpa/util/asm/RedefinedAttribute.java new file mode 100644 index 000000000..4fd3265a8 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/asm/RedefinedAttribute.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.openjpa.util.asm; + +import org.apache.xbean.asm9.Attribute; +import org.apache.xbean.asm9.ByteVector; +import org.apache.xbean.asm9.ClassReader; +import org.apache.xbean.asm9.ClassWriter; +import org.apache.xbean.asm9.Label; + +/** + * Custom Attribute to mark that this class already got redefined. + * + * @author <a href="mailto:strub...@apache.org">Mark Struberg</a> + */ +public class RedefinedAttribute extends Attribute { + public static final String ATTR_TYPE = "org/apache/openjpa/Redefined"; + public RedefinedAttribute() { + super(ATTR_TYPE); + } + + @Override + protected Attribute read(ClassReader classReader, int offset, int length, char[] charBuffer, int codeAttributeOffset, Label[] labels) { + return new RedefinedAttribute(); + } + + @Override + protected ByteVector write(ClassWriter classWriter, byte[] code, int codeLength, int maxStack, int maxLocals) { + int idx = classWriter.newUTF8("Redefined"); + return new ByteVector().putShort(idx); + } +}