mirror of
https://github.com/GravitLauncher/Launcher
synced 2025-04-01 22:14:01 +03:00
SafeClassWriter
This commit is contained in:
parent
84475c3b00
commit
f09025bd2c
2 changed files with 254 additions and 0 deletions
|
@ -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 + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue