From b54dd49426f1c4cb5a15d1702a4803af1846847e Mon Sep 17 00:00:00 2001 From: PimvanderLoos Date: Mon, 4 Jan 2021 06:24:34 +0100 Subject: [PATCH] Replace CGLib with ByteBuddy (#984) - The gclib dependency in the EnchancerFactory has been removed. All classes that used the actual factory part of it have been updated to use bytebuddy instead. This class will have to be removed at some point, but at the moment it is still used for accessing its class loader. - Renamed EnhancerFactory to ByteBuddyFactory. All ByteBuddy actions should go through this now. Every subclass created here implements the ByteBuddyGenerated interface. This makes it possible to recognize classes generated using ByteBuddy (by default, it doesn't leave such a trace). - Removed the method DefaultInstances#forEnhancer(Enhancer). This method isn't used anywhere; the last trace of usage of the method I could find was in 2013 (in the NetworkServerInjector). External plugins (I couldn't find any that used it), they should really have their own implementation, given that they already require an instance of an Enchancer. As such, I feel it is safe to remove rather than update it. --- pom.xml | 14 +- .../com/comphenix/protocol/CommandPacket.java | 5 +- .../com/comphenix/protocol/ProtocolLib.java | 4 +- .../events/SerializedOfflinePlayer.java | 123 +++++++---- .../injector/netty/ChannelInjector.java | 12 +- .../server/TemporaryPlayerFactory.java | 135 +++++++----- .../protocol/reflect/ClassAnalyser.java | 29 +-- .../reflect/compiler/BoxingHelper.java | 162 +++++++-------- .../reflect/compiler/MethodDescriptor.java | 16 +- .../reflect/compiler/StructureCompiler.java | 192 +++++++++--------- .../reflect/instances/DefaultInstances.java | 20 -- .../protocol/utility/ByteBuddyFactory.java | 65 ++++++ .../protocol/utility/ByteBuddyGenerated.java | 9 + .../protocol/utility/EnhancerFactory.java | 44 ---- .../protocol/utility/MinecraftMethods.java | 80 +++++--- .../wrappers/nbt/TileEntityAccessor.java | 125 ++++++++---- .../events/SerializedOfflinePlayerTest.java | 72 +++++++ .../server/TemporaryPlayerFactoryTest.java | 42 ++++ .../utility/MinecraftMethodsTest.java | 17 ++ 19 files changed, 725 insertions(+), 441 deletions(-) create mode 100644 src/main/java/com/comphenix/protocol/utility/ByteBuddyFactory.java create mode 100644 src/main/java/com/comphenix/protocol/utility/ByteBuddyGenerated.java delete mode 100644 src/main/java/com/comphenix/protocol/utility/EnhancerFactory.java create mode 100644 src/test/java/com/comphenix/protocol/events/SerializedOfflinePlayerTest.java create mode 100644 src/test/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactoryTest.java diff --git a/pom.xml b/pom.xml index b291c902..4702c721 100644 --- a/pom.xml +++ b/pom.xml @@ -51,8 +51,8 @@ - net.sf - com.comphenix.net.sf + net.bytebuddy + com.comphenix.net.bytebuddy @@ -100,6 +100,8 @@ maven-surefire-plugin 3.0.0-M5 + false + false projectVersion @@ -291,11 +293,11 @@ ${spigot.version} provided + - cglib - cglib-nodep - 3.2.5 - compile + net.bytebuddy + byte-buddy + 1.10.16 diff --git a/src/main/java/com/comphenix/protocol/CommandPacket.java b/src/main/java/com/comphenix/protocol/CommandPacket.java index a6ac8be1..30208929 100644 --- a/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -29,13 +29,12 @@ import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; -import net.sf.cglib.proxy.Factory; - import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; +import com.comphenix.protocol.utility.ByteBuddyGenerated; import com.comphenix.protocol.PacketType.Sender; import com.comphenix.protocol.concurrency.PacketTypeSet; import com.comphenix.protocol.error.ErrorReporter; @@ -464,7 +463,7 @@ class CommandPacket extends CommandBase { // Get the first Minecraft super class while (clazz != null && clazz != Object.class && (!MinecraftReflection.isMinecraftClass(clazz) || - Factory.class.isAssignableFrom(clazz))) { + ByteBuddyGenerated.class.isAssignableFrom(clazz))) { clazz = clazz.getSuperclass(); } diff --git a/src/main/java/com/comphenix/protocol/ProtocolLib.java b/src/main/java/com/comphenix/protocol/ProtocolLib.java index 42ce511b..84138a10 100644 --- a/src/main/java/com/comphenix/protocol/ProtocolLib.java +++ b/src/main/java/com/comphenix/protocol/ProtocolLib.java @@ -37,7 +37,7 @@ import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; import com.comphenix.protocol.updater.Updater; import com.comphenix.protocol.updater.Updater.UpdateType; import com.comphenix.protocol.utility.ChatExtensions; -import com.comphenix.protocol.utility.EnhancerFactory; +import com.comphenix.protocol.utility.ByteBuddyFactory; import com.comphenix.protocol.utility.MinecraftVersion; import com.google.common.base.Splitter; import com.google.common.collect.Iterables; @@ -144,7 +144,7 @@ public class ProtocolLib extends JavaPlugin { ProtocolLogger.init(this); // Initialize enhancer factory - EnhancerFactory.getInstance().setClassLoader(getClassLoader()); + ByteBuddyFactory.getInstance().setClassLoader(getClassLoader()); // Add global parameters DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this); diff --git a/src/main/java/com/comphenix/protocol/events/SerializedOfflinePlayer.java b/src/main/java/com/comphenix/protocol/events/SerializedOfflinePlayer.java index c130da21..618a7225 100644 --- a/src/main/java/com/comphenix/protocol/events/SerializedOfflinePlayer.java +++ b/src/main/java/com/comphenix/protocol/events/SerializedOfflinePlayer.java @@ -21,20 +21,32 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; +import net.bytebuddy.description.ByteCodeElement; +import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; +import net.bytebuddy.implementation.FieldAccessor; +import net.bytebuddy.implementation.InvocationHandlerAdapter; +import net.bytebuddy.implementation.MethodCall; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.implementation.bind.annotation.FieldValue; +import net.bytebuddy.implementation.bind.annotation.Pipe; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; import org.bukkit.*; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; -import com.comphenix.protocol.utility.EnhancerFactory; +import com.comphenix.protocol.utility.ByteBuddyFactory; /** * Represents a player object that can be serialized by Java. @@ -60,9 +72,8 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable { private boolean playedBefore; private boolean online; private boolean whitelisted; - - // Proxy helper - private static Map lookup = new ConcurrentHashMap(); + + private static final Constructor proxyPlayerConstructor = setupProxyPlayerConstructor(); /** * Constructor used by serialization. @@ -243,42 +254,78 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable { } /** - * Retrieve a player object that implements OfflinePlayer by refering to this object. + * Retrieve a player object that implements OfflinePlayer by referring to this object. *

* All other methods cause an exception. * @return Proxy object. */ public Player getProxyPlayer() { - - // Remember to initialize the method filter - if (lookup.size() == 0) { - // Add all public methods - for (Method method : OfflinePlayer.class.getMethods()) { - lookup.put(method.getName(), method); - } + try { + return (Player) proxyPlayerConstructor.newInstance(this); + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot access reflection.", e); + } catch (InstantiationException e) { + throw new RuntimeException("Cannot instantiate object.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Error in invocation.", e); } - - // MORE CGLIB magic! - Enhancer ex = EnhancerFactory.getInstance().createEnhancer(); - ex.setSuperclass(Player.class); - ex.setCallback(new MethodInterceptor() { - @Override - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { - - // There's no overloaded methods, so we don't care - Method offlineMethod = lookup.get(method.getName()); - - // Ignore all other methods - if (offlineMethod == null) { - throw new UnsupportedOperationException( - "The method " + method.getName() + " is not supported for offline players."); - } + } - // Invoke our on method - return offlineMethod.invoke(SerializedOfflinePlayer.this, args); - } - }); - - return (Player) ex.create(); + private static Constructor setupProxyPlayerConstructor() + { + final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods(); + final String[] methodNames = new String[offlinePlayerMethods.length]; + for (int idx = 0; idx < offlinePlayerMethods.length; ++idx) + methodNames[idx] = offlinePlayerMethods[idx].getName(); + + final ElementMatcher.Junction forwardedMethods = ElementMatchers.namedOneOf(methodNames); + + try { + final MethodDelegation forwarding = MethodDelegation.withDefaultConfiguration() + .withBinders(Pipe.Binder.install(Function.class)) + .to(new Object() { + @RuntimeType + public Object intercept(@Pipe Function pipe, + @FieldValue("offlinePlayer") OfflinePlayer proxy) { + return pipe.apply(proxy); + } + }); + + final InvocationHandlerAdapter throwException = InvocationHandlerAdapter.of((obj, method, args) -> { + throw new UnsupportedOperationException( + "The method " + method.getName() + " is not supported for offline players."); + }); + + return ByteBuddyFactory.getInstance() + .createSubclass(PlayerUnion.class, ConstructorStrategy.Default.NO_CONSTRUCTORS) + .name(SerializedOfflinePlayer.class.getPackage().getName() + ".PlayerInvocationHandler") + + .defineField("offlinePlayer", OfflinePlayer.class, Visibility.PRIVATE) + .defineConstructor(Visibility.PUBLIC) + .withParameters(OfflinePlayer.class) + .intercept(MethodCall.invoke(Object.class.getDeclaredConstructor()) + .andThen(FieldAccessor.ofField("offlinePlayer").setsArgumentAt(0))) + + .method(forwardedMethods) + .intercept(forwarding) + + .method(ElementMatchers.not(forwardedMethods)) + .intercept(throwException) + + .make() + .load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION) + .getLoaded() + .getDeclaredConstructor(OfflinePlayer.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Failed to find Player constructor!", e); + } + } + + /** + * This interface extends both OfflinePlayer and Player (in that order) so that the class generated by ByteBuddy + * looks at OfflinePlayer's methods first while still being a Player. + */ + private interface PlayerUnion extends OfflinePlayer, Player + { } } diff --git a/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java index 0a51184f..c12b1a7e 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java @@ -30,6 +30,8 @@ import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.utility.ByteBuddyFactory; +import com.comphenix.protocol.utility.ByteBuddyGenerated; import com.comphenix.protocol.utility.MinecraftFields; import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.utility.MinecraftProtocolVersion; @@ -37,7 +39,9 @@ import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.ObjectReconstructor; import com.comphenix.protocol.wrappers.Pair; import com.comphenix.protocol.wrappers.WrappedGameProfile; + import com.google.common.base.Preconditions; + import io.netty.buffer.ByteBuf; import io.netty.channel.*; import io.netty.channel.socket.SocketChannel; @@ -45,7 +49,7 @@ import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.MessageToByteEncoder; import io.netty.util.AttributeKey; import io.netty.util.internal.TypeParameterMatcher; -import net.sf.cglib.proxy.Factory; + import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -224,7 +228,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector { synchronized (networkManager) { if (closed) return false; - if (originalChannel instanceof Factory) + if (originalChannel instanceof ByteBuddyFactory) return false; if (!originalChannel.isActive()) return false; @@ -703,7 +707,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector { */ private void disconnect(String message) { // If we're logging in, we can only close the channel - if (playerConnection == null || player instanceof Factory) { + if (playerConnection == null || player instanceof ByteBuddyGenerated) { originalChannel.disconnect(); } else { // Call the disconnect method @@ -736,7 +740,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector { // Attempt to send the packet with NetworkMarker.handle(), or the PlayerConnection if its active try { - if (player instanceof Factory) { + if (player instanceof ByteBuddyGenerated) { MinecraftMethods.getNetworkManagerHandleMethod().invoke(networkManager, packet); } else { MinecraftMethods.getSendPacketMethod().invoke(getPlayerConnection(), packet); diff --git a/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java b/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java index 052797f8..fc95f834 100644 --- a/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java +++ b/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java @@ -17,15 +17,25 @@ package com.comphenix.protocol.injector.server; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import net.sf.cglib.proxy.Callback; -import net.sf.cglib.proxy.CallbackFilter; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; -import net.sf.cglib.proxy.NoOp; +import com.comphenix.protocol.utility.ByteBuddyFactory; +import net.bytebuddy.description.ByteCodeElement; +import net.bytebuddy.description.modifier.Visibility; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; +import net.bytebuddy.implementation.FieldAccessor; +import net.bytebuddy.implementation.MethodCall; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.FieldValue; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.implementation.bind.annotation.This; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; import org.bukkit.Server; import org.bukkit.entity.Player; @@ -38,8 +48,7 @@ import com.comphenix.protocol.utility.ChatExtensions; * Create fake player instances that represents pre-authenticated clients. */ public class TemporaryPlayerFactory { - // Prevent too many class creations - private static CallbackFilter callbackFilter; + private static final Constructor temporaryPlayerConstructor = setupProxyPlayerConstructor(); /** * Retrieve the injector from a given player if it contains one. @@ -82,21 +91,34 @@ public class TemporaryPlayerFactory { * @return A temporary player instance. */ public Player createTemporaryPlayer(final Server server) { - - // Default implementation - Callback implementation = new MethodInterceptor() { - @Override - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + try { + return temporaryPlayerConstructor.newInstance(server); + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot access reflection.", e); + } catch (InstantiationException e) { + throw new RuntimeException("Cannot instantiate object.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Error in invocation.", e); + } + } + + private static Constructor setupProxyPlayerConstructor() + { + final MethodDelegation implementation = MethodDelegation.to(new Object() { + @RuntimeType + public Object delegate(@This Object obj, @Origin Method method, @FieldValue("server") Server server, + @AllArguments Object... args) throws Throwable { + String methodName = method.getName(); SocketInjector injector = ((TemporaryPlayer) obj).getInjector(); - + if (injector == null) throw new IllegalStateException("Unable to find injector."); // Use the socket to get the address else if (methodName.equals("getPlayer")) return injector.getUpdatedPlayer(); - else if (methodName.equals("getAddress")) + else if (methodName.equals("getAddress")) return injector.getAddress(); else if (methodName.equals("getServer")) return server; @@ -104,70 +126,71 @@ public class TemporaryPlayerFactory { // Handle send message methods if (methodName.equals("chat") || methodName.equals("sendMessage")) { try { - Object argument = args[0]; - - // Dynamic overloading - if (argument instanceof String) { - return sendMessage(injector, (String) argument); - } else if (argument instanceof String[]) { - for (String message : (String[]) argument) { - sendMessage(injector, message); + Object argument = args[0]; + + // Dynamic overloading + if (argument instanceof String) { + return sendMessage(injector, (String) argument); + } else if (argument instanceof String[]) { + for (String message : (String[]) argument) { + sendMessage(injector, message); + } + return null; } - return null; - } } catch (InvocationTargetException e) { throw e.getCause(); } } - + // Also, handle kicking if (methodName.equals("kickPlayer")) { injector.disconnect((String) args[0]); return null; } - + // The fallback instance Player updated = injector.getUpdatedPlayer(); - + if (updated != obj && updated != null) { - return proxy.invoke(updated, args); + return method.invoke(updated, args); } - + // Methods that are supported in the fallback instance if (methodName.equals("isOnline")) return injector.getSocket() != null && injector.getSocket().isConnected(); else if (methodName.equals("getName")) return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]"; - + // Ignore all other methods throw new UnsupportedOperationException( "The method " + method.getName() + " is not supported for temporary players."); } - }; - - // Shared callback filter - if (callbackFilter == null) { - callbackFilter = new CallbackFilter() { - @Override - public int accept(Method method) { - // Do not override the object method or the superclass methods - if (method.getDeclaringClass().equals(Object.class) || - method.getDeclaringClass().equals(TemporaryPlayer.class)) - return 0; - else - return 1; - } - }; + }); + + final ElementMatcher.Junction callbackFilter = ElementMatchers.not( + ElementMatchers.isDeclaredBy(Object.class).or(ElementMatchers.isDeclaredBy(TemporaryPlayer.class))); + + try { + final Constructor constructor = ByteBuddyFactory.getInstance() + .createSubclass(TemporaryPlayer.class, ConstructorStrategy.Default.NO_CONSTRUCTORS) + .name(TemporaryPlayerFactory.class.getPackage().getName() + ".TemporaryPlayerInvocationHandler") + .implement(Player.class) + + .defineField("server", Server.class, Visibility.PRIVATE) + .defineConstructor(Visibility.PUBLIC) + .withParameters(Server.class) + .intercept(MethodCall.invoke(TemporaryPlayer.class.getDeclaredConstructor()) + .andThen(FieldAccessor.ofField("server").setsArgumentAt(0))) + .method(callbackFilter) + .intercept(implementation) + .make() + .load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION) + .getLoaded() + .getDeclaredConstructor(Server.class); + return (Constructor) constructor; + } catch (NoSuchMethodException e) { + throw new RuntimeException("Failed to find Temporary Player constructor!", e); } - - // CGLib is amazing - Enhancer ex = new Enhancer(); - ex.setSuperclass(TemporaryPlayer.class); - ex.setInterfaces(new Class[] { Player.class }); - ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation }); - ex.setCallbackFilter(callbackFilter); - - return (Player) ex.create(); } /** @@ -191,7 +214,7 @@ public class TemporaryPlayerFactory { * @throws InvocationTargetException If the message couldn't be sent. * @throws FieldAccessException If we were unable to construct the message packet. */ - private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException { + private static Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException { for (PacketContainer packet : ChatExtensions.createChatPackets(message)) { injector.sendServerPacket(packet.getHandle(), null, false); } diff --git a/src/main/java/com/comphenix/protocol/reflect/ClassAnalyser.java b/src/main/java/com/comphenix/protocol/reflect/ClassAnalyser.java index ab95602e..975f83c0 100644 --- a/src/main/java/com/comphenix/protocol/reflect/ClassAnalyser.java +++ b/src/main/java/com/comphenix/protocol/reflect/ClassAnalyser.java @@ -6,8 +6,11 @@ import java.util.List; import com.comphenix.protocol.reflect.ClassAnalyser.AsmMethod.AsmOpcodes; import com.google.common.collect.Lists; - -import net.sf.cglib.asm.*; +import net.bytebuddy.jar.asm.ClassReader; +import net.bytebuddy.jar.asm.ClassVisitor; +import net.bytebuddy.jar.asm.MethodVisitor; +import net.bytebuddy.jar.asm.Opcodes; +import net.bytebuddy.jar.asm.Type; public class ClassAnalyser { /** @@ -26,11 +29,11 @@ public class ClassAnalyser { public static AsmOpcodes fromIntOpcode(int opcode) { switch (opcode) { - case $Opcodes.INVOKEVIRTUAL: return AsmOpcodes.INVOKE_VIRTUAL; - case $Opcodes.INVOKESPECIAL: return AsmOpcodes.INVOKE_SPECIAL; - case $Opcodes.INVOKESTATIC: return AsmOpcodes.INVOKE_STATIC; - case $Opcodes.INVOKEINTERFACE: return AsmOpcodes.INVOKE_INTERFACE; - case $Opcodes.INVOKEDYNAMIC: return AsmOpcodes.INVOKE_DYNAMIC; + case Opcodes.INVOKEVIRTUAL: return AsmOpcodes.INVOKE_VIRTUAL; + case Opcodes.INVOKESPECIAL: return AsmOpcodes.INVOKE_SPECIAL; + case Opcodes.INVOKESTATIC: return AsmOpcodes.INVOKE_STATIC; + case Opcodes.INVOKEINTERFACE: return AsmOpcodes.INVOKE_INTERFACE; + case Opcodes.INVOKEDYNAMIC: return AsmOpcodes.INVOKE_DYNAMIC; default: throw new IllegalArgumentException("Unknown opcode: " + opcode); } } @@ -105,18 +108,18 @@ public class ClassAnalyser { * @throws IOException Cannot access the parent class. */ private List getMethodCalls(Class clazz, Method method) throws IOException { - final $ClassReader reader = new $ClassReader(clazz.getCanonicalName()); + final ClassReader reader = new ClassReader(clazz.getCanonicalName()); final List output = Lists.newArrayList(); // The method we are looking for final String methodName = method.getName(); - final String methodDescription = $Type.getMethodDescriptor(method); + final String methodDescription = Type.getMethodDescriptor(method); - reader.accept(new $ClassVisitor($Opcodes.ASM5) { + reader.accept(new ClassVisitor(Opcodes.ASM5) { @Override - public $MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (methodName.equals(name) && methodDescription.equals(desc)) { - return new $MethodVisitor($Opcodes.ASM5) { + return new MethodVisitor(Opcodes.ASM5) { @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean flag) { output.add(new AsmMethod(AsmOpcodes.fromIntOpcode(opcode), owner, methodName, desc)); @@ -126,7 +129,7 @@ public class ClassAnalyser { return null; } - }, $ClassReader.EXPAND_FRAMES); + }, ClassReader.EXPAND_FRAMES); return output; } } diff --git a/src/main/java/com/comphenix/protocol/reflect/compiler/BoxingHelper.java b/src/main/java/com/comphenix/protocol/reflect/compiler/BoxingHelper.java index 7d5938f9..5b52cbea 100644 --- a/src/main/java/com/comphenix/protocol/reflect/compiler/BoxingHelper.java +++ b/src/main/java/com/comphenix/protocol/reflect/compiler/BoxingHelper.java @@ -17,25 +17,25 @@ package com.comphenix.protocol.reflect.compiler; -import net.sf.cglib.asm.$MethodVisitor; -import net.sf.cglib.asm.$Opcodes; -import net.sf.cglib.asm.$Type; +import net.bytebuddy.jar.asm.MethodVisitor; +import net.bytebuddy.jar.asm.Opcodes; +import net.bytebuddy.jar.asm.Type; /** * Used by the compiler to automatically box and unbox values. */ class BoxingHelper { - private final static $Type BYTE_$Type = $Type.getObjectType("java/lang/Byte"); - private final static $Type BOOLEAN_$Type = $Type.getObjectType("java/lang/Boolean"); - private final static $Type SHORT_$Type = $Type.getObjectType("java/lang/Short"); - private final static $Type CHARACTER_$Type = $Type.getObjectType("java/lang/Character"); - private final static $Type INTEGER_$Type = $Type.getObjectType("java/lang/Integer"); - private final static $Type FLOAT_$Type = $Type.getObjectType("java/lang/Float"); - private final static $Type LONG_$Type = $Type.getObjectType("java/lang/Long"); - private final static $Type DOUBLE_$Type = $Type.getObjectType("java/lang/Double"); - private final static $Type NUMBER_$Type = $Type.getObjectType("java/lang/Number"); - private final static $Type OBJECT_$Type = $Type.getObjectType("java/lang/Object"); + private final static Type BYTE_Type = Type.getObjectType("java/lang/Byte"); + private final static Type BOOLEAN_Type = Type.getObjectType("java/lang/Boolean"); + private final static Type SHORT_Type = Type.getObjectType("java/lang/Short"); + private final static Type CHARACTER_Type = Type.getObjectType("java/lang/Character"); + private final static Type INTEGER_Type = Type.getObjectType("java/lang/Integer"); + private final static Type FLOAT_Type = Type.getObjectType("java/lang/Float"); + private final static Type LONG_Type = Type.getObjectType("java/lang/Long"); + private final static Type DOUBLE_Type = Type.getObjectType("java/lang/Double"); + private final static Type NUMBER_Type = Type.getObjectType("java/lang/Number"); + private final static Type OBJECT_Type = Type.getObjectType("java/lang/Object"); private final static MethodDescriptor BOOLEAN_VALUE = MethodDescriptor.getMethod("boolean booleanValue()"); private final static MethodDescriptor CHAR_VALUE = MethodDescriptor.getMethod("char charValue()"); @@ -44,9 +44,9 @@ class BoxingHelper { private final static MethodDescriptor LONG_VALUE = MethodDescriptor.getMethod("long longValue()"); private final static MethodDescriptor DOUBLE_VALUE = MethodDescriptor.getMethod("double doubleValue()"); - private $MethodVisitor mv; + private MethodVisitor mv; - public BoxingHelper($MethodVisitor mv) { + public BoxingHelper(MethodVisitor mv) { this.mv = mv; } @@ -54,42 +54,42 @@ class BoxingHelper { * Generates the instructions to box the top stack value. This value is * replaced by its boxed equivalent on top of the stack. * - * @param type the $Type of the top stack value. + * @param type the Type of the top stack value. */ - public void box(final $Type type){ - if(type.getSort() == $Type.OBJECT || type.getSort() == $Type.ARRAY) { + public void box(final Type type){ + if(type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { return; } - if(type == $Type.VOID_TYPE) { + if(type == Type.VOID_TYPE) { push((String) null); } else { - $Type boxed = type; + Type boxed = type; switch(type.getSort()) { - case $Type.BYTE: - boxed = BYTE_$Type; + case Type.BYTE: + boxed = BYTE_Type; break; - case $Type.BOOLEAN: - boxed = BOOLEAN_$Type; + case Type.BOOLEAN: + boxed = BOOLEAN_Type; break; - case $Type.SHORT: - boxed = SHORT_$Type; + case Type.SHORT: + boxed = SHORT_Type; break; - case $Type.CHAR: - boxed = CHARACTER_$Type; + case Type.CHAR: + boxed = CHARACTER_Type; break; - case $Type.INT: - boxed = INTEGER_$Type; + case Type.INT: + boxed = INTEGER_Type; break; - case $Type.FLOAT: - boxed = FLOAT_$Type; + case Type.FLOAT: + boxed = FLOAT_Type; break; - case $Type.LONG: - boxed = LONG_$Type; + case Type.LONG: + boxed = LONG_Type; break; - case $Type.DOUBLE: - boxed = DOUBLE_$Type; + case Type.DOUBLE: + boxed = DOUBLE_Type; break; } @@ -105,46 +105,46 @@ class BoxingHelper { swap(); } - invokeConstructor(boxed, new MethodDescriptor("", $Type.VOID_TYPE, new $Type[] {type})); + invokeConstructor(boxed, new MethodDescriptor("", Type.VOID_TYPE, new Type[] {type})); } } /** * Generates the instruction to invoke a constructor. * - * @param $Type the class in which the constructor is defined. + * @param Type the class in which the constructor is defined. * @param method the constructor to be invoked. */ - public void invokeConstructor(final $Type $Type, final MethodDescriptor method){ - invokeInsn($Opcodes.INVOKESPECIAL, $Type, method); + public void invokeConstructor(final Type Type, final MethodDescriptor method){ + invokeInsn(Opcodes.INVOKESPECIAL, Type, method); } /** * Generates a DUP_X1 instruction. */ public void dupX1(){ - mv.visitInsn($Opcodes.DUP_X1); + mv.visitInsn(Opcodes.DUP_X1); } /** * Generates a DUP_X2 instruction. */ public void dupX2(){ - mv.visitInsn($Opcodes.DUP_X2); + mv.visitInsn(Opcodes.DUP_X2); } /** * Generates a POP instruction. */ public void pop(){ - mv.visitInsn($Opcodes.POP); + mv.visitInsn(Opcodes.POP); } /** * Generates a SWAP instruction. */ public void swap(){ - mv.visitInsn($Opcodes.SWAP); + mv.visitInsn(Opcodes.SWAP); } /** @@ -163,11 +163,11 @@ class BoxingHelper { */ public void push(final int value) { if (value >= -1 && value <= 5) { - mv.visitInsn($Opcodes.ICONST_0 + value); + mv.visitInsn(Opcodes.ICONST_0 + value); } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { - mv.visitIntInsn($Opcodes.BIPUSH, value); + mv.visitIntInsn(Opcodes.BIPUSH, value); } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { - mv.visitIntInsn($Opcodes.SIPUSH, value); + mv.visitIntInsn(Opcodes.SIPUSH, value); } else { mv.visitLdcInsn(new Integer(value)); } @@ -176,10 +176,10 @@ class BoxingHelper { /** * Generates the instruction to create a new object. * - * @param $Type the class of the object to be created. + * @param Type the class of the object to be created. */ - public void newInstance(final $Type $Type){ - $TypeInsn($Opcodes.NEW, $Type); + public void newInstance(final Type Type){ + TypeInsn(Opcodes.NEW, Type); } /** @@ -189,7 +189,7 @@ class BoxingHelper { */ public void push(final String value) { if (value == null) { - mv.visitInsn($Opcodes.ACONST_NULL); + mv.visitInsn(Opcodes.ACONST_NULL); } else { mv.visitLdcInsn(value); } @@ -200,35 +200,35 @@ class BoxingHelper { * replaced by its unboxed equivalent on top of the stack. * * @param type - * the $Type of the top stack value. + * the Type of the top stack value. */ - public void unbox(final $Type type){ - $Type t = NUMBER_$Type; + public void unbox(final Type type){ + Type t = NUMBER_Type; MethodDescriptor sig = null; switch(type.getSort()) { - case $Type.VOID: + case Type.VOID: return; - case $Type.CHAR: - t = CHARACTER_$Type; + case Type.CHAR: + t = CHARACTER_Type; sig = CHAR_VALUE; break; - case $Type.BOOLEAN: - t = BOOLEAN_$Type; + case Type.BOOLEAN: + t = BOOLEAN_Type; sig = BOOLEAN_VALUE; break; - case $Type.DOUBLE: + case Type.DOUBLE: sig = DOUBLE_VALUE; break; - case $Type.FLOAT: + case Type.FLOAT: sig = FLOAT_VALUE; break; - case $Type.LONG: + case Type.LONG: sig = LONG_VALUE; break; - case $Type.INT: - case $Type.SHORT: - case $Type.BYTE: + case Type.INT: + case Type.SHORT: + case Type.BYTE: sig = INT_VALUE; } @@ -242,13 +242,13 @@ class BoxingHelper { /** * Generates the instruction to check that the top stack value is of the - * given $Type. + * given Type. * - * @param $Type a class or interface $Type. + * @param Type a class or interface Type. */ - public void checkCast(final $Type $Type){ - if(!$Type.equals(OBJECT_$Type)) { - $TypeInsn($Opcodes.CHECKCAST, $Type); + public void checkCast(final Type Type){ + if(!Type.equals(OBJECT_Type)) { + TypeInsn(Opcodes.CHECKCAST, Type); } } @@ -258,35 +258,35 @@ class BoxingHelper { * @param owner the class in which the method is defined. * @param method the method to be invoked. */ - public void invokeVirtual(final $Type owner, final MethodDescriptor method){ - invokeInsn($Opcodes.INVOKEVIRTUAL, owner, method); + public void invokeVirtual(final Type owner, final MethodDescriptor method){ + invokeInsn(Opcodes.INVOKEVIRTUAL, owner, method); } /** * Generates an invoke method instruction. * * @param opcode the instruction's opcode. - * @param $Type the class in which the method is defined. + * @param type the class in which the method is defined. * @param method the method to be invoked. */ - private void invokeInsn(final int opcode, final $Type $Type, final MethodDescriptor method){ - String owner = $Type.getSort() == $Type.ARRAY ? $Type.getDescriptor() : $Type.getInternalName(); + private void invokeInsn(final int opcode, final Type type, final MethodDescriptor method){ + String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName(); mv.visitMethodInsn(opcode, owner, method.getName(), method.getDescriptor()); } /** - * Generates a $Type dependent instruction. + * Generates a Type dependent instruction. * * @param opcode the instruction's opcode. - * @param $Type the instruction's operand. + * @param type the instruction's operand. */ - private void $TypeInsn(final int opcode, final $Type $Type){ + private void TypeInsn(final int opcode, final Type type){ String desc; - if($Type.getSort() == $Type.ARRAY) { - desc = $Type.getDescriptor(); + if(type.getSort() == Type.ARRAY) { + desc = type.getDescriptor(); } else { - desc = $Type.getInternalName(); + desc = type.getInternalName(); } mv.visitTypeInsn(opcode, desc); diff --git a/src/main/java/com/comphenix/protocol/reflect/compiler/MethodDescriptor.java b/src/main/java/com/comphenix/protocol/reflect/compiler/MethodDescriptor.java index 71a6ffd8..7a82b22c 100644 --- a/src/main/java/com/comphenix/protocol/reflect/compiler/MethodDescriptor.java +++ b/src/main/java/com/comphenix/protocol/reflect/compiler/MethodDescriptor.java @@ -20,7 +20,7 @@ package com.comphenix.protocol.reflect.compiler; import java.util.HashMap; import java.util.Map; -import net.sf.cglib.asm.$Type; +import net.bytebuddy.jar.asm.Type; /** * Represents a method. @@ -75,10 +75,10 @@ class MethodDescriptor { */ public MethodDescriptor( final String name, - final $Type returnType, - final $Type[] argumentTypes) + final Type returnType, + final Type[] argumentTypes) { - this(name, $Type.getMethodDescriptor(returnType, argumentTypes)); + this(name, Type.getMethodDescriptor(returnType, argumentTypes)); } /** @@ -206,8 +206,8 @@ class MethodDescriptor { * * @return the return type of the method described by this object. */ - public $Type getReturnType() { - return $Type.getReturnType(desc); + public Type getReturnType() { + return Type.getReturnType(desc); } /** @@ -215,8 +215,8 @@ class MethodDescriptor { * * @return the argument types of the method described by this object. */ - public $Type[] getArgumentTypes() { - return $Type.getArgumentTypes(desc); + public Type[] getArgumentTypes() { + return Type.getArgumentTypes(desc); } public String toString() { diff --git a/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java b/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java index 5f9af5e6..9ead20b2 100644 --- a/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java +++ b/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java @@ -25,19 +25,13 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import net.sf.cglib.asm.$ClassWriter; -import net.sf.cglib.asm.$FieldVisitor; -import net.sf.cglib.asm.$Label; -import net.sf.cglib.asm.$MethodVisitor; -import net.sf.cglib.asm.$Opcodes; -import net.sf.cglib.asm.$Type; - import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.reflect.StructureModifier; import com.google.common.base.Objects; import com.google.common.primitives.Primitives; +import net.bytebuddy.jar.asm.*; // public class CompiledStructureModifierPacket20 extends CompiledStructureModifier { // @@ -276,15 +270,15 @@ public final class StructureCompiler { */ private Class generateClass(StructureModifier source) { - $ClassWriter cw = new $ClassWriter(0); + ClassWriter cw = new ClassWriter(0); Class targetType = source.getTargetType(); String className = getCompiledName(source); - String targetSignature = $Type.getDescriptor(targetType); + String targetSignature = Type.getDescriptor(targetType); String targetName = targetType.getName().replace('.', '/'); // Define class - cw.visit($Opcodes.V1_6, $Opcodes.ACC_PUBLIC + $Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className, + cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className, null, COMPILED_CLASS, null); createFields(cw, targetSignature); @@ -352,35 +346,35 @@ public final class StructureCompiler { return !Modifier.isFinal(field.getModifiers()); } - private void createFields($ClassWriter cw, String targetSignature) { - $FieldVisitor typedField = cw.visitField($Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null); + private void createFields(ClassWriter cw, String targetSignature) { + FieldVisitor typedField = cw.visitField(Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null); typedField.visitEnd(); } - private void createWriteMethod($ClassWriter cw, String className, List fields, String targetSignature, String targetName) { + private void createWriteMethod(ClassWriter cw, String className, List fields, String targetSignature, String targetName) { String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";"; String methodSignature = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";"; - $MethodVisitor mv = cw.visitMethod($Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature, + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature, new String[] { FIELD_EXCEPTION_CLASS }); BoxingHelper boxingHelper = new BoxingHelper(mv); String generatedClassName = PACKAGE_NAME + "/" + className; mv.visitCode(); - mv.visitVarInsn($Opcodes.ALOAD, 0); - mv.visitFieldInsn($Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature); - mv.visitVarInsn($Opcodes.ASTORE, 3); - mv.visitVarInsn($Opcodes.ILOAD, 1); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature); + mv.visitVarInsn(Opcodes.ASTORE, 3); + mv.visitVarInsn(Opcodes.ILOAD, 1); // The last $Label is for the default switch - $Label[] $Labels = new $Label[fields.size()]; - $Label error$Label = new $Label(); - $Label return$Label = new $Label(); + Label[] $Labels = new Label[fields.size()]; + Label error$Label = new Label(); + Label return$Label = new Label(); // Generate $Labels for (int i = 0; i < fields.size(); i++) { - $Labels[i] = new $Label(); + $Labels[i] = new Label(); } mv.visitTableSwitchInsn(0, $Labels.length - 1, error$Label, $Labels); @@ -390,82 +384,82 @@ public final class StructureCompiler { Field field = fields.get(i); Class outputType = field.getType(); Class inputType = Primitives.wrap(outputType); - String typeDescriptor = $Type.getDescriptor(outputType); + String typeDescriptor = Type.getDescriptor(outputType); String inputPath = inputType.getName().replace('.', '/'); mv.visitLabel($Labels[i]); // Push the compare object if (i == 0) - mv.visitFrame($Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null); + mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null); else - mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); // Only write to public non-final fields if (isPublic(field) && isNonFinal(field)) { - mv.visitVarInsn($Opcodes.ALOAD, 3); - mv.visitVarInsn($Opcodes.ALOAD, 2); + mv.visitVarInsn(Opcodes.ALOAD, 3); + mv.visitVarInsn(Opcodes.ALOAD, 2); if (!outputType.isPrimitive()) - mv.visitTypeInsn($Opcodes.CHECKCAST, inputPath); + mv.visitTypeInsn(Opcodes.CHECKCAST, inputPath); else - boxingHelper.unbox($Type.getType(outputType)); + boxingHelper.unbox(Type.getType(outputType)); - mv.visitFieldInsn($Opcodes.PUTFIELD, targetName, field.getName(), typeDescriptor); + mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, field.getName(), typeDescriptor); } else { // Use reflection. We don't have a choice, unfortunately. - mv.visitVarInsn($Opcodes.ALOAD, 0); - mv.visitVarInsn($Opcodes.ILOAD, 1); - mv.visitVarInsn($Opcodes.ALOAD, 2); - mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, generatedClassName, "writeReflected", "(ILjava/lang/Object;)V"); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ILOAD, 1); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedClassName, "writeReflected", "(ILjava/lang/Object;)V"); } - mv.visitJumpInsn($Opcodes.GOTO, return$Label); + mv.visitJumpInsn(Opcodes.GOTO, return$Label); } mv.visitLabel(error$Label); - mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null); - mv.visitTypeInsn($Opcodes.NEW, FIELD_EXCEPTION_CLASS); - mv.visitInsn($Opcodes.DUP); - mv.visitTypeInsn($Opcodes.NEW, "java/lang/StringBuilder"); - mv.visitInsn($Opcodes.DUP); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS); + mv.visitInsn(Opcodes.DUP); + mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); + mv.visitInsn(Opcodes.DUP); mv.visitLdcInsn("Invalid index "); - mv.visitMethodInsn($Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V"); - mv.visitVarInsn($Opcodes.ILOAD, 1); - mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;"); - mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); - mv.visitMethodInsn($Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "", "(Ljava/lang/String;)V"); - mv.visitInsn($Opcodes.ATHROW); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V"); + mv.visitVarInsn(Opcodes.ILOAD, 1); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;"); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "", "(Ljava/lang/String;)V"); + mv.visitInsn(Opcodes.ATHROW); mv.visitLabel(return$Label); - mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null); - mv.visitVarInsn($Opcodes.ALOAD, 0); - mv.visitInsn($Opcodes.ARETURN); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitInsn(Opcodes.ARETURN); mv.visitMaxs(5, 4); mv.visitEnd(); } - private void createReadMethod($ClassWriter cw, String className, List fields, String targetSignature, String targetName) { - $MethodVisitor mv = cw.visitMethod($Opcodes.ACC_PROTECTED, "readGenerated", "(I)Ljava/lang/Object;", null, + private void createReadMethod(ClassWriter cw, String className, List fields, String targetSignature, String targetName) { + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "readGenerated", "(I)Ljava/lang/Object;", null, new String[] { "com/comphenix/protocol/reflect/FieldAccessException" }); BoxingHelper boxingHelper = new BoxingHelper(mv); String generatedClassName = PACKAGE_NAME + "/" + className; mv.visitCode(); - mv.visitVarInsn($Opcodes.ALOAD, 0); - mv.visitFieldInsn($Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature); - mv.visitVarInsn($Opcodes.ASTORE, 2); - mv.visitVarInsn($Opcodes.ILOAD, 1); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature); + mv.visitVarInsn(Opcodes.ASTORE, 2); + mv.visitVarInsn(Opcodes.ILOAD, 1); // The last $Label is for the default switch - $Label[] $Labels = new $Label[fields.size()]; - $Label error$Label = new $Label(); + Label[] $Labels = new Label[fields.size()]; + Label error$Label = new Label(); // Generate $Labels for (int i = 0; i < fields.size(); i++) { - $Labels[i] = new $Label(); + $Labels[i] = new Label(); } mv.visitTableSwitchInsn(0, fields.size() - 1, error$Label, $Labels); @@ -474,74 +468,74 @@ public final class StructureCompiler { Field field = fields.get(i); Class outputType = field.getType(); - String typeDescriptor = $Type.getDescriptor(outputType); + String typeDescriptor = Type.getDescriptor(outputType); mv.visitLabel($Labels[i]); // Push the compare object if (i == 0) - mv.visitFrame($Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null); + mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null); else - mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); // Note that byte code cannot access non-public fields if (isPublic(field)) { - mv.visitVarInsn($Opcodes.ALOAD, 2); - mv.visitFieldInsn($Opcodes.GETFIELD, targetName, field.getName(), typeDescriptor); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitFieldInsn(Opcodes.GETFIELD, targetName, field.getName(), typeDescriptor); - boxingHelper.box($Type.getType(outputType)); + boxingHelper.box(Type.getType(outputType)); } else { // We have to use reflection for private and protected fields. - mv.visitVarInsn($Opcodes.ALOAD, 0); - mv.visitVarInsn($Opcodes.ILOAD, 1); - mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, generatedClassName, "readReflected", "(I)Ljava/lang/Object;"); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ILOAD, 1); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedClassName, "readReflected", "(I)Ljava/lang/Object;"); } - mv.visitInsn($Opcodes.ARETURN); + mv.visitInsn(Opcodes.ARETURN); } mv.visitLabel(error$Label); - mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null); - mv.visitTypeInsn($Opcodes.NEW, FIELD_EXCEPTION_CLASS); - mv.visitInsn($Opcodes.DUP); - mv.visitTypeInsn($Opcodes.NEW, "java/lang/StringBuilder"); - mv.visitInsn($Opcodes.DUP); + mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS); + mv.visitInsn(Opcodes.DUP); + mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); + mv.visitInsn(Opcodes.DUP); mv.visitLdcInsn("Invalid index "); - mv.visitMethodInsn($Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V"); - mv.visitVarInsn($Opcodes.ILOAD, 1); - mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;"); - mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); - mv.visitMethodInsn($Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "", "(Ljava/lang/String;)V"); - mv.visitInsn($Opcodes.ATHROW); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "(Ljava/lang/String;)V"); + mv.visitVarInsn(Opcodes.ILOAD, 1); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;"); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "", "(Ljava/lang/String;)V"); + mv.visitInsn(Opcodes.ATHROW); mv.visitMaxs(5, 3); mv.visitEnd(); } - private void createConstructor($ClassWriter cw, String className, String targetSignature, String targetName) { - $MethodVisitor mv = cw.visitMethod($Opcodes.ACC_PUBLIC, "", + private void createConstructor(ClassWriter cw, String className, String targetSignature, String targetName) { + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "", "(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V", "(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V", null); String fullClassName = PACKAGE_NAME + "/" + className; mv.visitCode(); - mv.visitVarInsn($Opcodes.ALOAD, 0); - mv.visitMethodInsn($Opcodes.INVOKESPECIAL, COMPILED_CLASS, "", "()V"); - mv.visitVarInsn($Opcodes.ALOAD, 0); - mv.visitVarInsn($Opcodes.ALOAD, 1); - mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, fullClassName, "initialize", "(L" + SUPER_CLASS + ";)V"); - mv.visitVarInsn($Opcodes.ALOAD, 0); - mv.visitVarInsn($Opcodes.ALOAD, 1); - mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;"); - mv.visitFieldInsn($Opcodes.PUTFIELD, fullClassName, "target", "Ljava/lang/Object;"); - mv.visitVarInsn($Opcodes.ALOAD, 0); - mv.visitVarInsn($Opcodes.ALOAD, 0); - mv.visitFieldInsn($Opcodes.GETFIELD, fullClassName, "target", "Ljava/lang/Object;"); - mv.visitTypeInsn($Opcodes.CHECKCAST, targetName); - mv.visitFieldInsn($Opcodes.PUTFIELD, fullClassName, "typedTarget", targetSignature); - mv.visitVarInsn($Opcodes.ALOAD, 0); - mv.visitVarInsn($Opcodes.ALOAD, 2); - mv.visitFieldInsn($Opcodes.PUTFIELD, fullClassName, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;"); - mv.visitInsn($Opcodes.RETURN); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "", "()V"); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, fullClassName, "initialize", "(L" + SUPER_CLASS + ";)V"); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;"); + mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "target", "Ljava/lang/Object;"); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, fullClassName, "target", "Ljava/lang/Object;"); + mv.visitTypeInsn(Opcodes.CHECKCAST, targetName); + mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "typedTarget", targetSignature); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;"); + mv.visitInsn(Opcodes.RETURN); mv.visitMaxs(2, 3); mv.visitEnd(); } diff --git a/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java b/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java index 1a560c7e..88a0b72f 100644 --- a/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java +++ b/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java @@ -25,8 +25,6 @@ import java.util.logging.Level; import javax.annotation.Nullable; -import net.sf.cglib.proxy.Enhancer; - import com.comphenix.protocol.ProtocolLogger; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; @@ -304,24 +302,6 @@ public class DefaultInstances implements InstanceProvider { return null; } - /** - * Construct default instances using the CGLIB enhancer object instead. - * @param enhancer - a CGLIB enhancer to use. - * @return A default instance generator that uses the CGLIB enhancer. - */ - public DefaultInstances forEnhancer(Enhancer enhancer) { - final Enhancer ex = enhancer; - - return new DefaultInstances(this) { - @SuppressWarnings("unchecked") - @Override - protected T createInstance(Class type, Constructor constructor, Class[] types, Object[] params) { - // Use the enhancer instead - return (T) ex.create(types, params); - } - }; - } - /** * Used by the default instance provider to create a class from a given constructor. * The default method uses reflection. diff --git a/src/main/java/com/comphenix/protocol/utility/ByteBuddyFactory.java b/src/main/java/com/comphenix/protocol/utility/ByteBuddyFactory.java new file mode 100644 index 00000000..6d2404eb --- /dev/null +++ b/src/main/java/com/comphenix/protocol/utility/ByteBuddyFactory.java @@ -0,0 +1,65 @@ +package com.comphenix.protocol.utility; + +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; + +/** + * Represents a shared ByteBuddy factory. + * @author Kristian + */ +public class ByteBuddyFactory { + private static ByteBuddyFactory INSTANCE = new ByteBuddyFactory(); + + // The current class loader + private ClassLoader loader = ByteBuddyFactory.class.getClassLoader(); + + public static ByteBuddyFactory getInstance() { + return INSTANCE; + } + + /** + * Set the current class loader to use when constructing enhancers. + * @param loader - the class loader + */ + public void setClassLoader(ClassLoader loader) { + this.loader = loader; + } + + /** + * Get the current class loader we are using. + * @return The current class loader. + */ + public ClassLoader getClassLoader() { + return loader; + } + + /** + * Creates a type builder for a subclass of a given {@link Class}. + * @param clz The class for which to create a subclass. + * @return A type builder for creating a new class extending the provided clz and implementing + * {@link ByteBuddyGenerated}. + */ + public DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional createSubclass(Class clz) + { + return new ByteBuddy() + .subclass(clz) + .implement(ByteBuddyGenerated.class); + } + + /** + * Creates a type builder for a subclass of a given {@link Class}. + * @param clz The class for which to create a subclass. + * @param constructorStrategy The constructor strategy to use. + * @return A type builder for creating a new class extending the provided clz and implementing + * {@link ByteBuddyGenerated}. + */ + public DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional createSubclass(Class clz, + ConstructorStrategy.Default constructorStrategy) + { + return new ByteBuddy() + .subclass(clz, constructorStrategy) + .implement(ByteBuddyGenerated.class); + } +} diff --git a/src/main/java/com/comphenix/protocol/utility/ByteBuddyGenerated.java b/src/main/java/com/comphenix/protocol/utility/ByteBuddyGenerated.java new file mode 100644 index 00000000..b531d306 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/utility/ByteBuddyGenerated.java @@ -0,0 +1,9 @@ +package com.comphenix.protocol.utility; + +/** + * Represents an object that has been generated using ByteBuddy. + * + * @author Pim + */ +public interface ByteBuddyGenerated { +} diff --git a/src/main/java/com/comphenix/protocol/utility/EnhancerFactory.java b/src/main/java/com/comphenix/protocol/utility/EnhancerFactory.java deleted file mode 100644 index c64fb1e8..00000000 --- a/src/main/java/com/comphenix/protocol/utility/EnhancerFactory.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.comphenix.protocol.utility; - -import net.sf.cglib.proxy.Enhancer; - -/** - * Represents a shared enchancer factory. - * @author Kristian - */ -public class EnhancerFactory { - private static EnhancerFactory INSTANCE = new EnhancerFactory(); - - // The current class loader - private ClassLoader loader = EnhancerFactory.class.getClassLoader(); - - public static EnhancerFactory getInstance() { - return INSTANCE; - } - - /** - * Create a new CGLib enhancer. - * @return The new enhancer. - */ - public Enhancer createEnhancer() { - Enhancer enhancer = new Enhancer(); - enhancer.setClassLoader(loader); - return enhancer; - } - - /** - * Set the current class loader to use when constructing enhancers. - * @param loader - the class loader - */ - public void setClassLoader(ClassLoader loader) { - this.loader = loader; - } - - /** - * Get the current class loader we are using. - * @return The current class loader. - */ - public ClassLoader getClassLoader() { - return loader; - } -} diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java b/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java index 677a586b..0c6c3418 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftMethods.java @@ -1,10 +1,12 @@ package com.comphenix.protocol.utility; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.List; import java.util.Map; +import java.util.concurrent.Callable; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.PacketContainer; @@ -14,8 +16,13 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.MethodInterceptor; +import net.bytebuddy.dynamic.DynamicType; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.implementation.bind.annotation.SuperCall; +import net.bytebuddy.matcher.ElementMatchers; /** * Static methods for accessing Minecraft methods. @@ -33,6 +40,8 @@ public class MinecraftMethods { // For packet private volatile static Method packetReadByteBuf; private volatile static Method packetWriteByteBuf; + + private static Constructor proxyConstructor; /** * Retrieve the send packet method in PlayerConnection/NetServerHandler. @@ -166,36 +175,59 @@ public class MinecraftMethods { initializePacket(); return packetWriteByteBuf; } - + + private static Constructor setupProxyConstructor() + { + try { + return ByteBuddyFactory.getInstance() + .createSubclass(MinecraftReflection.getPacketDataSerializerClass()) + .name(MinecraftMethods.class.getPackage().getName() + ".PacketDecorator") + .method(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class))) + .intercept(MethodDelegation.to(new Object() { + @RuntimeType + public Object delegate(@SuperCall Callable zuper, @Origin Method method) throws Exception { + if (method.getName().contains("read")) { + throw new ReadMethodException(); + } + + if (method.getName().contains("write")) { + throw new WriteMethodException(); + } + return zuper.call(); + } + })) + .make() + .load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION) + .getLoaded() + .getDeclaredConstructor(MinecraftReflection.getByteBufClass()); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Failed to find constructor!", e); + } + } + /** * Initialize the two read() and write() methods. */ private static void initializePacket() { + // Initialize the methods if (packetReadByteBuf == null || packetWriteByteBuf == null) { - // This object will allow us to detect which methods were called - Enhancer enhancer = EnhancerFactory.getInstance().createEnhancer(); - enhancer.setSuperclass(MinecraftReflection.getPacketDataSerializerClass()); - enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> { - if (method.getName().contains("read")) { - throw new ReadMethodException(); - } + if (proxyConstructor == null) + proxyConstructor = setupProxyConstructor(); - if (method.getName().contains("write")) { - throw new WriteMethodException(); - } + final Object javaProxy; + try { + javaProxy = proxyConstructor.newInstance(Unpooled.buffer()); + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot access reflection.", e); + } catch (InstantiationException e) { + throw new RuntimeException("Cannot instantiate object.", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Error in invocation.", e); + } - return proxy.invokeSuper(obj, args); - }); - - // Create our proxy object - Object javaProxy = enhancer.create( - new Class[] { MinecraftReflection.getByteBufClass() }, - new Object[] { Unpooled.buffer() } - ); - - Object lookPacket = new PacketContainer(PacketType.Play.Client.CLOSE_WINDOW).getHandle(); - List candidates = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()) + final Object lookPacket = new PacketContainer(PacketType.Play.Client.CLOSE_WINDOW).getHandle(); + final List candidates = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()) .getMethodListByParameters(Void.TYPE, new Class[] { MinecraftReflection.getPacketDataSerializerClass() }); // Look through all the methods diff --git a/src/main/java/com/comphenix/protocol/wrappers/nbt/TileEntityAccessor.java b/src/main/java/com/comphenix/protocol/wrappers/nbt/TileEntityAccessor.java index f62e1314..81c20716 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/nbt/TileEntityAccessor.java +++ b/src/main/java/com/comphenix/protocol/wrappers/nbt/TileEntityAccessor.java @@ -1,6 +1,8 @@ package com.comphenix.protocol.wrappers.nbt; import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.concurrent.ConcurrentMap; @@ -11,18 +13,20 @@ import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; -import com.comphenix.protocol.utility.EnhancerFactory; +import com.comphenix.protocol.utility.ByteBuddyFactory; +import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; + import com.google.common.collect.Maps; -import net.sf.cglib.asm.$ClassReader; -import net.sf.cglib.asm.$ClassVisitor; -import net.sf.cglib.asm.$MethodVisitor; -import net.sf.cglib.asm.$Opcodes; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.implementation.InvocationHandlerAdapter; +import net.bytebuddy.jar.asm.ClassReader; +import net.bytebuddy.jar.asm.ClassVisitor; +import net.bytebuddy.jar.asm.MethodVisitor; +import net.bytebuddy.jar.asm.Opcodes; +import net.bytebuddy.matcher.ElementMatchers; import org.bukkit.block.BlockState; @@ -43,14 +47,12 @@ class TileEntityAccessor { */ private static final ConcurrentMap, TileEntityAccessor> cachedAccessors = Maps.newConcurrentMap(); + private static Constructor nbtCompoundParserConstructor; + private FieldAccessor tileEntityField; private MethodAccessor readCompound; private MethodAccessor writeCompound; - // For CGLib detection - private boolean writeDetected; - private boolean readDetected; - TileEntityAccessor() { // Do nothing } @@ -97,7 +99,7 @@ class TileEntityAccessor { } catch (IOException ex1) { try { // Much slower though - findMethodUsingCGLib(state); + findMethodUsingByteBuddy(state); } catch (Exception ex2) { throw new RuntimeException("Cannot find read/write methods in " + type, ex2); } @@ -117,26 +119,26 @@ class TileEntityAccessor { private void findMethodsUsingASM() throws IOException { final Class nbtCompoundClass = MinecraftReflection.getNBTCompoundClass(); final Class tileEntityClass = MinecraftReflection.getTileEntityClass(); - final $ClassReader reader = new $ClassReader(tileEntityClass.getCanonicalName()); + final ClassReader reader = new ClassReader(tileEntityClass.getCanonicalName()); final String tagCompoundName = getJarName(MinecraftReflection.getNBTCompoundClass()); final String expectedDesc = "(L" + tagCompoundName + ";)"; - reader.accept(new $ClassVisitor($Opcodes.ASM5) { + reader.accept(new ClassVisitor(Opcodes.ASM5) { @Override - public $MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { final String methodName = name; // Detect read/write calls to NBTTagCompound if (desc.startsWith(expectedDesc)) { - return new $MethodVisitor($Opcodes.ASM5) { + return new MethodVisitor(Opcodes.ASM5) { private int readMethods; private int writeMethods; @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean intf) { // This must be a virtual call on NBTTagCompound that accepts a String - if (opcode == $Opcodes.INVOKEVIRTUAL + if (opcode == Opcodes.INVOKEVIRTUAL && tagCompoundName.equals(owner) && desc.startsWith("(Ljava/lang/String")) { @@ -167,47 +169,60 @@ class TileEntityAccessor { }, 0); } + private static Constructor setupNBTCompoundParserConstructor() + { + final Class nbtCompoundClass = MinecraftReflection.getNBTCompoundClass(); + try { + return ByteBuddyFactory.getInstance() + .createSubclass(nbtCompoundClass) + .name(MinecraftMethods.class.getPackage().getName() + ".NBTInvocationHandler") + .method(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class))) + .intercept(InvocationHandlerAdapter.of((obj, method, args) -> { + // If true, we've found a write method. Otherwise, a read method. + if (method.getReturnType().equals(Void.TYPE)) + throw new WriteMethodException(); + throw new ReadMethodException(); + })) + .make() + .load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION) + .getLoaded() + .getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Failed to find NBTCompound constructor."); + } + } + /** * Find the read/write methods in TileEntity. * @param blockState - the block state. * @throws IOException If we cannot find these methods. */ - private void findMethodUsingCGLib(T blockState) throws IOException { + private void findMethodUsingByteBuddy(T blockState) throws IllegalAccessException, InvocationTargetException, + InstantiationException { + if (nbtCompoundParserConstructor == null) + nbtCompoundParserConstructor = setupNBTCompoundParserConstructor(); + final Class nbtCompoundClass = MinecraftReflection.getNBTCompoundClass(); - // This is a much slower method, but it is necessary in MCPC - Enhancer enhancer = EnhancerFactory.getInstance().createEnhancer(); - enhancer.setSuperclass(nbtCompoundClass); - enhancer.setCallback(new MethodInterceptor() { - @Override - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { - if (method.getReturnType().equals(Void.TYPE)) { - // Write method - writeDetected = true; - } else { - // Read method - readDetected = true; - } - throw new RuntimeException("Stop execution."); - } - }); - Object compound = enhancer.create(); - Object tileEntity = tileEntityField.get(blockState); + final Object compound = nbtCompoundParserConstructor.newInstance(); + final Object tileEntity = tileEntityField.get(blockState); // Look in every read/write like method for (Method method : FuzzyReflection.fromObject(tileEntity, true). getMethodListByParameters(Void.TYPE, new Class[] { nbtCompoundClass })) { try { - readDetected = false; - writeDetected = false; method.invoke(tileEntity, compound); - } catch (Exception e) { - // Okay - see if we detected a write or read - if (readDetected) + } catch (InvocationTargetException e) { + if (e.getCause() instanceof ReadMethodException) { readCompound = Accessors.getMethodAccessor(method, true); - if (writeDetected) + } else if (e.getCause() instanceof WriteMethodException) { writeCompound = Accessors.getMethodAccessor(method, true); + } else { + // throw new RuntimeException("Inner exception.", e); + } + } catch (Exception e) { + throw new RuntimeException("Generic reflection error.", e); } } } @@ -284,4 +299,28 @@ class TileEntityAccessor { } return (TileEntityAccessor) (accessor != EMPTY_ACCESSOR ? accessor : null); } + + /** + * An internal exception used to detect read methods. + * @author Kristian + */ + private static class ReadMethodException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public ReadMethodException() { + super("A read method was executed."); + } + } + + /** + * An internal exception used to detect write methods. + * @author Kristian + */ + private static class WriteMethodException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public WriteMethodException() { + super("A write method was executed."); + } + } } diff --git a/src/test/java/com/comphenix/protocol/events/SerializedOfflinePlayerTest.java b/src/test/java/com/comphenix/protocol/events/SerializedOfflinePlayerTest.java new file mode 100644 index 00000000..eabf8383 --- /dev/null +++ b/src/test/java/com/comphenix/protocol/events/SerializedOfflinePlayerTest.java @@ -0,0 +1,72 @@ +package com.comphenix.protocol.events; + +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.UUID; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.when; + +public class SerializedOfflinePlayerTest { + + @Mock + static OfflinePlayer offlinePlayer; + + private static final String name = "playerName"; + private static UUID uuid; + private static final long firstPlayed = 1000L; + private static final long lastPlayed = firstPlayed + 100L; + private static final boolean isOp = false; + private static final boolean playedBefore = true; + private static final boolean whitelisted = true; + + private static SerializedOfflinePlayer serializedOfflinePlayer; + + @Before + public void initMocks() { + MockitoAnnotations.initMocks(this); + + uuid = UUID.randomUUID(); + when(offlinePlayer.getName()).thenReturn(name); + when(offlinePlayer.getUniqueId()).thenReturn(uuid); + when(offlinePlayer.getFirstPlayed()).thenReturn(firstPlayed); + when(offlinePlayer.getLastPlayed()).thenReturn(lastPlayed); + when(offlinePlayer.isOp()).thenReturn(isOp); + when(offlinePlayer.hasPlayedBefore()).thenReturn(playedBefore); + when(offlinePlayer.isWhitelisted()).thenReturn(whitelisted); + + serializedOfflinePlayer = new SerializedOfflinePlayer(offlinePlayer); + } + + @Test + public void getProxyPlayer() { + Player player = serializedOfflinePlayer.getProxyPlayer(); + Assert.assertNotNull(player); + + // getDisplayName only works for online players. + assertThrows(UnsupportedOperationException.class, player::getDisplayName); + + assertEquals(uuid, player.getUniqueId()); + assertEquals(name, player.getName()); + assertEquals(firstPlayed, player.getFirstPlayed()); + assertEquals(lastPlayed, player.getLastPlayed()); + assertEquals(isOp, player.isOp()); + assertEquals(playedBefore, player.hasPlayedBefore()); + assertEquals(whitelisted, player.isWhitelisted()); + } + + @Test + public void getSecondProxyPlayer() { + // Make sure that the proxyPlayer generation doesn't work only once. + Player player = serializedOfflinePlayer.getProxyPlayer(); + Assert.assertNotNull(player); + + assertEquals(uuid, player.getUniqueId()); + } +} diff --git a/src/test/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactoryTest.java b/src/test/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactoryTest.java new file mode 100644 index 00000000..8fe0db80 --- /dev/null +++ b/src/test/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactoryTest.java @@ -0,0 +1,42 @@ +package com.comphenix.protocol.injector.server; + +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.Assert.*; + +public class TemporaryPlayerFactoryTest { + + private static final TemporaryPlayerFactory temporaryPlayerFactory = new TemporaryPlayerFactory(); + + @Mock + Server server; + @Mock + SocketInjector socketInjector; + + @Before + public void initMocks() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testUnavailableSocketInjector() + { + Player player = temporaryPlayerFactory.createTemporaryPlayer(server); + assertThrows(IllegalStateException.class, player::getPlayer); + } + + @Test + public void createTemporaryPlayer() { + + Player player = temporaryPlayerFactory.createTemporaryPlayer(server, socketInjector); + assertEquals(server, player.getServer()); + + // May seem dumb, but this makes sure that the .equals method is still instact. + assertEquals(player, player); + } +} diff --git a/src/test/java/com/comphenix/protocol/utility/MinecraftMethodsTest.java b/src/test/java/com/comphenix/protocol/utility/MinecraftMethodsTest.java index fc2fc1c4..eef7603d 100644 --- a/src/test/java/com/comphenix/protocol/utility/MinecraftMethodsTest.java +++ b/src/test/java/com/comphenix/protocol/utility/MinecraftMethodsTest.java @@ -7,6 +7,8 @@ import org.junit.Test; import com.comphenix.protocol.BukkitInitialization; +import java.lang.reflect.Field; + public class MinecraftMethodsTest { @BeforeClass @@ -19,4 +21,19 @@ public class MinecraftMethodsTest { assertNotNull(MinecraftMethods.getSendPacketMethod()); assertNotNull(MinecraftMethods.getNetworkManagerHandleMethod()); } + + private void setNull(final String fieldName) throws NoSuchFieldException, IllegalAccessException { + Field field = MinecraftMethods.class.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(null, null); + } + + @Test + public void initializePacket() throws NoSuchFieldException, IllegalAccessException { + setNull("packetReadByteBuf"); + setNull("packetWriteByteBuf"); + + assertNotNull(MinecraftMethods.getPacketWriteByteBufMethod()); + assertNotNull(MinecraftMethods.getPacketReadByteBufMethod()); + } }