Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Object class will not be searched when searching for super class #55

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

LiuYiGL
Copy link

@LiuYiGL LiuYiGL commented Nov 7, 2023

If hasExtend of Class<*> returns false, it means that the current super class is the Object class. But should not stop searching for the super class at this time, because there are some classes that do not override the String(), hash(), and equal() methods.

If `hasExtend` of `Class<*>` returns false, it means that the current
super class is the Object class. But should not stop searching for the
super class at this time, because there are some classes that do not
override the `String()`, `hash()`, and `equal()` methods.
@LiuYiGL LiuYiGL closed this Nov 7, 2023
@LiuYiGL LiuYiGL reopened this Nov 7, 2023
@fankes
Copy link
Collaborator

fankes commented Nov 8, 2023

findSuperOrThrow 的方法只有在第一次找不到的情况下才会生效,hasExtends 方法的判断标准也是 superclass != null && superclass != AnyClass,所以应该不需要这样做,或者你遇到了这样的问题吗,在什么地方,请说明

@LiuYiGL
Copy link
Author

LiuYiGL commented Nov 8, 2023

因为在xposed环境下,就存在两个不同的classloader,然后对象之间就会出现CastException异常。所以我目前的解决方法就是通过Proxy进行代理嘛。因为我的项目不包含混淆,我直接在模块下创建和宿主的方法相同的接口,然后通过Proxy实现这个接口,在代理对象调用这个接口时自动触发宿主对象中相同的方法。就不需要每次都写一堆条件代码了。

这里是我的InvocationHandler 代码

// origin 是被代理对象
internal class ProxyHandler(private val origin: Any) : InvocationHandler {
    override fun invoke(proxy: Any?, method: Method, args: Array<out Any?>?): Any? {
        // 查找被代理对象的方法
        origin.javaClass.method {
            superClass()    // 包含父类的方法
            name = method.name    // 代理接口的方法名应该和被代理对象的方法名相同
            param {
                if (args.isNullOrEmpty() && it.isEmpty()) true    // 两个方法参数都为 0 
                else if (args?.size != it.size) false     // 两个方法参数不相同,说明不是同一个方法
                else {
                    // 遍历所有参数判断是否符合能在origin对象中触发
                    it.forEachIndexed { index, clazz ->
                        if (args[index] != null && !clazz.isInstance(args[index])) {
                            return@param false
                        }
                    }
                    true
                }
            }
        }.ignored().onNoSuchMethod {
            // 问题出现在这,在调用 `toString()` 方法时居然提示origin对象不存在这个方法
           // 于是加了下面这段,问题解决
            Any::class.java.method {
                name = method.name
            }.ignored().onNoSuchMethod {
                YLog.error("[ProxyUtils - ${origin.javaClass.simpleName}] is not found this method: $method")
                YLog.error("[ProxyUtils] args: ${args?.asList()}")
                YLog.error("${origin.javaClass.method{superClass()}.giveAll()}")
                return null
            }.get(origin).let {
                return if (args.isNullOrEmpty()) it.call() else it.call(*args)
            }
        }.get(origin).let {
            return if (args.isNullOrEmpty()) it.call() else it.call(*args)
        }
    }
}

问题在注释上了,或者看看下面这里
Snipaste_2023-11-08_22-44-49

@fankes
Copy link
Collaborator

fankes commented Nov 8, 2023

恕我没看懂这是在做什么,或者你可以把需要的内容发上来单独写个方法提交?
classSet != AnyClass 是不等于 classSet.superclass != AnyClass 的,我们判断是否存在继承关系的依据也是如此

@LiuYiGL
Copy link
Author

LiuYiGL commented Nov 8, 2023

这里是一个简单的例子,可以在任何包含依赖的地方执行这里的main方法测试

在第一个InvocationHandler 中哪怕我用到了 superClass() 表示搜索范围包含父类,但是很明显的,在用到关于Any的方法时异常了

import com.highcapable.yukihookapi.hook.factory.method
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy

/**
 * 假设这个类是宿主中的一个类
 */
class A {
    fun b(str: String) {
        println("这里是业务内容:$str")
    }
}

/**
 * 下面这段代码写在模块上
 */
fun main() {
    val a1: Any = A::class.java.newInstance() // 在模块中我们只能通过反射实例并且赋值成Any,我们没法完成 val a: A = A()的方式

    // 但是我可以写一个接口 B,然后利用Proxy
    val proxy = Proxy.newProxyInstance(
        ClassLoader.getSystemClassLoader(), arrayOf(B::class.java),
        object : InvocationHandler {
            override fun invoke(proxy: Any?, method: Method, args: Array<out Any>?): Any? {
                a1.javaClass.method {
                    superClass()
                    name = method.name              // 这里只做简单的函数名匹配好了
                }.ignored().onNoSuchMethod {
                    println("$a1 找不到 $method")
                    return null
                }.get(a1).let {
                    return if (args.isNullOrEmpty()) it.call() else it.call(*args)
                }
            }

        }
    )
    println("proxy 是否可以转型成 B:${proxy is B}")
    if (proxy is B) {
        proxy.b("这个是B")
        proxy.b("这串代码正常执行")
        proxy.b("我可以在模块上任意调用这段代码")
    }

    // 但是如果我调用 Any 类的方法
    // 比如
    runCatching { println("直接输出:${proxy.toString()}") }.onFailure { println("直接输出异常:$it") }
    runCatching { println("hash值:${proxy.hashCode()}") }.onFailure { println("hash值异常: $it") }
    runCatching { println("相等:${proxy.equals(1)}") }.onFailure { println("相等异常:$it") }

    // 上面的代码就会异常,但如果能搜索到Any类的方法话,且 Any 肯定是 A 的父类
    println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")

    val proxy2 = Proxy.newProxyInstance(
        ClassLoader.getSystemClassLoader(), arrayOf(B::class.java),
        object : InvocationHandler {
            override fun invoke(proxy: Any?, method: Method, args: Array<out Any>?): Any? {
                a1.javaClass.method {
                    superClass()
                    name = method.name              // 这里只做简单的函数名匹配好了
                }.ignored().onNoSuchMethod {
                    Any::class.java.method {
                        name = method.name
                    }.ignored().onNoSuchMethod {
                        println("$a1 找不到 $method")
                        return null
                    }.get(a1).let {
                        return if (args.isNullOrEmpty()) it.call() else it.call(*args)
                    }
                }.get(a1).let {
                    return if (args.isNullOrEmpty()) it.call() else it.call(*args)
                }
            }
        }
    )

    println("proxy2 是否可以转型成 B:${proxy2 is B}")
    if (proxy2 is B) {
        proxy2.b("这个也是B")
        proxy2.b("这串代码正常执行")
        proxy2.b("我可以在模块上任意调用这段代码")
    }

    // 而且下面这段代码正常
    runCatching { println("直接输出:${proxy2.toString()}") }.onFailure { println("直接输出异常:$it") }
    runCatching { println("hash值:${proxy2.hashCode()}") }.onFailure { println("hash值异常: $it") }
    runCatching { println("相等:${proxy2.equals(1)}") }.onFailure { println("相等异常:$it") }
}

interface B {
    fun b(str: String)
}

下面是输出结果:

proxy 是否可以转型成 B:true
这里是业务内容:这个是B
这里是业务内容:这串代码正常执行
这里是业务内容:我可以在模块上任意调用这段代码
A@2de8284b 找不到 public java.lang.String java.lang.Object.toString()
直接输出:null
A@2de8284b 找不到 public native int java.lang.Object.hashCode()
hash值异常: java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.lang.reflect.InvocationHandler.invoke(Object, java.lang.reflect.Method, Object[])" is null
A@2de8284b 找不到 public boolean java.lang.Object.equals(java.lang.Object)
相等异常:java.lang.NullPointerException: Cannot invoke "java.lang.Boolean.booleanValue()" because the return value of "java.lang.reflect.InvocationHandler.invoke(Object, java.lang.reflect.Method, Object[])" is null
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
proxy2 是否可以转型成 B:true
这里是业务内容:这个也是B
这里是业务内容:这串代码正常执行
这里是业务内容:我可以在模块上任意调用这段代码
直接输出:A@2de8284b
hash值:770189387
相等:false

@LiuYiGL
Copy link
Author

LiuYiGL commented Nov 8, 2023

findSuperOrThrow 的方法只有在第一次找不到的情况下才会生效,hasExtends 方法的判断标准也是 superclass != null && superclass != AnyClass,所以应该不需要这样做,或者你遇到了这样的问题吗,在什么地方,请说明

你说的对,第一次找不到时才会执行 findSuperOrThrow,然后在findSuperOrThrow中判断是否设置了 isFindInSuper 并且 hasExtendssuperclass != null && superclass != AnyClass ,然后才会递归搜索

但是如果第一次搜索是Any的子类,而要搜的方法是 toString(),这可能很蠢,但某种情况下应该存在这种需求
因为子类没有重写toString(),于是向父类查找,但是在 findSuperOrThrow 中因为 superclass == AnyClass 并不会向Any查找,然后导致查找失败

@fankes
Copy link
Collaborator

fankes commented Nov 8, 2023

我大概理解你的意思了,但是你这样修改并不严谨,你需要考虑到其它可能的情况,所以请先进行一次测试

@LiuYiGL
Copy link
Author

LiuYiGL commented Nov 8, 2023

我大概理解你的意思了,但是你这样修改并不严谨,你需要考虑到其它可能的情况,所以请先进行一次测试

确实,或许再添加一个 superclass != null 的情况会更严谨一些,因为存在动态生成一个没爹的类的可能,但是我考虑到在你的find 方法中已经存在了null处理,即if (classSet == null) return@createResult mutableListOf(),所以觉得这种判断有点多余。

其实或者说压根不需要任何isFindInSuper以外的判断,因为当superclass == null时,递归会在下一次搜索classSet == null时跳出,除非有Any -> ClassA -> Any 这种循环继承的鬼畜存在?

@fankes
Copy link
Collaborator

fankes commented Nov 8, 2023

还真不一定可能有奇怪的情况,所以最好是添加一个备用的方案

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants