mamoe / mirai

高效率 QQ 机器人支持库
https://mirai.mamoe.net
GNU Affero General Public License v3.0
14.5k stars 2.55k forks source link

ListenerHost 不兼容 Java 方法 #443

Closed stupidTiecha closed 4 years ago

stupidTiecha commented 4 years ago

已在 1.0-EA2 修复

Originally posted by @Him188 in https://github.com/mamoe/mirai/issues/412#issuecomment-653416239

jdk版本 1.8_162(Orecla) 和 1.8_252(openJdk)

代码片段:

    Events.registerEvents(bot, new SimpleListenerHost() {

        @EventHandler
        public ListeningStatus onFriendMsg (FriendMessageEvent event) {

            MessageReceipt<Contact> receipt = event.getSender().sendMessage("测试测试");

            receipt.recallIn(10000);

            return ListeningStatus.LISTENING;
        }

堆栈信息:

2020-07-17 11:56:08 E/Bot 1057814219: An exception was thrown under a coroutine of Bot
java.lang.IllegalArgumentException: argument type mismatch
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at net.mamoe.mirai.event.Events__JvmMethodListenersKt$registerEvent$15$1.invokeSuspend(JvmMethodListeners.kt:368)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Him188 commented 4 years ago

由于 Kotlin MPP 目前无法使用任何 Java 代码,我无法进行 Java 的测试...

CafeBabe202 commented 4 years ago

我发现换成1.0-EA2后,依然会发生以上异常

stupidTiecha commented 4 years ago

由于 Kotlin MPP 目前无法使用任何 Java 代码,我无法进行 Java 的测试... 看看这个?

https://stackoverflow.com/questions/52082041/kotlin-argument-type-mismatch-when-passing-array-as-vararg-parameter

UPDATE: In newer versions of Kotlin you can use args.orEmpty() instead of args ?: emptyArray()

TL;DR You cannot pass args but you need to use *(args ?: emptyArray()) because Method.invoke does not expect an array but a variadic parameter.

See this answer for more information

The way I found the actual issue I looked at the generated bytecode, for Kotlin I get the following:

override fun invoke(proxy : Any?, method : Method, args : Array?) : Any? { println("Kotlin proxy works") return method.invoke(target, args) } public java.lang.Object invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]); Code: 0: aload_2 1: ldc #12 // String method 3: invokestatic #18 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V 6: ldc #20 // String Kotlin proxy works 8: astore 4 10: getstatic #26 // Field java/lang/System.out:Ljava/io/PrintStream; 13: aload 4 15: invokevirtual #32 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 18: aload_2 19: aload_0 20: getfield #36 // Field target:LIface; 23: iconst_1 24: anewarray #4 // class java/lang/Object 27: dup 28: iconst_0 29: aload_3 30: aastore 31: invokevirtual #41 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; 34: areturn Now, as you can see, Kotlin does manipulate the args parameter - in fact it creates a new array. Java does not do this (also it skips null-checks):

public Object invoke(Object proxy, Method method, Object args[]) throws Throwable { System.out.println("Java handler works"); return method.invoke(target, args); } public java.lang.Object invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) throws java.lang.Throwable; Code: 0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #4 // String Java handler works 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: aload_2 9: aload_0 10: getfield #2 // Field target:LIface; 13: aload_3 14: invokevirtual #6 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object; 17: areturn Now, let's intercept the actuall array. I created a method in Java code to act as an intermediary:

public static Object invoke0(Iface target, Method method, Object args[]) throws Throwable { System.out.println("Invoking method with " + java.util.Arrays.toString(args)); return method.invoke(target, args); } Execute that from both Java and Kotlin - and it works.

Now what is the difference? Right, we expect an Object[], but Method.invoke takes an Object....

Change our intermediary to take Object... and we get our error message, along with this output:

Invoking method with [[Ljava.lang.Object;@4b67cf4d] So obviously, we aren't passing an Object[] but an Object[][], which means type mismatch!

stupidTiecha commented 4 years ago
@javax.validation.constraints.NotNull

添加了注解依旧没用的

   import javax.validation.constraints.NotNull;

        @EventHandler
        public ListeningStatus onFriendMsg (@NotNull FriendMessageEvent event) {

            MessageReceipt<Contact> receipt = event.getSender().sendMessage("测试测试");

            receipt.recallIn(10000);

            return ListeningStatus.LISTENING;
        }

仍然报错: 2020-07-17 14:00:12 E/Bot 1057814219: An exception was thrown under a coroutine of Bot java.lang.IllegalArgumentException: argument type mismatch at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at net.mamoe.mirai.event.Events__JvmMethodListenersKt$registerEvent$15$1.invokeSuspend(JvmMethodListeners.kt:368) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)

stupidTiecha commented 4 years ago
@javax.validation.constraints.NotNull

添加了注解依旧没用的

   import javax.validation.constraints.NotNull;

        @EventHandler
        public ListeningStatus onFriendMsg (@NotNull FriendMessageEvent event) {

            MessageReceipt<Contact> receipt = event.getSender().sendMessage("测试测试");

            receipt.recallIn(10000);

            return ListeningStatus.LISTENING;
        }

仍然报错: 2020-07-17 14:00:12 E/Bot 1057814219: An exception was thrown under a coroutine of Bot java.lang.IllegalArgumentException: argument type mismatch at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at net.mamoe.mirai.event.Events__JvmMethodListenersKt$registerEvent$15$1.invokeSuspend(JvmMethodListeners.kt:368) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)

我这边经测试没有问题. 检查下该注解的版本. 这是我这边的依赖版本. image

依赖版本相同的 感觉不是这个的问题

CafeBabe202 commented 4 years ago
Exception in thread "main" java.lang.IllegalArgumentException: Kotlin event handlers cannot have nullable parameter type.
    at net.mamoe.mirai.event.Events__JvmMethodListenersKt.registerEvent$Events__JvmMethodListenersKt(JvmMethodListeners.kt:298)
    at net.mamoe.mirai.event.Events__JvmMethodListenersKt.registerEvents(JvmMethodListeners.kt:233)
    at net.mamoe.mirai.event.Events.registerEvents(JvmMethodListeners.kt:1)
    at net.mamoe.mirai.event.Events__JvmMethodListenersKt.registerEvents$default(JvmMethodListeners.kt:230)
    at net.mamoe.mirai.event.Events.registerEvents$default(JvmMethodListeners.kt:1)
    at net.mamoe.mirai.event.Events.registerEvents(JvmMethodListeners.kt)
    at Test.main(Test.java:35)

这个是为啥

BOFA1ex commented 4 years ago
@javax.validation.constraints.NotNull

添加了注解依旧没用的

   import javax.validation.constraints.NotNull;

        @EventHandler
        public ListeningStatus onFriendMsg (@NotNull FriendMessageEvent event) {

            MessageReceipt<Contact> receipt = event.getSender().sendMessage("测试测试");

            receipt.recallIn(10000);

            return ListeningStatus.LISTENING;
        }

仍然报错: 2020-07-17 14:00:12 E/Bot 1057814219: An exception was thrown under a coroutine of Bot java.lang.IllegalArgumentException: argument type mismatch at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at net.mamoe.mirai.event.Events__JvmMethodListenersKt$registerEvent$15$1.invokeSuspend(JvmMethodListeners.kt:368) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)

我这边经测试没有问题. 检查下该注解的版本. 这是我这边的依赖版本. image

依赖版本相同的 感觉不是这个的问题

不好意思哈, 眼误理解错问题定位了... 匿名内部类的问题. 单独拎一个类定义实现SimpleListenerHost类即可.

public static void main(String[] args) {
        Bot bot = BotFactoryJvm.newBot(account, pwd, new BotConfiguration(){
            {
                fileBasedDeviceInfo("deviceInfo.json");
            }
        });
        Events.registerEvents(bot, new CustomEventListener());
        bot.login();
        bot.join();
    }

    public static class CustomEventListener extends SimpleListenerHost{
        @EventHandler
        public ListeningStatus onFriendMsg (FriendMessageEvent event) {
            MessageReceipt<Contact> receipt = event.getSender().sendMessage("测试测试");
            receipt.recallIn(10000);
            return ListeningStatus.LISTENING;
        }

        @Override
        public void handleException(@NotNull CoroutineContext context, @NotNull Throwable exception) {
            super.handleException(context, exception);
        }
    }

原因不明, 之前遇到过, 隔了段时间忘记提issue了.

stupidTiecha commented 4 years ago

@Him188 麻烦关下这个issue, 多谢. 我不小心把个人配置信息暴露出来了.

问题定位: SimpleListenerHost实现类是匿名内部类, 导致监听到事件上报时抛出 java.lang.IllegalArgumentException: argument type mismatch异常

解决方案: 不能走匿名内部类, 静态/非静态内部类&外部类声明即可.

回复可以自己删掉的

CafeBabe202 commented 4 years ago
public static class CustomEventListener extends SimpleListenerHost{

        @EventHandler
        public ListeningStatus onFriendMsg (FriendMessageEvent event) {
            MessageReceipt<Contact> receipt = event.getSender().sendMessage("测试测试");
            receipt.recallIn(10000);
            return ListeningStatus.LISTENING;
        }

        @Override
        public void handleException(@javax.validation.constraints.NotNull CoroutineContext context,@javax.validation.constraints.NotNull Throwable exception) {
            super.handleException(context, exception);
        }
    }
Exception in thread "main" java.lang.IllegalArgumentException: Kotlin event handlers cannot have nullable parameter type.

哎,我真的是太菜了

Him188 commented 4 years ago

@stupidTiecha 请问你使用的 mirai 版本是多少?

BOFA1ex commented 4 years ago

@Him188 #mirai#1.1.2 目前还存在这个问题.

Karlatemp commented 4 years ago

然而实际是listener host传参错误