OPENJPA-2911 move more BCClass usage to ASM

This commit is contained in:
Mark Struberg 2023-07-18 10:08:49 +02:00
parent eabceb69f4
commit eba1637454
5 changed files with 237 additions and 16 deletions

View File

@ -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;

View File

@ -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:struberg@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;
}
}

View File

@ -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:struberg@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;
}
}

View File

@ -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;

View File

@ -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:struberg@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);
}
}