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.
This commit is contained in:
PimvanderLoos 2021-01-04 06:24:34 +01:00 committed by GitHub
parent 26274fed52
commit b54dd49426
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 725 additions and 441 deletions

14
pom.xml
View File

@ -51,8 +51,8 @@
<relocations>
<relocation>
<pattern>net.sf</pattern>
<shadedPattern>com.comphenix.net.sf</shadedPattern>
<pattern>net.bytebuddy</pattern>
<shadedPattern>com.comphenix.net.bytebuddy</shadedPattern>
</relocation>
</relocations>
@ -100,6 +100,8 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<trimStackTrace>false</trimStackTrace>
<useFile>false</useFile>
<systemProperties>
<property>
<name>projectVersion</name>
@ -291,11 +293,11 @@
<version>${spigot.version}</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.2.5</version>
<scope>compile</scope>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.16</version>
</dependency>
<!-- Testing dependencies -->

View File

@ -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();
}

View File

@ -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);

View File

@ -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<String, Method> lookup = new ConcurrentHashMap<String, Method>();
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.
* <p>
* 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<? extends Player> 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<ByteCodeElement> 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<OfflinePlayer, Object> 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
{
}
}

View File

@ -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);

View File

@ -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<? extends Player> 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<? extends Player> 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<ByteCodeElement> 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<? extends Player>) 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);
}

View File

@ -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<AsmMethod> getMethodCalls(Class<?> clazz, Method method) throws IOException {
final $ClassReader reader = new $ClassReader(clazz.getCanonicalName());
final ClassReader reader = new ClassReader(clazz.getCanonicalName());
final List<AsmMethod> 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;
}
}

View File

@ -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("<init>", $Type.VOID_TYPE, new $Type[] {type}));
invokeConstructor(boxed, new MethodDescriptor("<init>", 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);

View File

@ -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() {

View File

@ -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<TField> extends CompiledStructureModifier<TField> {
//
@ -276,15 +270,15 @@ public final class StructureCompiler {
*/
private <TField> Class<?> generateClass(StructureModifier<TField> 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<Field> fields, String targetSignature, String targetName) {
private void createWriteMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";";
String methodSignature = "(ILjava/lang/Object;)L" + SUPER_CLASS + "<Ljava/lang/Object;>;";
$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", "<init>", "(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, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn($Opcodes.ATHROW);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(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, "<init>", "(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<Field> 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<Field> 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", "<init>", "(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, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn($Opcodes.ATHROW);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(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, "<init>", "(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, "<init>",
private void createConstructor(ClassWriter cw, String className, String targetSignature, String targetName) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
"(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V",
"(L" + SUPER_CLASS + "<Ljava/lang/Object;>;L" + PACKAGE_NAME + "/StructureCompiler;)V", null);
String fullClassName = PACKAGE_NAME + "/" + className;
mv.visitCode();
mv.visitVarInsn($Opcodes.ALOAD, 0);
mv.visitMethodInsn($Opcodes.INVOKESPECIAL, COMPILED_CLASS, "<init>", "()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, "<init>", "()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();
}

View File

@ -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> T createInstance(Class<T> type, Constructor<T> 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.

View File

@ -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 <T> DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional<T> createSubclass(Class<T> 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 <T> DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional<T> createSubclass(Class<T> clz,
ConstructorStrategy.Default constructorStrategy)
{
return new ByteBuddy()
.subclass(clz, constructorStrategy)
.implement(ByteBuddyGenerated.class);
}
}

View File

@ -0,0 +1,9 @@
package com.comphenix.protocol.utility;
/**
* Represents an object that has been generated using ByteBuddy.
*
* @author Pim
*/
public interface ByteBuddyGenerated {
}

View File

@ -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;
}
}

View File

@ -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<Method> candidates = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass())
final Object lookPacket = new PacketContainer(PacketType.Play.Client.CLOSE_WINDOW).getHandle();
final List<Method> candidates = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass())
.getMethodListByParameters(Void.TYPE, new Class<?>[] { MinecraftReflection.getPacketDataSerializerClass() });
// Look through all the methods

View File

@ -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<T extends BlockState> {
*/
private static final ConcurrentMap<Class<?>, 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<T extends BlockState> {
} 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<T extends BlockState> {
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<T extends BlockState> {
}, 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<T extends BlockState> {
}
return (TileEntityAccessor<T>) (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.");
}
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}