SafeClassWriter

This commit is contained in:
zaxar163 2018-09-21 19:42:36 +03:00
parent 84475c3b00
commit f09025bd2c
2 changed files with 254 additions and 0 deletions

View file

@ -0,0 +1,220 @@
package ru.gravit.launchserver.asm;
import org.objectweb.asm.*;
import launcher.helper.IOHelper;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarFile;
/**
* Позволяет при помощи велосипеда из костылей искать методы внутри незагруженных классов
* и общие суперклассы для чего угодно. Работает через поиск class-файлов в classpath, и, в случае провала -
* ищет через рефлексию.
*/
public class ClassMetadataReader {
private static final Method m;
static {
try {
m = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
m.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new InternalError(e);
}
}
private final List<JarFile> list;
public ClassMetadataReader() {
this.list = new ArrayList<>();
}
public ClassMetadataReader(List<JarFile> list) {
this.list = new ArrayList<>(list);
}
public void add(JarFile jar) {
list.add(jar);
}
public byte[] getClassData(String className) throws IOException {
String classResourceName = className.replace('.', '/') + ".class";
for (JarFile jar : list) {
if (jar.getJarEntry(classResourceName) != null) return IOHelper.read(jar.getInputStream(jar.getJarEntry(classResourceName)));
}
return IOHelper.read(ClassMetadataReader.class.getResourceAsStream('/' + classResourceName));
}
public void acceptVisitor(byte[] classData, ClassVisitor visitor) {
new ClassReader(classData).accept(visitor, 0);
}
public void acceptVisitor(String className, ClassVisitor visitor) throws IOException {
acceptVisitor(getClassData(className), visitor);
}
public MethodReference findVirtualMethod(String owner, String name, String desc) {
ArrayList<String> superClasses = getSuperClasses(owner);
for (int i = superClasses.size() - 1; i > 0; i--) { // чекать текущий класс смысла нет
String className = superClasses.get(i);
MethodReference methodReference = getMethodReference(className, name, desc);
if (methodReference != null) {
System.out.println("found virtual method: " + methodReference);
return methodReference;
}
}
return null;
}
private MethodReference getMethodReference(String type, String methodName, String desc) {
try {
return getMethodReferenceASM(type, methodName, desc);
} catch (Exception e) {
return getMethodReferenceReflect(type, methodName, desc);
}
}
protected MethodReference getMethodReferenceASM(String type, String methodName, String desc) throws IOException {
FindMethodClassVisitor cv = new FindMethodClassVisitor(methodName, desc);
acceptVisitor(type, cv);
if (cv.found) {
return new MethodReference(type, cv.targetName, cv.targetDesc);
}
return null;
}
protected MethodReference getMethodReferenceReflect(String type, String methodName, String desc) {
Class<?> loadedClass = getLoadedClass(type);
if (loadedClass != null) {
for (Method m : loadedClass.getDeclaredMethods()) {
if (checkSameMethod(methodName, desc, m.getName(), Type.getMethodDescriptor(m))) {
return new MethodReference(type, m.getName(), Type.getMethodDescriptor(m));
}
}
}
return null;
}
protected boolean checkSameMethod(String sourceName, String sourceDesc, String targetName, String targetDesc) {
return sourceName.equals(targetName) && sourceDesc.equals(targetDesc);
}
/**
* Возвращает суперклассы в порядке возрастающей конкретности (начиная с java/lang/Object
* и заканчивая данным типом)
*/
public ArrayList<String> getSuperClasses(String type) {
ArrayList<String> superclasses = new ArrayList<String>(1);
superclasses.add(type);
while ((type = getSuperClass(type)) != null) {
superclasses.add(type);
}
Collections.reverse(superclasses);
return superclasses;
}
private Class<?> getLoadedClass(String type) {
if (m != null) {
try {
ClassLoader classLoader = ClassMetadataReader.class.getClassLoader();
return (Class<?>) m.invoke(classLoader, type.replace('/', '.'));
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
public String getSuperClass(String type) {
try {
return getSuperClassASM(type);
} catch (Exception e) {
return getSuperClassReflect(type);
}
}
protected String getSuperClassASM(String type) throws IOException {
CheckSuperClassVisitor cv = new CheckSuperClassVisitor();
acceptVisitor(type, cv);
return cv.superClassName;
}
protected String getSuperClassReflect(String type) {
Class<?> loadedClass = getLoadedClass(type);
if (loadedClass != null) {
if (loadedClass.getSuperclass() == null) return null;
return loadedClass.getSuperclass().getName().replace('.', '/');
}
return "java/lang/Object";
}
private class CheckSuperClassVisitor extends ClassVisitor {
String superClassName;
public CheckSuperClassVisitor() {
super(Opcodes.ASM5);
}
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
this.superClassName = superName;
}
}
protected class FindMethodClassVisitor extends ClassVisitor {
public String targetName;
public String targetDesc;
public boolean found;
public FindMethodClassVisitor(String name, String desc) {
super(Opcodes.ASM5);
this.targetName = name;
this.targetDesc = desc;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
System.out.println("visiting " + name + "#" + desc);
if ((access & Opcodes.ACC_PRIVATE) == 0 && checkSameMethod(name, desc, targetName, targetDesc)) {
found = true;
targetName = name;
targetDesc = desc;
}
return null;
}
}
public static class MethodReference {
public final String owner;
public final String name;
public final String desc;
public MethodReference(String owner, String name, String desc) {
this.owner = owner;
this.name = name;
this.desc = desc;
}
public Type getType() {
return Type.getMethodType(desc);
}
@Override public String toString() {
return "MethodReference{" +
"owner='" + owner + '\'' +
", name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}
}

View file

@ -0,0 +1,34 @@
package ru.gravit.launchserver.asm;
import org.objectweb.asm.ClassWriter;
import java.util.ArrayList;
/**
* ClassWriter с другой реализацией метода getCommonSuperClass: при его использовании не происходит загрузки классов.
* Однако, сама по себе загрузка классов редко является проблемой, потому что инициализация класса (вызов статических
* блоков) происходит не при загрузке класса. Проблемы появляются, когда изменения вставляются в зависимые друг от друга
* классы, тогда стандартная реализация отваливается с ClassCircularityError.
*/
public class SafeClassWriter extends ClassWriter {
private final ClassMetadataReader classMetadataReader;
public SafeClassWriter(ClassMetadataReader classMetadataReader, int flags) {
super(flags);
this.classMetadataReader = classMetadataReader;
}
@Override
protected String getCommonSuperClass(String type1, String type2) {
ArrayList<String> superClasses1 = classMetadataReader.getSuperClasses(type1);
ArrayList<String> superClasses2 = classMetadataReader.getSuperClasses(type2);
int size = Math.min(superClasses1.size(), superClasses2.size());
int i;
for (i = 0; i < size && superClasses1.get(i).equals(superClasses2.get(i)); i++);
if (i == 0) {
return "java/lang/Object";
} else {
return superClasses1.get(i-1);
}
}
}