Put invokedynamic bootstrap method in target class

This commit is contained in:
yushijinhun 2020-04-30 00:19:18 +08:00
parent 106e13b17f
commit 71913d41ab
No known key found for this signature in database
GPG key ID: 5BC167F73EA558E4
5 changed files with 104 additions and 48 deletions

View file

@ -1,35 +0,0 @@
/*
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package moe.yushi.authlibinjector.transform;
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
public final class CallbackEntryPoint {
private CallbackEntryPoint() {
}
public static CallSite bootstrap(Lookup lookup, String name, MethodType type, String owner) throws ReflectiveOperationException {
return new ConstantCallSite(
lookup.findStatic(
ClassLoader.getSystemClassLoader().loadClass(owner),
name,
type));
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (C) 2020 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package moe.yushi.authlibinjector.transform;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.ASM7;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.NEW;
import java.util.Optional;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
class CallbackMetafactoryTransformer implements TransformUnit {
@Override
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext context) {
return Optional.of(new ClassVisitor(ASM7, writer) {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
MethodVisitor mv = super.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC,
CallbackSupport.METAFACTORY_NAME,
CallbackSupport.METAFACTORY_SIGNATURE,
null, null);
mv.visitCode();
mv.visitTypeInsn(NEW, "java/lang/invoke/ConstantCallSite");
mv.visitInsn(DUP);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", "()Ljava/lang/ClassLoader;", false);
mv.visitVarInsn(ALOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false);
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "<init>", "(Ljava/lang/invoke/MethodHandle;)V", false);
mv.visitInsn(ARETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
context.markModified();
}
});
}
@Override
public String toString() {
return "Callback Metafactory Transformer";
}
}

View file

@ -16,12 +16,8 @@
*/
package moe.yushi.authlibinjector.transform;
import static org.objectweb.asm.Opcodes.H_INVOKESTATIC;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
@ -29,6 +25,9 @@ public final class CallbackSupport {
private CallbackSupport() {
}
static final String METAFACTORY_NAME = "__authlibinjector_metafactory";
static final String METAFACTORY_SIGNATURE = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;";
private static Method findCallbackMethod(Class<?> owner, String methodName) {
for (Method method : owner.getDeclaredMethods()) {
int modifiers = method.getModifiers();
@ -41,18 +40,11 @@ public final class CallbackSupport {
throw new IllegalArgumentException("No such method: " + methodName);
}
private static final Handle BOOTSTRAP_METHOD = new Handle(
H_INVOKESTATIC,
Type.getInternalName(CallbackEntryPoint.class),
"bootstrap",
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;",
false);
public static void invoke(TransformContext ctx, MethodVisitor mv, Class<?> owner, String methodName) {
ctx.requireMinimumClassVersion(50);
ctx.upgradeClassVersion(51);
String descriptor = Type.getMethodDescriptor(findCallbackMethod(owner, methodName));
mv.visitInvokeDynamicInsn(methodName, descriptor, BOOTSTRAP_METHOD, owner.getName());
mv.visitInvokeDynamicInsn(methodName, descriptor, ctx.acquireCallbackMetafactory(), owner.getName());
}
}

View file

@ -17,6 +17,7 @@
package moe.yushi.authlibinjector.transform;
import static java.util.Collections.emptyList;
import static org.objectweb.asm.Opcodes.H_INVOKESTATIC;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
@ -31,6 +32,7 @@ import java.util.logging.Level;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Handle;
import moe.yushi.authlibinjector.AuthlibInjector;
import moe.yushi.authlibinjector.util.Logging;
@ -44,9 +46,16 @@ public class ClassTransformer implements ClassFileTransformer {
private static class TransformContextImpl implements TransformContext {
private final String className;
public boolean modifiedMark;
public int minVersionMark = -1;
public int upgradedVersionMark = -1;
public boolean callbackMetafactoryRequested = false;
public TransformContextImpl(String className) {
this.className = className;
}
@Override
public void markModified() {
@ -66,6 +75,17 @@ public class ClassTransformer implements ClassFileTransformer {
this.upgradedVersionMark = version;
}
}
@Override
public Handle acquireCallbackMetafactory() {
this.callbackMetafactoryRequested = true;
return new Handle(
H_INVOKESTATIC,
className.replace('.', '/'),
CallbackSupport.METAFACTORY_NAME,
CallbackSupport.METAFACTORY_SIGNATURE,
false);
}
}
private static class TransformHandle {
@ -77,6 +97,7 @@ public class ClassTransformer implements ClassFileTransformer {
private List<TransformUnit> appliedTransformers;
private int minVersion = -1;
private int upgradedVersion = -1;
private boolean addCallbackMetafactory = false;
public TransformHandle(ClassLoader classLoader, String className, byte[] classBuffer) {
this.className = className;
@ -86,7 +107,7 @@ public class ClassTransformer implements ClassFileTransformer {
public void accept(TransformUnit unit) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
TransformContextImpl ctx = new TransformContextImpl();
TransformContextImpl ctx = new TransformContextImpl(className);
Optional<ClassVisitor> optionalVisitor = unit.transform(classLoader, className, writer, ctx);
if (optionalVisitor.isPresent()) {
@ -105,6 +126,7 @@ public class ClassTransformer implements ClassFileTransformer {
if (ctx.upgradedVersionMark > this.upgradedVersion) {
this.upgradedVersion = ctx.upgradedVersionMark;
}
this.addCallbackMetafactory |= ctx.callbackMetafactoryRequested;
}
}
}
@ -113,6 +135,9 @@ public class ClassTransformer implements ClassFileTransformer {
if (appliedTransformers == null || appliedTransformers.isEmpty()) {
return Optional.empty();
} else {
if (addCallbackMetafactory) {
accept(new CallbackMetafactoryTransformer());
}
if (minVersion == -1 && upgradedVersion == -1) {
return Optional.of(classBuffer);
} else {

View file

@ -16,6 +16,8 @@
*/
package moe.yushi.authlibinjector.transform;
import org.objectweb.asm.Handle;
public interface TransformContext {
void markModified();
@ -24,4 +26,5 @@ public interface TransformContext {
void upgradeClassVersion(int version);
Handle acquireCallbackMetafactory();
}