/*
* Copyright 2016, Stuart Douglas, and individual contributors as indicated
* by the @authors tag.
*
* Licensed 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.fakereplace.replacement;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import javassist.ClassPool;
import javassist.LoaderClassPath;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.AttributeInfo;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.Bytecode;
import javassist.bytecode.ClassFile;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.ConstPool;
import javassist.bytecode.DuplicateMemberException;
import javassist.bytecode.ExceptionsAttribute;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import javassist.bytecode.ParameterAnnotationsAttribute;
import javassist.bytecode.SignatureAttribute;
import org.fakereplace.classloading.ProxyDefinitionStore;
import org.fakereplace.core.BuiltinClassData;
import org.fakereplace.core.Constants;
import org.fakereplace.core.Transformer;
import org.fakereplace.data.AnnotationDataStore;
import org.fakereplace.data.BaseClassData;
import org.fakereplace.data.ClassDataStore;
import org.fakereplace.data.MemberType;
import org.fakereplace.data.MethodData;
import org.fakereplace.logging.Logger;
import org.fakereplace.manip.data.FakeMethodCallData;
import org.fakereplace.manip.util.Boxing;
import org.fakereplace.manip.util.ManipulationUtils;
import org.fakereplace.manip.util.ManipulationUtils.MethodReturnRewriter;
import org.fakereplace.replacement.notification.ChangedClassImpl;
import org.fakereplace.runtime.MethodIdentifierStore;
import org.fakereplace.transformation.FakereplaceTransformer;
import org.fakereplace.util.AccessFlagUtils;
import org.fakereplace.util.DescriptorUtils;
public class MethodReplacementTransformer implements FakereplaceTransformer {
private static final Logger logger = Logger.getLogger(MethodReplacementTransformer.class);
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;
// if this is not a static method then we need to load the instance
// 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 {
proxyBytecode.addAload(i + paramOffset); // load parameter i onto
// the stack
}
proxyBytecode.add(Opcode.AASTORE);// store the value in the array
}
// 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);
// now we have the static method that actually does the re-writes.
// if this is a virtual method then we need to add another virtual method
// with the exact signature of the existing
// method.
// 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) {
if (ret.equals("V")) {
b.add(Opcode.RETURN);
} else if (ret.equals("D")) {
b.add(Opcode.DCONST_0);
b.add(Opcode.DRETURN);
} else if (ret.equals("F")) {
b.add(Opcode.FCONST_0);
b.add(Opcode.FRETURN);
} else if (ret.equals("J")) {
b.add(Opcode.LCONST_0);
b.add(Opcode.LRETURN);
} else {
b.add(Opcode.ICONST_0);
b.add(Opcode.IRETURN);
}
} 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);
}
}
/**
* Adds a method to a class
*/
private static Class<?> addMethod(ClassFile file, ClassLoader loader, MethodInfo mInfo, Set<FakeMethod> builder, CodeAttribute bytecode, boolean staticMethod, Class oldClass) {
int methodCount = MethodIdentifierStore.instance().getMethodNumber(mInfo.getName(), mInfo.getDescriptor());
try {
if ((AccessFlag.ABSTRACT & mInfo.getAccessFlags()) == 0) {
// abstract methods don't get a body
generateBoxedConditionalCodeBlock(methodCount, mInfo, file.getConstPool(), bytecode, staticMethod, false);
}
String proxyName = generateProxyInvocationBytecode(mInfo, methodCount, file.getName(), loader, staticMethod, file.isInterface());
ClassDataStore.instance().registerProxyName(oldClass, proxyName);
Transformer.getManipulator().addFakeMethodCallRewrite(new FakeMethodCallData(file.getName(), mInfo.getName(), mInfo.getDescriptor(), staticMethod ? FakeMethodCallData.Type.STATIC : file.isInterface() ? FakeMethodCallData.Type.INTERFACE : FakeMethodCallData.Type.VIRTUAL, loader, methodCount));
builder.add(new FakeMethod(mInfo.getName(), proxyName, mInfo.getDescriptor(), mInfo.getAccessFlags()));
if (!staticMethod) {
Class<?> sup = oldClass.getSuperclass();
while (sup != null && !sup.getName().equals(Object.class.getName())) {
for (Method m : sup.getDeclaredMethods()) {
if (m.getName().equals(mInfo.getName())) {
if (DescriptorUtils.getDescriptor(m).equals(mInfo.getDescriptor())) {
Transformer.getManipulator().rewriteSubclassCalls(file.getName(), loader, sup.getName(), sup.getClassLoader(), mInfo.getName(), mInfo.getDescriptor());
return sup;
}
}
}
sup = sup.getSuperclass();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* This method will take a method body and add it to an added method local
* the bytecode is inserted inside a conditional that will only run the code
* if the method number is correct variables are removed from the parameter
* array and unboxed if nessesary the return value is boxed if nessesary
* <p>
* Much of this work is handled by helper classes
*
*/
private static void generateBoxedConditionalCodeBlock(int methodNumber, MethodInfo mInfo, ConstPool methodConstPool, CodeAttribute addedMethod, boolean staticMethod, boolean constructor)
throws BadBytecode {
// we need to insert a conditional
Bytecode bc = new Bytecode(mInfo.getConstPool());
CodeAttribute ca = (CodeAttribute) mInfo.getCodeAttribute().copy(mInfo.getConstPool(), Collections.emptyMap());
if (staticMethod) {
bc.addOpcode(Opcode.ILOAD_0);
} else {
bc.addOpcode(Opcode.ILOAD_1);
}
int methodCountIndex = methodConstPool.addIntegerInfo(methodNumber);
bc.addLdc(methodCountIndex);
bc.addOpcode(Opcode.IF_ICMPNE);
// now we need to fix local variables and unbox parameters etc
int addedCodeLength = mangleParameters(staticMethod, constructor, ca, mInfo.getDescriptor());
int newMax = ca.getMaxLocals() + 2;
if (constructor) {
// for the extra
newMax++;
}
if (newMax > addedMethod.getMaxLocals()) {
addedMethod.setMaxLocals(newMax);
}
// later
int offset = ca.getCodeLength();
// offset is +3, 2 for the branch offset after the IF_ICMPNE and 1 to
// take it past the end of the code
ManipulationUtils.add16bit(bc, offset + 3); // add the branch offset
// now we need to insert our generated conditional at the start of the
// new method
CodeIterator newInfo = ca.iterator();
newInfo.insert(bc.get());
// now insert the new method code at the beginning of the static method
// code attribute
addedMethod.iterator().insert(ca.getCode());
// update the exception table
int exOffset = bc.length() + addedCodeLength;
for (int i = 0; i < mInfo.getCodeAttribute().getExceptionTable().size(); ++i) {
int start = mInfo.getCodeAttribute().getExceptionTable().startPc(i) + exOffset;
int end = mInfo.getCodeAttribute().getExceptionTable().endPc(i) + exOffset;
int handler = mInfo.getCodeAttribute().getExceptionTable().handlerPc(i) + exOffset;
int type = mInfo.getCodeAttribute().getExceptionTable().catchType(i);
addedMethod.getExceptionTable().add(start, end, handler, type);
}
// now we need to make sure the function is returning an object
// rewriteFakeMethod makes sure that the return type is properly boxed
if (!constructor) {
MethodReturnRewriter.rewriteFakeMethod(addedMethod.iterator(), mInfo.getDescriptor());
}
}
private static MethodInfo createRemovedMethod(ClassFile file, MethodData md, Class<?> oldClass, Set<MethodData> methodsToRemove) {
if (md.getMethodName().equals("<clinit>")) {
return null; // if the static constructor is removed it gets added later on
// in the process
}
// load up the existing method object
MethodInfo m = new MethodInfo(file.getConstPool(), md.getMethodName(), md.getDescriptor());
m.setAccessFlags(md.getAccessFlags());
// put the old annotations on the class
if (md.getMethodName().equals("<init>")) {
Constructor<?> meth;
try {
meth = md.getConstructor(oldClass);
} catch (Exception e) {
throw new RuntimeException("Error accessing existing constructor via reflection in not found", e);
}
m.addAttribute(AnnotationReplacer.duplicateAnnotationsAttribute(file.getConstPool(), meth));
} else {
Method meth;
try {
meth = md.getMethod(oldClass);
} catch (Exception e) {
throw new RuntimeException("Error accessing existing method via reflection in not found", e);
}
m.addAttribute(AnnotationReplacer.duplicateAnnotationsAttribute(file.getConstPool(), meth));
}
Bytecode b = new Bytecode(file.getConstPool(), 5, 3);
b.addNew("java.lang.NoSuchMethodError");
b.add(Opcode.DUP);
b.addInvokespecial("java.lang.NoSuchMethodError", "<init>", "()V");
b.add(Bytecode.ATHROW);
CodeAttribute ca = b.toCodeAttribute();
m.setCodeAttribute(ca);
try {
ca.computeMaxStack();
file.addMethod(m);
} catch (DuplicateMemberException e) {
logger.error("Duplicate error", e);
} catch (BadBytecode e) {
logger.error("Bad bytecode", e);
}
methodsToRemove.add(md);
return m;
}
private static void addConstructor(ClassFile file, ClassLoader loader, MethodInfo mInfo, Set<FakeMethod> builder, CodeAttribute bytecode, Class<?> oldClass) {
int methodCount = MethodIdentifierStore.instance().getMethodNumber(mInfo.getName(), mInfo.getDescriptor());
try {
generateBoxedConditionalCodeBlock(methodCount, mInfo, file.getConstPool(), bytecode, false, true);
String proxyName = generateFakeConstructorBytecode(mInfo, loader);
ClassDataStore.instance().registerProxyName(oldClass, proxyName);
Transformer.getManipulator().rewriteConstructorAccess(file.getName(), mInfo.getDescriptor(), methodCount, loader);
builder.add(new FakeMethod(mInfo.getName(),proxyName, mInfo.getDescriptor(), mInfo.getAccessFlags(), methodCount));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 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);
}
}
public static void copyMethodAttributes(MethodInfo oldMethod, MethodInfo newMethod) {
AnnotationsAttribute annotations = (AnnotationsAttribute) oldMethod.getAttribute(AnnotationsAttribute.visibleTag);
ParameterAnnotationsAttribute pannotations = (ParameterAnnotationsAttribute) oldMethod.getAttribute(ParameterAnnotationsAttribute.visibleTag);
ExceptionsAttribute exAt = (ExceptionsAttribute) oldMethod.getAttribute(ExceptionsAttribute.tag);
SignatureAttribute sigAt = (SignatureAttribute) oldMethod.getAttribute(SignatureAttribute.tag);
if (annotations != null) {
AttributeInfo newAnnotations = annotations.copy(newMethod.getConstPool(), Collections.EMPTY_MAP);
newMethod.addAttribute(newAnnotations);
}
if (pannotations != null) {
AttributeInfo newAnnotations = pannotations.copy(newMethod.getConstPool(), Collections.EMPTY_MAP);
newMethod.addAttribute(newAnnotations);
}
if (sigAt != null) {
AttributeInfo newAnnotations = sigAt.copy(newMethod.getConstPool(), Collections.EMPTY_MAP);
newMethod.addAttribute(newAnnotations);
}
if (exAt != null) {
AttributeInfo newAnnotations = exAt.copy(newMethod.getConstPool(), Collections.EMPTY_MAP);
newMethod.addAttribute(newAnnotations);
}
}
/**
* Takes method parameters out of an array and puts them into local variables in the correct location. Also
* deals with unboxing if necessary
*
* @return the length of the added code
*/
private static int mangleParameters(boolean staticMethod, boolean constructor, CodeAttribute attribute, String methodSigniture) {
try {
int offset = 0;
String[] data = DescriptorUtils.descriptorStringToParameterArray(methodSigniture);
if (!staticMethod) {
// non static methods have a this pointer as the first argument
// which should not be mangled
offset = 1;
}
// insert two new local variables, these are the fake method parameters
attribute.insertLocalVar(offset, 1);
attribute.insertLocalVar(offset, 1);
if (constructor) {
// constructors have an extra one
attribute.insertLocalVar(offset, 1);
}
Bytecode code = new Bytecode(attribute.getConstPool());
int varpos = offset + 2;
if (constructor) {
varpos++;
}
for (int i = 0; i < data.length; ++i) {
// push the parameter array onto the stack
if (staticMethod) {
code.add(Opcode.ALOAD_1);
} else {
code.add(Opcode.ALOAD_2);
}
int index = attribute.getConstPool().addIntegerInfo(i);
code.addLdc(index);
code.add(Opcode.AALOAD);
// now we have the parameter on the stack.
// what happens next depends on the type
switch (data[i].charAt(0)) {
case 'L':
// add a checkcast substring is to get rid of the L
code.addCheckcast(data[i].substring(1));
// now stick it into its proper local variable
code.addAstore(varpos);
varpos++;
break;
case '[':
code.addCheckcast(data[i]);
// now stick it into its proper local variable
code.addAstore(varpos);
varpos++;
break;
case 'I':
// integer, we need to unbox it
Boxing.unboxInt(code);
code.addIstore(varpos);
varpos++;
break;
case 'S':
// short, we need to unbox it
Boxing.unboxShort(code);
code.addIstore(varpos);
varpos++;
break;
case 'B':
// short, we need to unbox it
Boxing.unboxByte(code);
code.addIstore(varpos);
varpos++;
break;
case 'J':
// long, we need to unbox it
Boxing.unboxLong(code);
code.addLstore(varpos);
varpos = varpos + 2;
break;
case 'F':
Boxing.unboxFloat(code);
code.addFstore(varpos);
varpos++;
break;
case 'D':
Boxing.unboxDouble(code);
code.addDstore(varpos);
varpos++;
break;
case 'C':
Boxing.unboxChar(code);
code.addIstore(varpos);
varpos++;
break;
case 'Z':
Boxing.unboxBoolean(code);
code.addIstore(varpos);
varpos++;
break;
}
}
attribute.iterator().insert(0, code.get());
return code.length();
} catch (BadBytecode e) {
throw new RuntimeException(e);
}
}
@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_STATIC_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<MethodData>();
methods.addAll(data.getMethods());
ListIterator<?> it = file.getMethods().listIterator();
// now we iterator through all methods and constructors and compare new
// and old. in the process we modify the new class so that is's signature
// is exactly compatible with the old class, otherwise an
// 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())) {
// if the access flags do not match then what we need to do
// 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>")) {
// other methods
// static constructors cannot have annotations so
// 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;
}
// This is a newly added method.
// or the visilbility has been upgraded
// with the visiblity upgrade we just copy the method
// 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);
}
}
// these methods have been removed, change them to throw a
// MethodNotFoundError
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));
}
});
// if we did not return from a virtual method we need to call the parent
// method directly so to this end we append some stuff to the bottom of
// 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;
}
private static final class FakeMethod {
final String name;
final String proxyName;
final String descriptor;
final int accessFlags;
final int methodCount;
private FakeMethod(String name, String proxyName, String descriptor, int accessFlags) {
this.name = name;
this.proxyName = proxyName;
this.descriptor = descriptor;
this.accessFlags = accessFlags;
this.methodCount = -1;
}
private FakeMethod(String name, String proxyName, String descriptor, int accessFlags, int methodCount) {
this.name = name;
this.proxyName = proxyName;
this.descriptor = descriptor;
this.accessFlags = accessFlags;
this.methodCount = methodCount;
}
}
}