Quantcast
Channel: 例外タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 100

kotlin inline 関数内で発生した例外のスタックトレース

$
0
0
概要 クラッシュレポートを調べていて、スタックトレースに書かれた行番号がそのソースファイルに存在しないことがありました。 検索してみると、以下のページを見つけました。 inline 関数で展開されるコードで例外が発生した場合、そのスタックトレースにはソースファイルの行数を超えた行番号が書かれるようです。 試してみる 例1. inline 関数で例外を投げてみる 以下のようなコードを作成しました。例外を投げる inline 関数 throwErrorFunc を実行し、例外をキャッチしスタックトレースをログに出力します。 MainActivity.kt 1: package com.example.inlineexceptionapp 2: 3: import androidx.appcompat.app.AppCompatActivity 4: import android.os.Bundle 5: 6: class MainActivity : AppCompatActivity() { 7: override fun onCreate(savedInstanceState: Bundle?) { 8: super.onCreate(savedInstanceState) 9: setContentView(R.layout.activity_main) 10: 11: try { 12: throwErrorFunc() 13: } 14: catch (e: Exception) { 15: println(e.stackTraceToString()) 16: } 17: } 18: 19: 20: inline fun throwErrorFunc() { 21: throw Exception("error") 22: } 23: } 実行した際に出力されたログ I/System.out: java.lang.Exception: error I/System.out: at com.example.inlineexceptionapp.MainActivity.onCreate(MainActivity.kt:24) I/System.out: at android.app.Activity.performCreate(Activity.java:7009) I/System.out: at android.app.Activity.performCreate(Activity.java:7000) I/System.out: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214) I/System.out: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2731) I/System.out: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856) I/System.out: at android.app.ActivityThread.-wrap11(Unknown Source:0) I/System.out: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589) I/System.out: at android.os.Handler.dispatchMessage(Handler.java:106) I/System.out: at android.os.Looper.loop(Looper.java:164) I/System.out: at android.app.ActivityThread.main(ActivityThread.java:6494) I/System.out: at java.lang.reflect.Method.invoke(Native Method) I/System.out: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) I/System.out: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) MainActivity.kt には 23 行しかありませんが、スタックトレースには at com.example.inlineexceptionapp.MainActivity.onCreate(MainActivity.kt:24) とあります。MainActivity.kt に存在しない行番号が出力されていることがわかります。 例2. forEach 関数 for など列挙中のコレクションに対して add や remove を行い、コレクションの要素数を変更した場合、ConcurrentModificationException が発生します。 以下のコードを作成しました。 MainActivity.kt 1: package com.example.inlineexceptionapp 2: 3: import androidx.appcompat.app.AppCompatActivity 4: import android.os.Bundle 5: 6: class MainActivity : AppCompatActivity() { 7: override fun onCreate(savedInstanceState: Bundle?) { 8: super.onCreate(savedInstanceState) 9: setContentView(R.layout.activity_main) 10: 11: try { 12: val list = mutableListOf(1, 2, 3) 13: list.forEach { 14: // 何かしらの処理 15: 16: // 誤って列挙中のコレクションを変更してしまう 17: list.add(1) 18: 19: // 何かしらの処理 20: } 21: } 22: catch (e: Exception) { 23: println(e.stackTraceToString()) 24: } 25: } 26: } 実行した際に出力されたログ I/System.out: java.util.ConcurrentModificationException I/System.out: at java.util.ArrayList$Itr.next(ArrayList.java:860) I/System.out: at com.example.inlineexceptionapp.MainActivity.onCreate(MainActivity.kt:27) I/System.out: at android.app.Activity.performCreate(Activity.java:7009) I/System.out: at android.app.Activity.performCreate(Activity.java:7000) I/System.out: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214) I/System.out: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2731) I/System.out: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856) I/System.out: at android.app.ActivityThread.-wrap11(Unknown Source:0) I/System.out: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589) I/System.out: at android.os.Handler.dispatchMessage(Handler.java:106) I/System.out: at android.os.Looper.loop(Looper.java:164) I/System.out: at android.app.ActivityThread.main(ActivityThread.java:6494) I/System.out: at java.lang.reflect.Method.invoke(Native Method) I/System.out: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) I/System.out: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) at com.example.inlineexceptionapp.MainActivity.onCreate(MainActivity.kt:27) とあり、やはり MainActivity.kt に存在しない 27 行で発生したことになっています。 原因 概要に挙げたリンクに以下のようにあります。 This means that the actual source line is an inlined code fragment from somewhere else. As class files (in the proper debug information) only support specifying a single source file kotlin had to use a workaround. Basically it adds a table in the class file with mappings between line number ranges and source files. The line numbers used for this are outside the actual range of line numbers of the file. The debugger/ide will “fix” it up for you, but exceptions don’t do that. (Google 翻訳) これは、実際のソース行が別の場所からのインライン化されたコードフラグメントであることを意味します。クラスファイル(適切なデバッグ情報内)は単一のソースファイルの指定のみをサポートしているため、kotlinは回避策を使用する必要がありました。基本的に、行番号範囲とソースファイル間のマッピングを含むテーブルをクラスファイルに追加します。これに使用される行番号は、ファイルの実際の行番号の範囲外です。デバッガー/ IDEはそれを「修正」しますが、例外はそれを行いません。 デバッグ目的のため、inline 展開されるコードには実際のソースファイルの範囲外の行番号を付けているようです。 クラスファイルに行番号が追加されているようです。 確認してみる 例2 で使用したコードで確認してみます。 1: Kotlin Bytecode を表示してみる Bytecode を表示してみました。以下の手順でソースコードの Bytecode を表示することができます。 * Tools -> Kotlin -> Show Kotlin Bytecode ソースコード 13 行にカーソルを合わせると、その行に対応する Bytecode がハイライトされます。 Bytecode 87行以降は java.util.List.dd を実行していることから、Bytecode 67-87 行が展開された forEach のコードのようです。 Bytecode 67, 72 行に LINENUMBER 27 とあり、スタックトレースにある行番号と一致します。 2: class ファイルを逆アセンブルしてみる LineNumberTable はそのバイトコードとソース上の行番号の対応がかかれています。LocalVariableTable は、メソッド内で宣言した変数の名前などを保持したものです。 LineNumberTable と LocalVariableTable は主にデバッグ時に用いられ、実行時には不必要な属性です。 class ファイルを逆アセンブルし、LineNumberTable を確認すると良さそうです。 class ファイルは \app\build\tmp\kotlin-classes\debug\com\example\inlineexceptionapp\MainActivity.class にあります。以下のコマンドを実行して、逆アセンブルします。 $ javap -verbose -l -c MainActivity.class 出力結果です。 // 省略 protected void onCreate(android.os.Bundle); descriptor: (Landroid/os/Bundle;)V flags: ACC_PROTECTED Code: stack=4, locals=9, args_size=2 0: aload_0 1: aload_1 2: invokespecial #11 // Method androidx/appcompat/app/AppCompatActivity.onCreate:(Landroid/os/Bundle;)V 5: aload_0 6: ldc #12 // int 2131427356 8: invokevirtual #16 // Method setContentView:(I)V 11: iconst_5 12: anewarray #18 // class java/lang/Integer 15: dup 16: iconst_0 17: iconst_1 18: invokestatic #22 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 21: aastore 22: dup 23: iconst_1 24: iconst_2 25: invokestatic #22 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 28: aastore 29: dup 30: iconst_2 31: iconst_3 32: invokestatic #22 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 35: aastore 36: dup 37: iconst_3 38: iconst_4 39: invokestatic #22 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 42: aastore 43: dup 44: iconst_4 45: iconst_5 46: invokestatic #22 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 49: aastore 50: invokestatic #28 // Method kotlin/collections/CollectionsKt.mutableListOf:([Ljava/lang/Object;)Ljava/util/List; 53: astore_2 54: nop 55: aload_2 56: checkcast #30 // class java/lang/Iterable 59: astore_3 60: iconst_0 61: istore 4 63: aload_3 64: invokeinterface #34, 1 // InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator; 69: astore 5 71: aload 5 73: invokeinterface #40, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 78: ifeq 118 81: aload 5 83: invokeinterface #44, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 88: astore 6 90: aload 6 92: checkcast #46 // class java/lang/Number 95: invokevirtual #50 // Method java/lang/Number.intValue:()I 98: istore 7 100: iconst_0 101: istore 8 103: aload_2 104: iconst_1 105: invokestatic #22 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 108: invokeinterface #56, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z //省略 LineNumberTable: line 8: 0 line 9: 5 line 11: 11 line 12: 54 line 13: 55 line 27: 63 line 27: 71 line 17: 103 line 20: 114 line 28: 118 line 22: 122 line 23: 123 line 24: 143 line 25: 143 LineNumberTable より、Bytecode とソースコードを以下のように対応させているようです。 Bytecode 55 行 -> ソースコード 13 行 Bytecode 63-103 行 -> ソースコード 27 - 行 Bytecode 103 行 -> ソースコード 17 行 Bytecode 63-103 行 は Kotlin Bytecode で見たように Iterator , Iterator.hasNext が見られます。forEach で展開されるコードを ソースコードの 27 行としているのは間違いなさそうです。 まとめ クラッシュレポートなど、スタックトレースでソースファイルの範囲外の行番号が出力されていた場合は、inline 関数を実行している個所を疑ってみましょう。 ソースファイルの Kotlin Bytecode を表示させてスタックトレースに書かれた行番号を検索することで、該当箇所を見つけられるかもしれません。

Viewing all articles
Browse latest Browse all 100

Trending Articles