use of javassist.bytecode.DuplicateMemberException in project fakereplace by fakereplace.
the class FieldReplacementTransformer method transform.
@Override
public boolean transform(ClassLoader loader, String className, Class<?> oldClass, ProtectionDomain protectionDomain, ClassFile file, Set<Class<?>> classesToRetransform, ChangedClassImpl changedClass, Set<MethodInfo> modifiedMethods) throws IllegalClassFormatException, BadBytecode, DuplicateMemberException {
if (oldClass == null || className == null) {
return false;
}
BaseClassData data = ClassDataStore.instance().getBaseClassData(loader, Descriptor.toJvmName(file.getName()));
Set<FieldData> fields = new LinkedHashSet<>();
fields.addAll(data.getFields());
ListIterator<?> it = file.getFields().listIterator();
final Set<FieldData> toRemove = new HashSet<>();
final Set<FieldProxyInfo> toAdd = new HashSet<>();
while (it.hasNext()) {
FieldInfo m = (FieldInfo) it.next();
FieldData md = null;
for (FieldData i : fields) {
if (i.getName().equals(m.getName()) && i.getType().equals(m.getDescriptor()) && i.getAccessFlags() == m.getAccessFlags()) {
try {
Field field = i.getField(oldClass);
AnnotationDataStore.recordFieldAnnotations(field, (AnnotationsAttribute) m.getAttribute(AnnotationsAttribute.visibleTag));
// now revert the annotations:
m.addAttribute(AnnotationReplacer.duplicateAnnotationsAttribute(file.getConstPool(), field));
} catch (Exception e) {
throw new RuntimeException(e);
}
md = i;
break;
}
}
// This is a newly added field.
if (md == null) {
int fieldNo = addField(loader, m, toAdd, oldClass);
Transformer.getManipulator().rewriteInstanceFieldAccess(fieldNo, m.getName(), m.getDescriptor(), file.getName(), loader);
it.remove();
} else {
fields.remove(md);
}
}
// TODO: rewrite classes that access them to throw a NoSuchFieldError
for (FieldData md : fields) {
if (md.getMemberType() == MemberType.NORMAL) {
FieldInfo old = new FieldInfo(file.getConstPool(), md.getName(), md.getType());
old.setAccessFlags(md.getAccessFlags());
toRemove.add(md);
}
}
// clear all the fields and re-add them in the correct order
// turns out order is important
file.getFields().clear();
for (FieldData md : data.getFields()) {
if (md.getMemberType() == MemberType.NORMAL) {
try {
Field field = md.getField(oldClass);
FieldInfo old = new FieldInfo(file.getConstPool(), md.getName(), md.getType());
old.setAccessFlags(md.getAccessFlags());
file.addField(old);
old.addAttribute(AnnotationReplacer.duplicateAnnotationsAttribute(file.getConstPool(), field));
} catch (DuplicateMemberException | SecurityException | NoSuchFieldException e) {
// this should not happen
throw new RuntimeException(e);
}
}
}
ClassDataStore.instance().modifyCurrentData(loader, file.getName(), (builder) -> {
for (FieldProxyInfo field : toAdd) {
builder.addFakeField(field.fieldData, field.proxyName, field.modifiers);
}
for (FieldData field : toRemove) {
builder.removeField(field);
}
});
return true;
}
use of javassist.bytecode.DuplicateMemberException in project fakereplace by fakereplace.
the class FieldReplacementTransformer method addField.
/**
* This will create a proxy with a non static field. This field does not
* store anything, it merely provides a Field object for reflection. Attempts
* to change and read it's value are redirected to the actual array based
* store
*/
private static int addField(ClassLoader loader, FieldInfo m, Set<FieldProxyInfo> builder, Class<?> oldClass) {
int fieldNo = FieldReferenceDataStore.instance().getFieldNo(m.getName(), m.getDescriptor());
String proxyName = ProxyDefinitionStore.getProxyName();
ClassFile proxy = new ClassFile(false, proxyName, "java.lang.Object");
ClassDataStore.instance().registerProxyName(oldClass, proxyName);
FieldAccessor accessor = new FieldAccessor(oldClass, fieldNo, (m.getAccessFlags() & AccessFlag.STATIC) != 0);
ClassDataStore.instance().registerFieldAccessor(proxyName, accessor);
proxy.setAccessFlags(AccessFlag.PUBLIC);
FieldInfo newField = new FieldInfo(proxy.getConstPool(), m.getName(), m.getDescriptor());
newField.setAccessFlags(m.getAccessFlags());
copyFieldAttributes(m, newField);
try {
proxy.addField(newField);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bytes);
try {
proxy.write(dos);
} catch (IOException e) {
throw new RuntimeException(e);
}
ProxyDefinitionStore.saveProxyDefinition(loader, proxyName, bytes.toByteArray());
builder.add(new FieldProxyInfo(newField, proxyName, m.getAccessFlags()));
} catch (DuplicateMemberException e) {
// can't happen
}
return fieldNo;
}
use of javassist.bytecode.DuplicateMemberException in project fakereplace by fakereplace.
the class MethodReplacementTransformer method generateFakeConstructorBytecode.
/**
* creates a class with a fake constructor that can be used by the reflection
* api
* <p>
* Constructors are not invoked through the proxy class, instead we have to
* do a lot more bytecode re-writing at the actual invocation sites
*/
private static String generateFakeConstructorBytecode(MethodInfo mInfo, ClassLoader loader) throws BadBytecode {
String proxyName = ProxyDefinitionStore.getProxyName();
ClassFile proxy = new ClassFile(false, proxyName, "java.lang.Object");
proxy.setVersionToJava5();
proxy.setAccessFlags(AccessFlag.PUBLIC);
// add our new annotations directly onto the new proxy method. This way
// they will just work without registering them with the
// AnnotationDataStore
String[] types = DescriptorUtils.descriptorStringToParameterArray(mInfo.getDescriptor());
// as this method is never called the bytecode just returns
Bytecode b = new Bytecode(proxy.getConstPool());
b.add(Opcode.ALOAD_0);
b.addInvokespecial("java.lang.Object", "<init>", "()V");
b.add(Opcode.RETURN);
MethodInfo method = new MethodInfo(proxy.getConstPool(), mInfo.getName(), mInfo.getDescriptor());
method.setAccessFlags(mInfo.getAccessFlags());
method.setCodeAttribute(b.toCodeAttribute());
method.getCodeAttribute().computeMaxStack();
method.getCodeAttribute().setMaxLocals(types.length + 1);
copyMethodAttributes(mInfo, method);
try {
proxy.addMethod(method);
} catch (DuplicateMemberException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bytes);
proxy.write(dos);
ProxyDefinitionStore.saveProxyDefinition(loader, proxyName, bytes.toByteArray());
return proxyName;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
use of javassist.bytecode.DuplicateMemberException in project fakereplace by fakereplace.
the class MethodReplacementTransformer method transform.
@Override
public boolean transform(ClassLoader loader, String className, Class<?> oldClass, ProtectionDomain protectionDomain, ClassFile file, Set<Class<?>> classesToRetransform, ChangedClassImpl changedClass, Set<MethodInfo> modifiedMethods) throws IllegalClassFormatException, BadBytecode, DuplicateMemberException {
if (oldClass == null || className == null) {
return false;
}
final Set<MethodData> methodsToRemove = new HashSet<>();
final Set<FakeMethod> methodsToAdd = new HashSet<>();
final Set<FakeMethod> constructorsToAdd = new HashSet<>();
BaseClassData data = ClassDataStore.instance().getBaseClassData(loader, className);
// state for added static methods
CodeAttribute staticCodeAttribute = null, virtualCodeAttribute = null, constructorCodeAttribute = null;
try {
// stick our added methods into the class file
// we can't finalise the code yet because we will probably need
// the add stuff to them
MethodInfo virtMethod = new MethodInfo(file.getConstPool(), Constants.ADDED_METHOD_NAME, Constants.ADDED_METHOD_DESCRIPTOR);
modifiedMethods.add(virtMethod);
virtMethod.setAccessFlags(AccessFlag.PUBLIC);
if (file.isInterface()) {
virtMethod.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.ABSTRACT | AccessFlag.SYNTHETIC);
} else {
virtMethod.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.SYNTHETIC);
Bytecode b = new Bytecode(file.getConstPool(), 0, 3);
if (BuiltinClassData.skipInstrumentation(file.getSuperclass())) {
b.addNew(NoSuchMethodError.class.getName());
b.add(Opcode.DUP);
b.addInvokespecial(NoSuchMethodError.class.getName(), "<init>", "()V");
b.add(Opcode.ATHROW);
} else {
b.add(Bytecode.ALOAD_0);
b.add(Bytecode.ILOAD_1);
b.add(Bytecode.ALOAD_2);
b.addInvokespecial(file.getSuperclass(), Constants.ADDED_METHOD_NAME, Constants.ADDED_METHOD_DESCRIPTOR);
b.add(Bytecode.ARETURN);
}
virtualCodeAttribute = b.toCodeAttribute();
virtMethod.setCodeAttribute(virtualCodeAttribute);
MethodInfo m = new MethodInfo(file.getConstPool(), Constants.ADDED_STATIC_METHOD_NAME, Constants.ADDED_METHOD_DESCRIPTOR);
modifiedMethods.add(m);
m.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC | AccessFlag.SYNTHETIC);
b = new Bytecode(file.getConstPool(), 0, 3);
b.addNew(NoSuchMethodError.class.getName());
b.add(Opcode.DUP);
b.addInvokespecial(NoSuchMethodError.class.getName(), "<init>", "()V");
b.add(Opcode.ATHROW);
staticCodeAttribute = b.toCodeAttribute();
m.setCodeAttribute(staticCodeAttribute);
file.addMethod(m);
m = new MethodInfo(file.getConstPool(), "<init>", Constants.ADDED_CONSTRUCTOR_DESCRIPTOR);
modifiedMethods.add(m);
m.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.SYNTHETIC);
b = new Bytecode(file.getConstPool(), 0, 4);
if (ManipulationUtils.addBogusConstructorCall(file, b)) {
constructorCodeAttribute = b.toCodeAttribute();
m.setCodeAttribute(constructorCodeAttribute);
constructorCodeAttribute.setMaxLocals(6);
file.addMethod(m);
}
}
file.addMethod(virtMethod);
} catch (DuplicateMemberException e) {
e.printStackTrace();
}
Set<MethodData> methods = new HashSet<>();
methods.addAll(data.getMethods());
ListIterator<?> it = file.getMethods().listIterator();
// IncompatibleClassChange exception will be thrown
while (it.hasNext()) {
MethodInfo m = (MethodInfo) it.next();
MethodData md = null;
boolean upgradedVisibility = false;
for (MethodData i : methods) {
if (i.getMethodName().equals(m.getName()) && i.getDescriptor().equals(m.getDescriptor())) {
// depends on what has changed
if (i.getAccessFlags() != m.getAccessFlags()) {
if (AccessFlagUtils.upgradeVisibility(m.getAccessFlags(), i.getAccessFlags())) {
upgradedVisibility = true;
} else if (AccessFlagUtils.downgradeVisibility(m.getAccessFlags(), i.getAccessFlags())) {
// ignore this, we don't need to do anything
} else {
// we can't handle this yet
continue;
}
}
m.setAccessFlags(i.getAccessFlags());
// if it is the constructor
if (m.getName().equals("<init>")) {
try {
Constructor<?> meth = i.getConstructor(oldClass);
AnnotationDataStore.recordConstructorAnnotations(meth, (AnnotationsAttribute) m.getAttribute(AnnotationsAttribute.visibleTag));
AnnotationDataStore.recordConstructorParameterAnnotations(meth, (ParameterAnnotationsAttribute) m.getAttribute(ParameterAnnotationsAttribute.visibleTag));
// now revert the annotations:
m.addAttribute(AnnotationReplacer.duplicateAnnotationsAttribute(file.getConstPool(), meth));
m.addAttribute(AnnotationReplacer.duplicateParameterAnnotationsAttribute(file.getConstPool(), meth));
} catch (Exception e) {
throw new RuntimeException(e);
}
} else if (!m.getName().equals("<clinit>")) {
// we do not have to worry about them
try {
Method meth = i.getMethod(oldClass);
AnnotationDataStore.recordMethodAnnotations(meth, (AnnotationsAttribute) m.getAttribute(AnnotationsAttribute.visibleTag));
AnnotationDataStore.recordMethodParameterAnnotations(meth, (ParameterAnnotationsAttribute) m.getAttribute(ParameterAnnotationsAttribute.visibleTag));
// now revert the annotations:
m.addAttribute(AnnotationReplacer.duplicateAnnotationsAttribute(file.getConstPool(), meth));
m.addAttribute(AnnotationReplacer.duplicateParameterAnnotationsAttribute(file.getConstPool(), meth));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
md = i;
break;
}
}
// we do not need to deal with these
if (m.getName().equals(Constants.ADDED_METHOD_NAME) || m.getName().equals(Constants.ADDED_STATIC_METHOD_NAME)) {
break;
}
// so it is still in the original
if (md == null || upgradedVisibility) {
if ((m.getAccessFlags() & AccessFlag.STATIC) != 0) {
Class<?> c = addMethod(file, loader, m, methodsToAdd, staticCodeAttribute, true, oldClass);
if (c != null) {
classesToRetransform.add(c);
}
} else if ((m.getName().equals("<init>"))) {
addConstructor(file, loader, m, constructorsToAdd, constructorCodeAttribute, oldClass);
} else if (m.getName().equals("<clinit>")) {
// nop, we can't change this, just ignore it
} else {
Class<?> c = addMethod(file, loader, m, methodsToAdd, virtualCodeAttribute, false, oldClass);
if (c != null) {
classesToRetransform.add(c);
}
}
if (!upgradedVisibility) {
it.remove();
}
} else {
methods.remove(md);
}
if (upgradedVisibility) {
methods.remove(md);
}
}
for (MethodData md : methods) {
if (md.getType() == MemberType.NORMAL) {
MethodInfo removedMethod = createRemovedMethod(file, md, oldClass, methodsToRemove);
if (removedMethod != null) {
modifiedMethods.add(removedMethod);
}
}
}
ClassDataStore.instance().modifyCurrentData(loader, className, (builder) -> {
for (MethodData method : methodsToRemove) {
builder.removeMethod(method);
}
for (FakeMethod fake : methodsToAdd) {
ClassDataStore.instance().registerReplacedMethod(fake.proxyName, builder.addFakeMethod(fake.name, fake.descriptor, fake.proxyName, fake.accessFlags));
}
for (FakeMethod fake : constructorsToAdd) {
ClassDataStore.instance().registerReplacedMethod(fake.proxyName, builder.addFakeConstructor(fake.name, fake.descriptor, fake.proxyName, fake.accessFlags, fake.methodCount));
}
});
// the method declaration to propagate the call to the parent
if (!file.isInterface()) {
try {
staticCodeAttribute.computeMaxStack();
virtualCodeAttribute.computeMaxStack();
if (constructorCodeAttribute != null) {
constructorCodeAttribute.computeMaxStack();
}
} catch (BadBytecode e) {
e.printStackTrace();
}
}
return true;
}
use of javassist.bytecode.DuplicateMemberException in project fakereplace by fakereplace.
the class MethodReplacementTransformer method generateProxyInvocationBytecode.
private static String generateProxyInvocationBytecode(MethodInfo mInfo, int methodNumber, String className, ClassLoader loader, boolean staticMethod, boolean isInterface) throws BadBytecode {
String proxyName = ProxyDefinitionStore.getProxyName();
ClassFile proxy = new ClassFile(false, proxyName, "java.lang.Object");
proxy.setVersionToJava5();
proxy.setAccessFlags(AccessFlag.PUBLIC);
// now generate our proxy that is used to actually call the method
// we use a proxy because it makes the re-writing of loaded classes
// much simpler
MethodInfo nInfo;
if (staticMethod) {
nInfo = new MethodInfo(proxy.getConstPool(), mInfo.getName(), mInfo.getDescriptor());
} else {
// the descriptor is different as now there is an extra parameter for a
// static call
String nDesc = "(" + DescriptorUtils.extToInt(className) + mInfo.getDescriptor().substring(1);
nInfo = new MethodInfo(proxy.getConstPool(), mInfo.getName(), nDesc);
}
copyMethodAttributes(mInfo, nInfo);
// set the sync bit on the proxy if it was set on the method
nInfo.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC);
Bytecode proxyBytecode = new Bytecode(proxy.getConstPool());
int paramOffset = 0;
// onto the stack
if (!staticMethod) {
proxyBytecode.addAload(0);
paramOffset = 1;
}
// stick the method number in the const pool then load it onto the
// stack
int scind = proxy.getConstPool().addIntegerInfo(methodNumber);
proxyBytecode.addLdc(scind);
String[] types = DescriptorUtils.descriptorStringToParameterArray(mInfo.getDescriptor());
// create a new array the same size as the parameter array
int index = proxyBytecode.getConstPool().addIntegerInfo(types.length);
proxyBytecode.addLdc(index);
// create new array to use to pass our parameters
proxyBytecode.addAnewarray("java.lang.Object");
int locals = types.length + paramOffset;
for (int i = 0; i < types.length; ++i) {
// duplicate the array reference on the stack
proxyBytecode.add(Opcode.DUP);
// load the array index into the stack
index = proxyBytecode.getConstPool().addIntegerInfo(i);
proxyBytecode.addLdc(index);
char tp = types[i].charAt(0);
if (tp != 'L' && tp != '[') {
// we have a primitive type
switch(tp) {
case 'J':
proxyBytecode.addLload(i + paramOffset);
locals++;
break;
case 'D':
proxyBytecode.addDload(i + paramOffset);
locals++;
break;
case 'F':
proxyBytecode.addFload(i + paramOffset);
break;
default:
proxyBytecode.addIload(i + paramOffset);
}
// lets box it
Boxing.box(proxyBytecode, tp);
} else {
// load parameter i onto
proxyBytecode.addAload(i + paramOffset);
// the stack
}
// store the value in the array
proxyBytecode.add(Opcode.AASTORE);
}
// invoke the added static method
if (staticMethod) {
proxyBytecode.addInvokestatic(className, Constants.ADDED_STATIC_METHOD_NAME, "(I[Ljava/lang/Object;)Ljava/lang/Object;");
} else if (isInterface) {
proxyBytecode.addInvokeinterface(className, Constants.ADDED_METHOD_NAME, "(I[Ljava/lang/Object;)Ljava/lang/Object;", 3);
} else {
proxyBytecode.addInvokevirtual(className, Constants.ADDED_METHOD_NAME, "(I[Ljava/lang/Object;)Ljava/lang/Object;");
}
// cast it to the appropriate type and return it
ManipulationUtils.MethodReturnRewriter.addReturnProxyMethod(mInfo.getDescriptor(), proxyBytecode);
CodeAttribute ca = proxyBytecode.toCodeAttribute();
ca.setMaxLocals(locals);
ca.computeMaxStack();
nInfo.setCodeAttribute(ca);
// this is so that we do not need to instrument the reflection API to much
if (!staticMethod) {
// as this method is never called the bytecode just returns
MethodInfo method = new MethodInfo(proxy.getConstPool(), mInfo.getName(), mInfo.getDescriptor());
method.setAccessFlags(mInfo.getAccessFlags());
if ((method.getAccessFlags() & AccessFlag.ABSTRACT) == 0) {
Bytecode b = new Bytecode(proxy.getConstPool());
String ret = DescriptorUtils.getReturnType(mInfo.getDescriptor());
if (ret.length() == 1) {
switch(ret) {
case "V":
b.add(Opcode.RETURN);
break;
case "D":
b.add(Opcode.DCONST_0);
b.add(Opcode.DRETURN);
break;
case "F":
b.add(Opcode.FCONST_0);
b.add(Opcode.FRETURN);
break;
case "J":
b.add(Opcode.LCONST_0);
b.add(Opcode.LRETURN);
break;
default:
b.add(Opcode.ICONST_0);
b.add(Opcode.IRETURN);
break;
}
} else {
b.add(Opcode.ACONST_NULL);
b.add(Opcode.ARETURN);
}
method.setCodeAttribute(b.toCodeAttribute());
method.getCodeAttribute().computeMaxStack();
method.getCodeAttribute().setMaxLocals(locals);
}
copyMethodAttributes(mInfo, method);
try {
proxy.addMethod(method);
} catch (DuplicateMemberException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
proxy.addMethod(nInfo);
} catch (DuplicateMemberException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bytes);
proxy.write(dos);
ProxyDefinitionStore.saveProxyDefinition(loader, proxyName, bytes.toByteArray());
return proxyName;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Aggregations