use of org.fakereplace.data.BaseClassData 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 org.fakereplace.data.BaseClassData 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 org.fakereplace.data.BaseClassData in project fakereplace by fakereplace.
the class Fakereplace method redefine.
public static void redefine(ClassDefinition[] classes, AddedClass[] addedData, boolean wait) {
try {
for (AddedClass i : addedData) {
ClassFile cf = new ClassFile(new DataInputStream(new ByteArrayInputStream(i.getData())));
mainTransformer.addNewClass(new NewClassData(i.getClassName(), i.getLoader(), cf));
}
for (ClassDefinition i : classes) {
ClassDataStore.instance().markClassReplaced(i.getDefinitionClass());
BaseClassData baseClassData = ClassDataStore.instance().getBaseClassData(i.getDefinitionClass().getClassLoader(), i.getDefinitionClass().getName());
if (baseClassData != null) {
ClassDataStore.instance().saveClassData(i.getDefinitionClass().getClassLoader(), i.getDefinitionClass().getName(), new ClassDataBuilder(baseClassData));
}
}
for (AddedClass c : addedData) {
ClassLookupManager.addClassInfo(c.getClassName(), c.getLoader(), c.getData());
}
inst.redefineClasses(classes);
clearJvmCaches();
if (wait) {
mainTransformer.waitForTasks();
}
} catch (Throwable e) {
try {
// dump the classes to /tmp so we can look at them
for (ClassDefinition d : classes) {
try {
ByteArrayInputStream bin = new ByteArrayInputStream(d.getDefinitionClassFile());
DataInputStream dis = new DataInputStream(bin);
final ClassFile file = new ClassFile(dis);
Transformer.getManipulator().transformClass(file, d.getDefinitionClass().getClassLoader(), true, new HashSet<>());
String dumpDir = AgentOptions.getOption(AgentOption.DUMP_DIR);
if (dumpDir != null) {
FileOutputStream s = new FileOutputStream(dumpDir + '/' + d.getDefinitionClass().getName() + "1.class");
DataOutputStream dos = new DataOutputStream(s);
file.write(dos);
dos.flush();
dos.close();
// s.write(d.getDefinitionClassFile());
s.close();
}
} catch (IOException a) {
a.printStackTrace();
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
throw (new RuntimeException(e));
}
}
use of org.fakereplace.data.BaseClassData in project fakereplace by fakereplace.
the class Transformer method transform.
public boolean transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, ClassFile file, Set<Class<?>> classesToRetransform, ChangedClassImpl changedClass, Set<MethodInfo> modifiedMethods) throws IllegalClassFormatException, BadBytecode, DuplicateMemberException {
boolean modified = false;
if (classBeingRedefined != null) {
ClassDataStore.instance().markClassReplaced(classBeingRedefined);
}
// java/io namespace except for java/lang/reflect/Proxy
if (BuiltinClassData.skipInstrumentation(className)) {
if (classBeingRedefined != null && manipulator.transformClass(file, loader, false, modifiedMethods)) {
modified = true;
}
return modified;
}
if (classBeingRedefined == null) {
AnnotationsAttribute at = (AnnotationsAttribute) file.getAttribute(AnnotationsAttribute.invisibleTag);
if (at != null) {
// NoInstrument is used for testing or by integration modules
Object an = at.getAnnotation(NoInstrument.class.getName());
if (an != null) {
return modified;
}
}
}
final boolean replaceable = Fakereplace.isClassReplaceable(className, loader);
if (manipulator.transformClass(file, loader, replaceable, modifiedMethods)) {
modified = true;
}
if (replaceable) {
if ((AccessFlag.ENUM & file.getAccessFlags()) == 0 && (AccessFlag.ANNOTATION & file.getAccessFlags()) == 0) {
modified = true;
watcher.addClassFile(className, loader);
if (file.isInterface()) {
addAbstractMethodForInstrumentation(file);
} else {
addMethodForInstrumentation(file);
addConstructorForInstrumentation(file);
addStaticConstructorForInstrumentation(file);
}
}
if (classBeingRedefined == null) {
BaseClassData baseData = new BaseClassData(file, loader, replaceable);
ClassDataStore.instance().saveClassData(loader, baseData.getInternalName(), baseData);
}
}
// file.getSuperclass(), classfileBuffer);
return modified;
}
use of org.fakereplace.data.BaseClassData in project fakereplace by fakereplace.
the class FakeMethodCallManipulator method transformClass.
public boolean transformClass(ClassFile file, ClassLoader loader, boolean modifiableClass, final Set<MethodInfo> modifiedMethods) {
if (!Fakereplace.isRetransformationStarted()) {
return false;
}
final Map<String, Set<Data>> knownFakeMethods = data.getManipulationData(loader);
// methods that are known to need a rewrite to a generated static method
final Map<Integer, Data> knownFakeMethodCallLocations = new HashMap<>();
// methods that may need a rewrite to a generated static method
final Map<Integer, AddedMethodInfo> potentialFakeMethodCallLocations = new HashMap<>();
// first we need to scan the constant pool looking for
// CONSTANT_method_info_ref structures
ConstPool pool = file.getConstPool();
for (int i = 1; i < pool.getSize(); ++i) {
// we have a method call
if (pool.getTag(i) == ConstPool.CONST_Methodref || pool.getTag(i) == ConstPool.CONST_InterfaceMethodref) {
String className, methodDesc, methodName;
if (pool.getTag(i) == ConstPool.CONST_Methodref) {
className = pool.getMethodrefClassName(i);
methodDesc = pool.getMethodrefType(i);
methodName = pool.getMethodrefName(i);
} else {
className = pool.getInterfaceMethodrefClassName(i);
methodDesc = pool.getInterfaceMethodrefType(i);
methodName = pool.getInterfaceMethodrefName(i);
}
if (methodName.equals("<clinit>") || methodName.equals("<init>")) {
continue;
}
boolean handled = false;
if (knownFakeMethods.containsKey(className)) {
for (Data data : knownFakeMethods.get(className)) {
if (methodName.equals(data.getMethodName()) && methodDesc.equals(data.getMethodDesc())) {
// store the location in the const pool of the method ref
knownFakeMethodCallLocations.put(i, data);
// we have found a method call
// now lets replace it
handled = true;
break;
}
}
}
if (loader != null && !handled && !className.equals(file.getName()) && Fakereplace.isClassReplaceable(className, loader)) {
// may be an added method
// if the field does not actually exist yet we just assume it is about to come into existence
// and rewrite it anyway
BaseClassData data = ClassDataStore.instance().getBaseClassData(loader, className);
if (data != null) {
boolean noClassData = false;
MethodData method = null;
try {
Class<?> mainClass = loader.loadClass(className);
Set<Class> allClasses = new HashSet<>();
addToAllClasses(mainClass, allClasses);
for (Class clazz : allClasses) {
data = ClassDataStore.instance().getBaseClassData(clazz.getClassLoader(), clazz.getName());
if (data == null) {
noClassData = true;
break;
}
method = data.getMethodOrConstructor(methodName, methodDesc);
if (method != null) {
break;
}
}
} catch (ClassNotFoundException e) {
noClassData = true;
}
if (!noClassData) {
if (method == null) {
// this is a new method
// lets deal with it
int methodNo = MethodIdentifierStore.instance().getMethodNumber(methodName, methodDesc);
potentialFakeMethodCallLocations.put(i, new AddedMethodInfo(methodNo, className, methodName, methodDesc));
} else if (!Modifier.isPublic(method.getAccessFlags())) {
boolean requiresVisibilityUpgrade = false;
if (Modifier.isPrivate(method.getAccessFlags())) {
requiresVisibilityUpgrade = true;
} else if (!Modifier.isProtected(method.getAccessFlags())) {
// we can't handle protected properly, because we need to know the class heirachy
// this is package local, so we check the package names
boolean thisDefault = !file.getName().contains(".");
boolean thatDefault = !className.contains(".");
if (thisDefault && !thatDefault) {
requiresVisibilityUpgrade = true;
} else if (thatDefault && !thisDefault) {
requiresVisibilityUpgrade = true;
} else if (!thatDefault) {
String thatPackage = className.substring(0, className.lastIndexOf("."));
String thisPackage = file.getName().substring(0, file.getName().lastIndexOf("."));
if (!thisPackage.equals(thatPackage)) {
requiresVisibilityUpgrade = true;
}
}
}
if (requiresVisibilityUpgrade) {
int methodNo = MethodIdentifierStore.instance().getMethodNumber(methodName, methodDesc);
potentialFakeMethodCallLocations.put(i, new AddedMethodInfo(methodNo, className, methodName, methodDesc));
}
}
}
}
}
}
}
// through the methods and replace instances of the call
if (!knownFakeMethodCallLocations.isEmpty() || !potentialFakeMethodCallLocations.isEmpty()) {
handleLambdas(file, knownFakeMethodCallLocations, pool);
List<MethodInfo> methods = file.getMethods();
for (MethodInfo m : methods) {
try {
// ignore abstract methods
if (m.getCodeAttribute() == null) {
continue;
}
CodeIterator it = m.getCodeAttribute().iterator();
while (it.hasNext()) {
// loop through the bytecode
int index = it.next();
int op = it.byteAt(index);
// if the bytecode is a method invocation
if (op == CodeIterator.INVOKEVIRTUAL || op == CodeIterator.INVOKESTATIC || op == CodeIterator.INVOKEINTERFACE || op == CodeIterator.INVOKESPECIAL) {
int val = it.s16bitAt(index + 1);
// replacing
if (potentialFakeMethodCallLocations.containsKey(val)) {
AddedMethodInfo methodInfo = potentialFakeMethodCallLocations.get(val);
Data data = new Data(methodInfo.className, methodInfo.name, methodInfo.desc, op == Opcode.INVOKESTATIC ? Type.STATIC : op == Opcode.INVOKEINTERFACE ? Type.INTERFACE : Type.VIRTUAL, loader, methodInfo.number, null);
handleFakeMethodCall(file, modifiedMethods, m, it, index, op, data);
} else if (knownFakeMethodCallLocations.containsKey(val)) {
Data data = knownFakeMethodCallLocations.get(val);
handleFakeMethodCall(file, modifiedMethods, m, it, index, op, data);
}
}
}
modifiedMethods.add(m);
m.getCodeAttribute().computeMaxStack();
} catch (Exception e) {
log.error("Bad byte code transforming " + file.getName(), e);
e.printStackTrace();
}
}
return true;
} else {
return false;
}
}
Aggregations