OPENJPA-2911 move PCSubclassValidator to ASM

work in progress.
For now we compare old and new mechanism to catch errors.
This commit is contained in:
Mark Struberg 2023-05-24 10:38:44 +02:00
parent b51d003ed9
commit 31681982de
6 changed files with 470 additions and 176 deletions

View File

@ -52,6 +52,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.Predicate;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.conf.OpenJPAConfigurationImpl;
@ -96,6 +97,14 @@ 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.ClassWriterTracker;
import org.apache.xbean.asm9.ClassReader;
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.MethodNode;
import org.apache.xbean.asm9.tree.VarInsnNode;
import serp.bytecode.BCClass;
import serp.bytecode.BCField;
@ -541,14 +550,15 @@ public class PCEnhancer {
Class<?> type = _managedType.getType();
try {
// if enum, skip, no need of any meta
if (_pc.isEnum())
if (type.isEnum())
return ENHANCE_NONE;
// if managed interface, skip
if (_pc.isInterface())
if (type.isInterface())
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())) {
@ -558,10 +568,10 @@ public class PCEnhancer {
return ENHANCE_NONE;
}
}
if (_log.isTraceEnabled()) {
_log.trace(_loc.get("enhance-start", type, loader));
}
if (_log.isTraceEnabled()) {
_log.trace(_loc.get("enhance-start", type));
}
configureBCs();
@ -569,8 +579,9 @@ public class PCEnhancer {
// we build up a record of backing fields, etc
if (isPropertyAccess(_meta)) {
validateProperties();
if (getCreateSubclass())
if (getCreateSubclass()) {
addAttributeTranslation();
}
}
replaceAndValidateFieldAccess();
processViolations();
@ -710,7 +721,7 @@ public class PCEnhancer {
true);
continue;
}
returned = getReturnedField(getter);
returned = getReturnedField_old(getter);
if (returned != null)
registerBackingFieldInfo(fmd, getter, returned);
@ -738,7 +749,7 @@ public class PCEnhancer {
}
if (setter != null)
assigned = getAssignedField(setter);
assigned = getAssignedField_old(setter);
if (assigned != null) {
if (setter != null)
@ -848,28 +859,35 @@ public class PCEnhancer {
* Return the field returned by the given method, or null if none.
* Package-protected and static for testing.
*/
static BCField getReturnedField(BCMethod meth) {
return findField(meth, (AccessController.doPrivileged(
J2DoPrivHelper.newCodeAction())).xreturn()
static BCField getReturnedField_old(BCMethod meth) {
return findField_old(meth, new Code().xreturn()
.setType(meth.getReturnType()), false);
}
static Field getReturnedField(Method meth) {
return findField(meth, (ain) -> ain.getOpcode() == AsmHelper.getReturnInsn(meth.getReturnType()), false);
}
/**
* Return the field assigned in the given method, or null if none.
* Package-protected and static for testing.
*/
static BCField getAssignedField(BCMethod meth) {
return findField(meth, (AccessController.doPrivileged(
static BCField getAssignedField_old(BCMethod meth) {
return findField_old(meth, (AccessController.doPrivileged(
J2DoPrivHelper.newCodeAction())).putfield(), true);
}
static Field getAssignedField(Method meth) {
return findField(meth, (ain) -> ain.getOpcode() == Opcodes.PUTFIELD, true);
}
/**
* Return the field returned / assigned by <code>meth</code>. Returns
* null if non-fields (methods, literals, parameters, variables) are
* returned, or if non-parameters are assigned to fields.
*/
private static BCField findField(BCMethod meth, Instruction template,
boolean findAccessed) {
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())
@ -918,8 +936,7 @@ public class PCEnhancer {
&& ((LoadInstruction) prevIns).getParam() == 0) {
final FieldInstruction fTemplateIns =
(FieldInstruction) templateIns;
cur = AccessController.doPrivileged(J2DoPrivHelper
.getFieldInstructionFieldAction(fTemplateIns));
cur = fTemplateIns.getField();
} else
return null;
@ -936,6 +953,103 @@ public class PCEnhancer {
return field;
}
private static Field findField(Method meth, Predicate<AbstractInsnNode> ain, boolean findAccessed) {
// ignore any static methods. OpenJPA only currently supports
// non-static setters and getters
if (Modifier.isStatic(meth.getModifiers())) {
return null;
}
ClassReader cr;
try {
cr = new ClassReader(meth.getDeclaringClass().getName());
}
catch (IOException e) {
throw new RuntimeException(e);
}
ClassNode classNode = new ClassNode();
cr.accept(classNode, 0);
final MethodNode methodNode = classNode.methods.stream()
.filter(mn -> mn.name.equals(meth.getName()) && mn.desc.equals(Type.getMethodDescriptor(meth)))
.findAny().get();
Field field = null;
Field cur;
AbstractInsnNode prevInsn, earlierInsn;
for (AbstractInsnNode insn : methodNode.instructions) {
if (!ain.test(insn)) {
continue;
}
prevInsn = insn.getPrevious();
if (prevInsn == null) {
return null;
}
if (prevInsn.getOpcode() == Opcodes.INSTANCEOF && prevInsn.getPrevious() != null ) {
prevInsn = prevInsn.getPrevious();
}
if (prevInsn.getPrevious() == null) {
return null;
}
earlierInsn = prevInsn.getPrevious();
// 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 (!AsmHelper.isLoadInsn(earlierInsn)
|| !AsmHelper.isThisInsn(earlierInsn)) {
return null;
}
// if the middle instruction was a getfield, then it's the
// field that's being accessed
if (!findAccessed && prevInsn.getOpcode() == Opcodes.GETFIELD) {
final FieldInsnNode fieldInsn = (FieldInsnNode) prevInsn;
cur = getField(meth.getDeclaringClass(), fieldInsn);
// if the middle instruction was an xload_1, then the
// matched instruction is the field that's being set.
} else if (findAccessed && AsmHelper.isLoadInsn(prevInsn)
&& ((VarInsnNode) prevInsn).var == 1) {
final FieldInsnNode fieldInsn = (FieldInsnNode)insn;
cur = getField(meth.getDeclaringClass(), fieldInsn);
} else {
return null;
}
if (field != null && cur != field) {
return null;
}
field = cur;
}
return field;
}
private static Field getField(Class<?> clazz, FieldInsnNode fieldInsn) {
try {
final Class<?> fieldOwner = clazz.getClassLoader().loadClass(fieldInsn.owner.replace("/", "."));
return fieldOwner.getDeclaredField(fieldInsn.name);
}
catch (NoSuchFieldException e) {
if (clazz.getSuperclass() == Object.class) {
throw new IllegalStateException("Cannot find field " + fieldInsn + " in Class " + clazz);
}
return getField(clazz.getSuperclass(), fieldInsn);
}
catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* Record a violation of the property access restrictions.
*/
@ -4895,8 +5009,8 @@ public class PCEnhancer {
if (args == null || args.length == 0) {
classes = repos.getPersistentTypeNames(true, loader);
if (classes == null) {
log.warn(_loc.get("no-class-to-enhance"));
return false;
log.warn(_loc.get("no-class-to-enhance"));
return false;
}
} else {
ClassArgParser cap = conf.getMetaDataRepositoryInstance().
@ -4923,8 +5037,9 @@ public class PCEnhancer {
else
bc = project.loadClass((Class) o);
enhancer = new PCEnhancer(conf, bc, repos, loader);
if (writer != null)
if (writer != null) {
enhancer.setBytecodeWriter(writer);
}
enhancer.setDirectory(flags.directory);
enhancer.setAddDefaultConstructor(flags.addDefaultConstructor);
status = enhancer.run();

View File

@ -21,6 +21,7 @@ package org.apache.openjpa.enhance;
import java.io.Externalizable;
import java.io.ObjectInput;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@ -34,7 +35,6 @@ import org.apache.openjpa.lib.util.StringUtil;
import org.apache.openjpa.meta.AccessCode;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.UserException;
import serp.bytecode.BCClass;
@ -42,48 +42,48 @@ import serp.bytecode.BCField;
import serp.bytecode.BCMethod;
/**
* <p>Validates that a given type meets the JPA contract, plus a few
* OpenJPA-specific additions for subclassing / redefinition:
* <p>Validates that a given type meets the JPA contract, plus a few
* OpenJPA-specific additions for subclassing / redefinition:
*
* <ul>
* <li>must have an accessible no-args constructor</li>
* <li>must be a public or protected class</li>
* <li>must not be final</li>
* <li>must not extend an enhanced class</li>
* <li>all persistent data represented by accessible setter/getter
* methods (persistent properties)</li>
* <li>if versioning is to be used, exactly one persistent property for
* the numeric version data</li> <!-- ##### is this true? -->
* <ul>
* <li>must have an accessible no-args constructor</li>
* <li>must be a public or protected class</li>
* <li>must not be final</li>
* <li>must not extend an enhanced class</li>
* <li>all persistent data represented by accessible setter/getter
* methods (persistent properties)</li>
* <li>if versioning is to be used, exactly one persistent property for
* the numeric version data</li> <!-- ##### is this true? -->
*
* <li>When using property access, the backing field for a persistent
* property must be:
* <ul>
* <!-- ##### JPA validation of these needs to be tested -->
* <li>private</li>
* <li>set only in the designated setter,
* in the constructor, or in {@link Object#clone()},
* <code>readObject(ObjectInputStream)</code>, or
* {@link Externalizable#readExternal(ObjectInput)}.</li>
* <li>read only in the designated getter and the
* constructor.</li>
* </ul>
* </li>
* </ul>
* <li>When using property access, the backing field for a persistent
* property must be:
* <ul>
* <!-- ##### JPA validation of these needs to be tested -->
* <li>private</li>
* <li>set only in the designated setter,
* in the constructor, or in {@link Object#clone()},
* <code>readObject(ObjectInputStream)</code>, or
* {@link Externalizable#readExternal(ObjectInput)}.</li>
* <li>read only in the designated getter and the
* constructor.</li>
* </ul>
* </li>
* </ul>
*
* <p>If you use this technique and use the <code>new</code> keyword instead
* of a OpenJPA-supplied construction routine, OpenJPA will need to do extra
* work with persistent-new-flushed instances, since OpenJPA cannot in this
* case track what happens to such an instance.</p>
*
* @since 1.0.0
* <p>If you use this technique and use the <code>new</code> keyword instead
* of a OpenJPA-supplied construction routine, OpenJPA will need to do extra
* work with persistent-new-flushed instances, since OpenJPA cannot in this
* case track what happens to such an instance.</p>
* <p>
* @since 1.0.0
*/
public class PCSubclassValidator {
private static final Localizer loc =
Localizer.forPackage(PCSubclassValidator.class);
Localizer.forPackage(PCSubclassValidator.class);
private final ClassMetaData meta;
private final BCClass pc;
private final BCClass bc;
private final Log log;
private final boolean failOnContractViolations;
@ -91,52 +91,51 @@ public class PCSubclassValidator {
private Collection contractViolations;
public PCSubclassValidator(ClassMetaData meta, BCClass bc, Log log,
boolean enforceContractViolations) {
boolean enforceContractViolations) {
this.meta = meta;
this.pc = bc;
this.bc = bc;
this.log = log;
this.failOnContractViolations = enforceContractViolations;
}
public void assertCanSubclass() {
Class superclass = meta.getDescribedType();
Class<?> superclass = meta.getDescribedType();
String name = superclass.getName();
if (superclass.isInterface())
if (superclass.isInterface()) {
addError(loc.get("subclasser-no-ifaces", name), meta);
if (Modifier.isFinal(superclass.getModifiers()))
}
if (Modifier.isFinal(superclass.getModifiers())) {
addError(loc.get("subclasser-no-final-classes", name), meta);
if (Modifier.isPrivate(superclass.getModifiers()))
}
if (Modifier.isPrivate(superclass.getModifiers())) {
addError(loc.get("subclasser-no-private-classes", name), meta);
if (PersistenceCapable.class.isAssignableFrom(superclass))
}
if (PersistenceCapable.class.isAssignableFrom(superclass)) {
addError(loc.get("subclasser-super-already-pc", name), meta);
}
try {
Constructor c = superclass.getDeclaredConstructor(new Class[0]);
Constructor c = superclass.getDeclaredConstructor();
if (!(Modifier.isProtected(c.getModifiers())
|| Modifier.isPublic(c.getModifiers())))
|| Modifier.isPublic(c.getModifiers()))) {
addError(loc.get("subclasser-private-ctor", name), meta);
}
}
catch (NoSuchMethodException e) {
addError(loc.get("subclasser-no-void-ctor", name),
meta);
addError(loc.get("subclasser-no-void-ctor", name), meta);
}
// if the BCClass we loaded is already pc and the superclass is not,
// then we should never get here, so let's make sure that the
// calling context is caching correctly by throwing an exception.
if (pc.isInstanceOf(PersistenceCapable.class) &&
!PersistenceCapable.class.isAssignableFrom(superclass))
throw new InternalException(
loc.get("subclasser-class-already-pc", name));
if (AccessCode.isProperty(meta.getAccessType()))
if (AccessCode.isProperty(meta.getAccessType())) {
checkPropertiesAreInterceptable();
}
if (errors != null && !errors.isEmpty())
if (errors != null && !errors.isEmpty()) {
throw new UserException(errors.toString());
}
else if (contractViolations != null &&
!contractViolations.isEmpty() && log.isWarnEnabled())
!contractViolations.isEmpty() && log.isWarnEnabled()) {
log.warn(contractViolations.toString());
}
}
private void checkPropertiesAreInterceptable() {
@ -146,7 +145,7 @@ public class PCSubclassValidator {
Method getter = getBackingMember(fmd);
if (getter == null) {
addError(loc.get("subclasser-no-getter",
fmd.getName()), fmd);
fmd.getName()), fmd);
continue;
}
BCField returnedField = checkGetterIsSubclassable(getter, fmd);
@ -154,18 +153,19 @@ public class PCSubclassValidator {
Method setter = setterForField(fmd);
if (setter == null) {
addError(loc.get("subclasser-no-setter", fmd.getName()),
fmd);
fmd);
continue;
}
BCField assignedField = checkSetterIsSubclassable(setter, fmd);
if (assignedField == null)
if (assignedField == null) {
continue;
}
if (assignedField != returnedField)
addContractViolation(loc.get
("subclasser-setter-getter-field-mismatch",
fmd.getName(), returnedField, assignedField),
fmd);
if (assignedField != returnedField) {
addContractViolation(loc.get("subclasser-setter-getter-field-mismatch",
fmd.getName(), returnedField, assignedField),
fmd);
}
// ### scan through all the rest of the class to make sure it
// ### doesn't use the field.
@ -173,22 +173,21 @@ public class PCSubclassValidator {
}
private Method getBackingMember(FieldMetaData fmd) {
Member back = fmd.getBackingMember();
if (Method.class.isInstance(back))
return (Method)back;
Member back = fmd.getBackingMember();
if (back instanceof Method) {
return (Method) back;
}
Method getter = Reflection.findGetter(meta.getDescribedType(),
fmd.getName(), false);
if (getter != null)
fmd.backingMember(getter);
return getter;
Method getter = Reflection.findGetter(meta.getDescribedType(), fmd.getName(), false);
if (getter != null) {
fmd.backingMember(getter);
}
return getter;
}
private Method setterForField(FieldMetaData fmd) {
try {
return fmd.getDeclaringType().getDeclaredMethod(
"set" + StringUtil.capitalize(fmd.getName()),
new Class[]{ fmd.getDeclaredType() });
return fmd.getDeclaringType().getDeclaredMethod("set" + StringUtil.capitalize(fmd.getName()), fmd.getDeclaredType());
}
catch (NoSuchMethodException e) {
return null;
@ -197,93 +196,111 @@ public class PCSubclassValidator {
/**
* @return the name of the field that is returned by <code>meth</code>, or
* <code>null</code> if something other than a single field is
* returned, or if it cannot be determined what is returned.
* <code>null</code> if something other than a single field is
* returned, or if it cannot be determined what is returned.
*/
private BCField checkGetterIsSubclassable(Method meth, FieldMetaData fmd) {
checkMethodIsSubclassable(meth, fmd);
BCField field = PCEnhancer.getReturnedField(getBCMethod(meth));
if (field == null) {
addContractViolation(loc.get("subclasser-invalid-getter",
fmd.getName()), fmd);
BCField bcField = PCEnhancer.getReturnedField_old(getBCMethod(meth));
Field field = PCEnhancer.getReturnedField(meth);
//X TODO remove
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);
}
if (bcField == null) {
addContractViolation(loc.get("subclasser-invalid-getter", fmd.getName()), fmd);
return null;
} else {
return field;
}
else {
return bcField;
}
}
/**
* @return the field that is set in <code>meth</code>, or
* <code>null</code> if something other than a single field is
* set, or if it cannot be determined what is set.
* <code>null</code> if something other than a single field is
* set, or if it cannot be determined what is set.
*/
private BCField checkSetterIsSubclassable(Method meth, FieldMetaData fmd) {
checkMethodIsSubclassable(meth, fmd);
BCField field = PCEnhancer.getAssignedField(getBCMethod(meth));
if (field == null) {
addContractViolation(loc.get("subclasser-invalid-setter",
fmd.getName()), fmd);
BCField bcField = PCEnhancer.getAssignedField_old(getBCMethod(meth));
Field field = PCEnhancer.getAssignedField(meth);
//X TODO remove
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);
}
if (bcField == null) {
addContractViolation(loc.get("subclasser-invalid-setter", fmd.getName()), fmd);
return null;
} else {
return field;
}
else {
return bcField;
}
}
private BCMethod getBCMethod(Method meth) {
BCClass bc = pc.getProject().loadClass(meth.getDeclaringClass());
BCClass bc = this.bc.getProject().loadClass(meth.getDeclaringClass());
return bc.getDeclaredMethod(meth.getName(), meth.getParameterTypes());
}
private void checkMethodIsSubclassable(Method meth, FieldMetaData fmd) {
String className = fmd.getDefiningMetaData().
getDescribedType().getName();
getDescribedType().getName();
if (!(Modifier.isProtected(meth.getModifiers())
|| Modifier.isPublic(meth.getModifiers())))
addError(loc.get("subclasser-private-accessors-unsupported",
className, meth.getName()), fmd);
if (Modifier.isFinal(meth.getModifiers()))
addError(loc.get("subclasser-final-methods-not-allowed",
className, meth.getName()), fmd);
if (Modifier.isNative(meth.getModifiers()))
addContractViolation(loc.get
("subclasser-native-methods-not-allowed", className,
meth.getName()),
fmd);
if (Modifier.isStatic(meth.getModifiers()))
addError(loc.get("subclasser-static-methods-not-supported",
className, meth.getName()), fmd);
|| Modifier.isPublic(meth.getModifiers()))) {
addError(loc.get("subclasser-private-accessors-unsupported", className, meth.getName()), fmd);
}
if (Modifier.isFinal(meth.getModifiers())) {
addError(loc.get("subclasser-final-methods-not-allowed", className, meth.getName()), fmd);
}
if (Modifier.isNative(meth.getModifiers())) {
addContractViolation(loc.get("subclasser-native-methods-not-allowed", className, meth.getName()), fmd);
}
if (Modifier.isStatic(meth.getModifiers())) {
addError(loc.get("subclasser-static-methods-not-supported", className, meth.getName()), fmd);
}
}
private void addError(Message s, ClassMetaData cls) {
if (errors == null)
if (errors == null) {
errors = new ArrayList();
}
errors.add(loc.get("subclasser-error-meta", s,
cls.getDescribedType().getName(),
cls.getSourceFile()));
cls.getDescribedType().getName(),
cls.getSourceFile()));
}
private void addError(Message s, FieldMetaData fmd) {
if (errors == null)
if (errors == null) {
errors = new ArrayList();
}
errors.add(loc.get("subclasser-error-field", s,
fmd.getFullName(),
fmd.getDeclaringMetaData().getSourceFile()));
fmd.getFullName(),
fmd.getDeclaringMetaData().getSourceFile()));
}
private void addContractViolation(Message m, FieldMetaData fmd) {
// add the violation as an error in case we're processing violations
// as errors; this keeps them in the order that they were found rather
// than just adding the violations to the end of the list.
if (failOnContractViolations)
if (failOnContractViolations) {
addError(m, fmd);
}
if (contractViolations == null)
if (contractViolations == null) {
contractViolations = new ArrayList();
}
contractViolations.add(loc.get
("subclasser-contract-violation-field", m.getMessage(),
fmd.getFullName(), fmd.getDeclaringMetaData().getSourceFile()));
("subclasser-contract-violation-field", m.getMessage(),
fmd.getFullName(), fmd.getDeclaringMetaData().getSourceFile()));
}
}

View File

@ -187,11 +187,11 @@ public abstract class AbstractMetaDataDefaults
Member member;
FieldMetaData fmd;
for (int i = 0; i < fieldNames.length; i ++) {
String property = fieldNames[i];
member = getMemberByProperty(meta, property,
AccessCode.UNKNOWN, true);
if (member == null) // transient or indeterminable access
continue;
String property = fieldNames[i];
member = getMemberByProperty(meta, property, AccessCode.UNKNOWN, true);
if (member == null) { // transient or indeterminable access
continue;
}
fmd = meta.addDeclaredField(property, fieldTypes[i]);
fmd.backingMember(member);
populate(fmd);
@ -285,21 +285,27 @@ public abstract class AbstractMetaDataDefaults
* the next letter lower-cased. For other methods, returns null.
*/
public static String getFieldName(Member member) {
if (member instanceof Field)
if (member instanceof Field) {
return member.getName();
if (!(member instanceof Method))
return null;
}
if (!(member instanceof Method)) {
return null;
}
Method method = (Method) member;
String name = method.getName();
if (isNormalGetter(method))
name = name.substring("get".length());
else if (isBooleanGetter(method))
name = name.substring("is".length());
else
if (isNormalGetter(method)) {
name = name.substring("get".length());
}
else if (isBooleanGetter(method)) {
name = name.substring("is".length());
}
else {
return null;
}
if (name.length() == 1)
if (name.length() == 1) {
return name.toLowerCase();
}
return Character.toLowerCase(name.charAt(0)) + name.substring(1);
}
@ -335,7 +341,7 @@ public abstract class AbstractMetaDataDefaults
if (fmd == null)
return null;
if (fmd.getBackingMember() != null)
return fmd.getBackingMember();
return fmd.getBackingMember();
return getMemberByProperty(fmd.getDeclaringMetaData(), fmd.getName(),
fmd.getAccessType(), true);
}
@ -362,10 +368,10 @@ public abstract class AbstractMetaDataDefaults
* where T is any non-void type.
*/
public static boolean isNormalGetter(Method method) {
String methodName = method.getName();
return startsWith(methodName, "get")
&& method.getParameterTypes().length == 0
&& method.getReturnType() != void.class;
String methodName = method.getName();
return startsWith(methodName, "get")
&& method.getParameterTypes().length == 0
&& method.getReturnType() != void.class;
}
/**
@ -374,10 +380,10 @@ public abstract class AbstractMetaDataDefaults
* <code> public Boolean isXXX() </code>
*/
public static boolean isBooleanGetter(Method method) {
String methodName = method.getName();
return startsWith(methodName, "is")
&& method.getParameterTypes().length == 0
&& isBoolean(method.getReturnType());
String methodName = method.getName();
return startsWith(methodName, "is")
&& method.getParameterTypes().length == 0
&& isBoolean(method.getReturnType());
}
/**
@ -388,16 +394,16 @@ public abstract class AbstractMetaDataDefaults
* <code> public T isXXX()</code> where T is boolean or Boolean.<br>
*/
public static boolean isGetter(Method method, boolean includePrivate) {
if (method == null)
return false;
int mods = method.getModifiers();
if (!(Modifier.isPublic(mods)
|| Modifier.isProtected(mods)
|| (Modifier.isPrivate(mods) && includePrivate))
|| Modifier.isNative(mods)
|| Modifier.isStatic(mods))
return false;
return isNormalGetter(method) || isBooleanGetter(method);
if (method == null)
return false;
int mods = method.getModifiers();
if (!(Modifier.isPublic(mods)
|| Modifier.isProtected(mods)
|| (Modifier.isPrivate(mods) && includePrivate))
|| Modifier.isNative(mods)
|| Modifier.isStatic(mods))
return false;
return isNormalGetter(method) || isBooleanGetter(method);
}
/**
@ -409,14 +415,14 @@ public abstract class AbstractMetaDataDefaults
}
public static boolean isBoolean(Class<?> cls) {
return cls == boolean.class || cls == Boolean.class;
return cls == boolean.class || cls == Boolean.class;
}
public static List<String> toNames(List<? extends Member> members) {
List<String> result = new ArrayList<>();
for (Member m : members)
result.add(m.getName());
return result;
List<String> result = new ArrayList<>();
for (Member m : members)
result.add(m.getName());
return result;
}
}

View File

@ -23,6 +23,8 @@ import org.apache.xbean.asm9.ClassReader;
import org.apache.xbean.asm9.ClassWriter;
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.VarInsnNode;
import serp.bytecode.BCClass;
import serp.bytecode.Project;
@ -211,4 +213,32 @@ public final class AsmHelper {
return types;
}
/**
* @return true if the instruction is an LOAD instruction
*/
public static boolean isLoadInsn(AbstractInsnNode insn) {
return insn.getOpcode() == Opcodes.ALOAD
|| insn.getOpcode() == Opcodes.ILOAD
|| insn.getOpcode() == Opcodes.IALOAD
|| insn.getOpcode() == Opcodes.LLOAD
|| insn.getOpcode() == Opcodes.LALOAD
|| insn.getOpcode() == Opcodes.FLOAD
|| insn.getOpcode() == Opcodes.FALOAD
|| insn.getOpcode() == Opcodes.DLOAD
|| insn.getOpcode() == Opcodes.DALOAD
|| insn.getOpcode() == Opcodes.BALOAD
|| insn.getOpcode() == Opcodes.CALOAD
|| insn.getOpcode() == Opcodes.SALOAD;
}
/**
* @return true if the instruction is an ALOAD_0
*/
public static boolean isThisInsn(AbstractInsnNode insn) {
return insn instanceof VarInsnNode
&& insn.getOpcode() == Opcodes.ALOAD
&& ((VarInsnNode)insn).var == 0;
}
}

View File

@ -40,7 +40,7 @@ public class TestPCEnhancerFindField
Project proj = new Project();
BCClass bc = proj.loadClass(getClass());
BCMethod meth = bc.getMethods("myMethod")[0];
BCField field = PCEnhancer.getReturnedField(meth);
BCField field = PCEnhancer.getReturnedField_old(meth);
assertEquals("field", field.getName());
}
}

View File

@ -0,0 +1,126 @@
/*
* 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;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.TemporaryClassLoader;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.MetaDataModes;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.persistence.common.apps.Department;
import org.apache.openjpa.persistence.common.apps.RuntimeTest2;
import org.apache.openjpa.enhance.UnenhancedPropertyAccess;
import org.apache.openjpa.persistence.test.SingleEMFTestCase;
import org.apache.openjpa.util.UserException;
import org.junit.Test;
import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import serp.bytecode.BCClass;
import serp.bytecode.Project;
public class TestSubclassValidator extends SingleEMFTestCase {
@Override
public void setUp() throws Exception {
setUp("openjpa.RuntimeUnenhancedClasses", "supported",
Department.class,
RuntimeTest2.class,
EnhancableGetterEntity.class,
UnenhancedPropertyAccess.class,
CLEAR_TABLES);
}
@Test
public void testBcSubclassValidator() throws Exception {
Project project = new Project();
TemporaryClassLoader tempCl = new TemporaryClassLoader(this.getClass().getClassLoader());
final OpenJPAConfiguration conf = emf.getConfiguration();
final MetaDataRepository repos = conf.newMetaDataRepositoryInstance();
repos.setSourceMode(MetaDataModes.MODE_META);
final Log log = conf.getLog(OpenJPAConfiguration.LOG_ENHANCE);
/*
{
final BCClass bcClass = project.loadClass(Department.class.getName(), tempCl);
final ClassMetaData meta = repos.getMetaData(tempCl.loadClass(Department.class.getName()), tempCl, false);
PCSubclassValidator subclassValidator = new PCSubclassValidator(meta, bcClass, log, false);
subclassValidator.assertCanSubclass();
}
try {
final BCClass bcClass = project.loadClass(RuntimeTest2.class.getName(), tempCl);
final ClassMetaData meta = repos.getMetaData(tempCl.loadClass(RuntimeTest2.class.getName()), tempCl, false);
PCSubclassValidator subclassValidator = new PCSubclassValidator(meta, bcClass, log, true);
subclassValidator.assertCanSubclass();
fail("RuntimeTest2 validation should fail");
}
catch (UserException ue) {
// all fine
}
*/
{
final BCClass bcClass = project.loadClass(EnhancableGetterEntity.class.getName(), tempCl);
final ClassMetaData meta = repos.getMetaData(tempCl.loadClass(EnhancableGetterEntity.class.getName()), tempCl, false);
PCSubclassValidator subclassValidator = new PCSubclassValidator(meta, bcClass, log, true);
subclassValidator.assertCanSubclass();
}
{
final BCClass bcClass = project.loadClass(UnenhancedPropertyAccess.class.getName(), tempCl);
final ClassMetaData meta = repos.getMetaData(tempCl.loadClass(UnenhancedPropertyAccess.class.getName()), tempCl, false);
PCSubclassValidator subclassValidator = new PCSubclassValidator(meta, bcClass, log, true);
subclassValidator.assertCanSubclass();
}
}
@Entity
@Access(AccessType.PROPERTY)
public static class EnhancableGetterEntity {
@Id
private String id;
@Basic
private String name;
@Basic
private String another;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDifferent() {
return another;
}
public void setDifferent(String another) {
this.another = another;
}
}
}