40

Kotlin编译与Intrinsics检查 - 技术小黑屋

 4 years ago
source link: https://droidyue.com/blog/2019/08/11/kotlin-compiler-intrinsics/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Kotlin编译与Intrinsics检查

Aug 11th, 2019

在很早的时候,小黑屋就介绍过如何研究Kotlin,其中涉及到了查看字节码和反编译成Java代码的方式,相信很多人研究过的人,都会或多或少遇到过Intrinsics.checkParameterIsNotNull这样或者类似的代码。

首先,我们先看一下这段简单的方法

1
2
3
fun dumpStringMessage(message: String) {
    println("dumpStringMessage=$message")
}

按照我们之前的方法,反编译成Java代码就是这样的

1
2
3
4
5
6
public static final void dumpStringMessage(@NotNull String message) {
      Intrinsics.checkParameterIsNotNull(message, "message");
      String var1 = "dumpStringMessage=" + message;
      boolean var2 = false;
      System.out.println(var1);
}

反编译后,我们可以看到代码中有这样的一行代码Intrinsics.checkParameterIsNotNull(message, "message");

Intrinsics 是什么

  • Intrinsics是Kotlin内部的一个类
  • 包含了检查参数是否为null的checkParameterIsNotNull
  • 包含了表达式结果是否为null的checkExpressionValueIsNotNull
  • 包含了检测lateinit是否初始化的throwUninitializedPropertyAccessException
  • 包含了开发者强制非空!!出现空指针时抛出throwNpe的方法
  • 判断对象相等的方法areEqual
  • 其他的一些处理数据异常的方法和辅助方法

所以上面代码中的Intrinsics.checkParameterIsNotNull(message, "message");是为了检测参数message是否为null进行的判断。

为什么会有Intrinsics等判断代码呢

不是说 Kotlin 是空指针安全,有可空(Any?)和不可空(Any)的类型么,我上面的代码声明的是message: String又不是message: String?,为什么还要多此一举呢?

是的,你的这句话基本上没有毛病,但是有一个前提,那就是空指针和两种类型的特性,目前只在纯kotlin中生效,一旦涉及到和Java交互时,就不灵了。

比如我们在Java代码中这样调用,不会产生任何编译的问题。

1
2
3
4
5
public class JavaTest {
    public void test() {
        StringExtKt.dumpStringMessage(null);
    }
}

但是当我们运行时,就会报出这样的错误

1
2
3
4
5
6
7
Exception in thread "main" java.lang.IllegalArgumentException: Parameter specified as non-null is null: method StringExtKt.dumpStringMessage, parameter message
  at StringExtKt.dumpStringMessage(StringExt.kt)
  at JavaTest.test(JavaTest.java:5)
  at MainKt.main(Main.kt:3)
  at MainKt.main(Main.kt)

Process finished with exit code 1

所以考虑到方法被Java调用的情况,Kotlin会默认的增加checkParameterIsNotNull校验。

Intrinsics.checkParameterIsNotNull 一直都有么?

不过好在Kotlin编译器还是足够聪明的,对于不能被Java直接调用的方法,就不会增加相关处理。

比如标记为private的方法,通常情况下,不会被java调用。

1
2
3
private fun innerDumpStringMessage(message: String) {
    println("innerDumpStringMessage=$message")
}

反编译成的如下代码,就没有Intrinsics.checkParameterIsNotNull

1
2
3
4
5
private static final void innerDumpStringMessage(String message) {
      String var1 = "innerDumpStringMessage=" + message;
      boolean var2 = false;
      System.out.println(var1);
   }

Intrinsics.checkParameterIsNotNull 的好处

定位排查问题快捷

上面代码的好处之一就是对于代码混淆之后,可以相对更加方便的定位问题。

比如这段代码,经过混淆之后,运行

1
2
3
4
5
public class JavaMethod {
    public void callKotlin() {
        KotlinCodeKt.dumpMessage(null);
    }
}

得到如下的崩溃日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 E AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.droidyue.intrinsicsmattersandroidsample/com.droidyue.intrinsicsmattersandroidsample.MainActivity}: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method a.a.a.a.a, parameter message
 E AndroidRuntime:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2927)
 E AndroidRuntime:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2988)
 E AndroidRuntime:     at android.app.ActivityThread.-wrap14(ActivityThread.java)
 E AndroidRuntime:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1631)
 E AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:102)
 E AndroidRuntime:     at android.os.Looper.loop(Looper.java:154)
 E AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:6682)
 E AndroidRuntime:     at java.lang.reflect.Method.invoke(Native Method)
 E AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
 E AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)
 E AndroidRuntime: Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method a.a.a.a.a, parameter message
 E AndroidRuntime:     at com.droidyue.intrinsicsmattersandroidsample.b.a(Unknown Source)
 E AndroidRuntime:     at com.droidyue.intrinsicsmattersandroidsample.a.a(Unknown Source)
 E AndroidRuntime:     at com.droidyue.intrinsicsmattersandroidsample.MainActivity.onCreate(Unknown Source)
 E AndroidRuntime:     at android.app.Activity.performCreate(Activity.java:6942)
 E AndroidRuntime:     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1126)
 E AndroidRuntime:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2880)
 E AndroidRuntime:     ... 9 more

这里我们可以清晰的看到出问题的参数名称,定位出问题的位置。

  • 对于先决条件(参数和状态)提前判断可以避免很多不必要的资源消耗。
  • 避免不必要的状态产生

Intrinsics的问题

刚才我们提到了Intrinsics可以辅助混淆情况下定位排查问题,但是同时也带来了一个问题,那就是

  • 为混淆之后逆向工程提供了更多的帮助。

除此之外,还有人担心Intrinsics是不是存在这样的问题

  • Intrinsics调用和返回带来进栈出栈操作,而Intrinsics为java实现,无法在编译时inline,会不会有性能问题

对于性能的担忧可以说是有些过于杞人忧天了,不过还在好在Kotlin提供了方法来消除这种不必要的过虑。当然也能解决逆向混淆的问题。

编译时去除Intrinsics检查

1
2
-Xno-param-assertions      Don't generate not-null assertions on parameters of methods accessible from Java
-Xno-receiver-assertions   Don't generate not-null assertion for extension receiver arguments of platform types

具体的实施方法,可以参考另一篇文章为 Kotlin 项目设置编译选项

其他Intrinsics出现的场景

checkExpressionValueIsNotNull

当Kotlin 调用 Java 获取表达式结果后需要进行操作时,会增加Intrinsics.checkExpressionValueIsNotNull校验

1
2
3
4
5
//Intrinsics.checkExpressionValueIsNotNull(var10000, "JavaUtil.getBook()");
fun test1() {
    val book: Book = JavaUtil.getBook()
    book.name
}

Intrinsics.throwNpe

当使用!!非空断言时,会有校验非空断言结果的检查,如果有问题,则抛出NPE.

1
2
3
4
5
6
7
8
/**
 * if (message == null) {
       Intrinsics.throwNpe();
   }
 */
fun test2(message: String?) {
   message!!.toInt()
}

throwUninitializedPropertyAccessException

当尝试访问一个lateinit的属性时,会增加是否初始化的判断,如果有问题,会抛出异常。

1
2
3
4
5
6
7
class Movie {
    lateinit var name: String
    //Intrinsics.throwUninitializedPropertyAccessException("name");
    fun dump() {
        println(name)
    }
}

以上就是关于Kotlin编译与 Intrinsics 检查的内容。Enjoy.

相关文章推荐阅读


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK