raphw / byte-buddy

Runtime code generation for the Java virtual machine.
https://bytebuddy.net
Apache License 2.0
6.23k stars 804 forks source link

java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields) #1550

Closed iXanadu13 closed 8 months ago

iXanadu13 commented 11 months ago

version: 1.14.9 Hello, author. I am a novice byte buddy. When I try to redefine a method in a class which has been loaded, I get this error. How should I modify the code? Looking forward to your reply!

The exception message:

[11:28:55 WARN]: Unexpected exception while parsing console command "test parse"
org.bukkit.command.CommandException: Unhandled exception executing command 'test' in plugin SculkSensor v1.0
        at org.bukkit.command.PluginCommand.execute(PluginCommand.java:47) ~[paper-api-1.19-R0.1-SNAPSHOT.jar:?]
        at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:155) ~[paper-api-1.19-R0.1-SNAPSHOT.jar:?]
        at org.bukkit.craftbukkit.v1_19_R1.CraftServer.dispatchCommand(CraftServer.java:909) ~[paper-1.19.jar:git-Paper-81]
        at org.bukkit.craftbukkit.v1_19_R1.CraftServer.dispatchServerCommand(CraftServer.java:872) ~[paper-1.19.jar:git-Paper-81]
        at net.minecraft.server.dedicated.DedicatedServer.handleConsoleInputs(DedicatedServer.java:473) ~[paper-1.19.jar:git-Paper-81]
        at net.minecraft.server.dedicated.DedicatedServer.tickChildren(DedicatedServer.java:447) ~[paper-1.19.jar:git-Paper-81]
        at net.minecraft.server.MinecraftServer.tickServer(MinecraftServer.java:1415) ~[paper-1.19.jar:git-Paper-81]
        at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1188) ~[paper-1.19.jar:git-Paper-81]
        at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:303) ~[paper-1.19.jar:git-Paper-81]
        at java.lang.Thread.run(Thread.java:833) ~[?:?]
Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
        at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method) ~[?:?]
        at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:169) ~[?:?]
        at jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[?:?]
        at java.lang.reflect.Method.invoke(Method.java:577) ~[?:?]
        at net.bytebuddy.utility.Invoker$Dispatcher.invoke(Unknown Source) ~[?:?]
        at net.bytebuddy.utility.dispatcher.JavaDispatcher$Dispatcher$ForNonStaticMethod.invoke(JavaDispatcher.java:1032) ~[SculkSensor-1.0-SNAPSHOT.jar:?]
        at net.bytebuddy.utility.dispatcher.JavaDispatcher$ProxiedInvocationHandler.invoke(JavaDispatcher.java:1162) ~[SculkSensor-1.0-SNAPSHOT.jar:?]
        at net.bytebuddy.dynamic.loading.$Proxy415.retransformClasses(Unknown Source) ~[SculkSensor-1.0-SNAPSHOT.jar:?]
        at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Strategy$2.apply(ClassReloadingStrategy.java:399) ~[SculkSensor-1.0-SNAPSHOT.jar:?]
        at net.bytebuddy.dynamic.loading.ClassReloadingStrategy.load(ClassReloadingStrategy.java:227) ~[SculkSensor-1.0-SNAPSHOT.jar:?]
        at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:101) ~[SculkSensor-1.0-SNAPSHOT.jar:?]
        at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:6325) ~[SculkSensor-1.0-SNAPSHOT.jar:?]
        at xanadu.sculksensor.command.MainCommand.inject(MainCommand.java:61) ~[SculkSensor-1.0-SNAPSHOT.jar:?]
        at xanadu.sculksensor.command.MainCommand.onCommand(MainCommand.java:40) ~[SculkSensor-1.0-SNAPSHOT.jar:?]
        at org.bukkit.command.PluginCommand.execute(PluginCommand.java:45) ~[paper-api-1.19-R0.1-SNAPSHOT.jar:?]
        ... 9 more

This is my inject() function:

private void inject() throws ClassNotFoundException {
        ByteBuddyAgent.install();
        new ByteBuddy()
                .redefine(EntitySheep.class)
                .method(named("a").and(takesArgument(0,byte.class)))
                .intercept(MethodDelegation.to(MySheep.class))
                .make()
                .load(EntitySheep.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
        Bukkit.broadcastMessage("mixin!");
}

EntitySheep.class (I want to redefine the method "a" of it)

public class EntitySheep extends EntityAnimal implements IShearable {
    private int cc;
    // many other fields
    public void a(byte b0) {
        if (b0 == 10) {
            this.cc = 40;
        } else {
            super.a(b0);
        }
    }
    // many other methods
}

MySheep.java

package xanadu.sculksensor;

import net.bytebuddy.implementation.bind.annotation.*;
import org.bukkit.Bukkit;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

public class MySheep {
    @RuntimeType
    public static void intercept(@Origin Method method, @SuperCall Callable<?> superCall) {
        Bukkit.broadcastMessage("123");
        try{
            superCall.call();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
raphw commented 11 months ago

Delegation does not work well here. Have a look at Advice and disableClassFormatChanges.

iXanadu13 commented 11 months ago

Thanks for your rapid reply! I am not familiar with Advice and I am reading some other issues in order to find some clues about it. As for "disableClassFormatChanges", it seems that it only can be found in AgentBuilder class, which runs on premain. But for some reasons I cannot do it on premain. I'm sure that the classes I want to redefine has been loaded.

iXanadu13 commented 8 months ago

I will write a lib for what I need. Thanks for your help anyway.