javap の隠しオプション

たまたま OpenJDK の javap のソースコードを読んでいたら、`-help` には表示されない隠しオプションがいくつかあることに気づきました。

* `-XDdetails`
* `-XDinner`
* `-XDindent:(数値)`
* `-XDtab:(数値)`

https://github.com/openjdk/jdk/blob/master/src/jdk.jdeps/share/classes/com/sun/tools/javap/JavapTask.java#L125

せっかくなので、詳しく調べてみました。

:::note warn
実装依存の情報なので、今後のバージョンで変わる可能性があります。
:::

# `-XDdetails`

バイトコードの出力時に、追加で詳細情報を出力するオプションです。
`-verbose` オプションと併用します。

出力する情報は、 `-XDdetails:(オプション)` と指定することで制御可能です。
（複数指定する場合は、`,` または `:` 区切り）

指定できるオプションは、下記の6つです。
* `localVariables`
* `localVariableTypes`
* `source`
* `stackMaps`
* `tryBlocks`
* `typeAnnotations`

`-XDdetails` とした場合、全部のオプションを指定したことになります。
また、`-XDdetails:all,-localVariables` のように `-` 指定すると、一部の出力を省略できます。

## `-XDdetails:localVariables`

`LocalVariableTable` 属性の情報をもとに、ローカル変数の情報をバイトコードと一緒に出力します。
（これらの属性を出力するように、コンパイル時に `-g:vars` オプションが必要です）

### 実行例

適当なコード [^code] を用意して、コンパイルします。  

[^code]:拡張 for 文をつかうと `javap` の出力が長くなるので、ここではあえて for ループを使っています。

```java:Main.java
public class Main {
    public static void main(String[] args) {
        var list = java.util.List.of("foo", "bar");
        for (var i = 0; i < list.size(); i++){
            System.out.println(list.get(i));
        }
    }
}
```

このクラスファイルを `javap -XDdetails:localVariables ...` を実行すると、**バイトコードと一緒に `start local …` と `end local …` を埋め込んで出力してくれます。**

```java:javap -verbose -XDdetails:localVariables Main
Code:
  stack=3, locals=3, args_size=1
    start local 0 // java.lang.String[] args
     0: ldc           #7                  // String foo
     2: ldc           #9                  // String bar
     4: invokestatic  #11                 // InterfaceMethod java/util/List.of:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;
     7: astore_1
    start local 1 // java.util.List list
     8: iconst_0
     9: istore_2
    start local 2 // int i
    10: iload_2
    11: aload_1
    12: invokeinterface #17,  1           // InterfaceMethod java/util/List.size:()I
    17: if_icmpge     42
    20: getstatic     #21                 // Field java/lang/System.out:Ljava/io/PrintStream;
    23: aload_1
    24: iload_2
    25: invokeinterface #27,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
    30: checkcast     #31                 // class java/lang/String
    33: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    36: iinc          2, 1
    39: goto          10
    end local 2 // int i
    42: return
    end local 1 // java.util.List list
    end local 0 // java.lang.String[] args
  LocalVariableTable:
    Start  Length  Slot  Name   Signature
       10      32     2     i   I
        0      43     0  args   [Ljava/lang/String;
        8      35     1  list   Ljava/util/List;
  LocalVariableTypeTable:
    Start  Length  Slot  Name   Signature
        8      35     1  list   Ljava/util/List<Ljava/lang/String;>;
```

## `-XDdetails:localVariableTypes`

`LocalVariableTypeTable` 属性の情報をもとに、ローカル変数のジェネリクス型情報をバイトコードと一緒に出力します。
（これらの属性を出力するように、コンパイル時に `-g:vars` オプションが必要です）


### 実行例

上記と同じクラスファイルに対して `javap -XDdetails:localVariableTypes ...` を実行すると、**バイトコードと一緒に `start generic local …` と `end generic local …` を埋め込んで出力してくれます。**

```java:javap -verbose -XDdetails:localVariableTypes Main
Code:
  stack=3, locals=3, args_size=1
     0: ldc           #7                  // String foo
     2: ldc           #9                  // String bar
     4: invokestatic  #11                 // InterfaceMethod java/util/List.of:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;
     7: astore_1
    start generic local 1 // java.util.List<java.lang.String> list
     8: iconst_0
     9: istore_2
    10: iload_2
    11: aload_1
    12: invokeinterface #17,  1           // InterfaceMethod java/util/List.size:()I
    17: if_icmpge     42
    20: getstatic     #21                 // Field java/lang/System.out:Ljava/io/PrintStream;
    23: aload_1
    24: iload_2
    25: invokeinterface #27,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
    30: checkcast     #31                 // class java/lang/String
    33: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    36: iinc          2, 1
    39: goto          10
    42: return
    end generic local 1 // java.util.List<java.lang.String> list
  LocalVariableTable:
    Start  Length  Slot  Name   Signature
       10      32     2     i   I
        0      43     0  args   [Ljava/lang/String;
        8      35     1  list   Ljava/util/List;
  LocalVariableTypeTable:
    Start  Length  Slot  Name   Signature
        8      35     1  list   Ljava/util/List<Ljava/lang/String;>;
```


## `-XDdetails:source`

`LineNumberTable` 属性と `SourceFile` 属性の情報をもとに、ソースコードの情報をバイトコードと一緒に出力します。
（これらの属性を出力するように、コンパイル時に `-g:lines` オプションが必要です）

このオプションを指定する際には、クラスパス上にもとのソースコードが必要です。


### 実行例

上記と同じクラスファイルに対して `javap -XDdetails:source ...` を実行すると、**バイトコードと一緒にもとの行番号とソースコードを埋め込んで出力してくれます。**
（下記の出力例、スマホだと右スクロールしないと見えないかもしれません）

```java:javap -verbose -XDdetails:source Main
Code:
  stack=3, locals=3, args_size=1
                                              3         var list = java.util.List.of("foo", "bar");
     0: ldc           #7                  // String foo
     2: ldc           #9                  // String bar
     4: invokestatic  #11                 // InterfaceMethod java/util/List.of:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;
     7: astore_1
                                              4         for (var i = 0;i < list.size(); i++){
     8: iconst_0
     9: istore_2
    10: iload_2
    11: aload_1
    12: invokeinterface #17,  1           // InterfaceMethod java/util/List.size:()I
    17: if_icmpge     42
                                              5             System.out.println(list.get(i));
                                          (   6)        }
    20: getstatic     #21                 // Field java/lang/System.out:Ljava/io/PrintStream;
    23: aload_1
    24: iload_2
    25: invokeinterface #27,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
    30: checkcast     #31                 // class java/lang/String
    33: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                                              4         for (var i = 0;i < list.size(); i++){
    36: iinc          2, 1
    39: goto          10
                                              7     }
    42: return
  LineNumberTable:
    line 3: 0
    line 4: 8
    line 5: 20
    line 4: 36
    line 7: 42
```


## `-XDdetails:stackMaps`

`StackMapTable` 属性をもとに、スタックの情報をバイトコードと一緒に出力します。

### 実行例

上記と同じクラスファイルに対して `javap -XDdetails:stackMaps` を実行すると、**バイトコードと一緒に `StackMap locals …` と `StackMap stack …` を埋め込んで出力してくれます。**

```java:javap -verbose -XDdetails:stackMaps Main
Code:
  stack=3, locals=3, args_size=1
  StackMap locals:  java/lang/String[]
  StackMap stack:
     0: ldc           #7                  // String foo
     2: ldc           #9                  // String bar
     4: invokestatic  #11                 // InterfaceMethod java/util/List.of:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/List;
     7: astore_1
     8: iconst_0
     9: istore_2
  StackMap locals:  java/lang/String[] java/util/List int
  StackMap stack:
    10: iload_2
    11: aload_1
    12: invokeinterface #17,  1           // InterfaceMethod java/util/List.size:()I
    17: if_icmpge     42
    20: getstatic     #21                 // Field java/lang/System.out:Ljava/io/PrintStream;
    23: aload_1
    24: iload_2
    25: invokeinterface #27,  2           // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
    30: checkcast     #31                 // class java/lang/String
    33: invokevirtual #33                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    36: iinc          2, 1
    39: goto          10
  StackMap locals:  java/lang/String[] java/util/List
  StackMap stack:
    42: return
  StackMapTable: number_of_entries = 2
    frame_type = 253 /* append */
      offset_delta = 10
      locals = [ class java/util/List, int ]
    frame_type = 250 /* chop */
      offset_delta = 31
```

## `-XDdetails:tryBlocks`

`ExceptionTable` 属性をもとに、例外処理の情報をバイトコードと一緒に出力します。

### 実行例

適当な `try-catch` を使ったコードを用意して、コンパイルします。  

```java:Main.java
public class Main {
    public static void main(String[] args) {
        try {
            System.out.println("try!");
        } catch (Exception e) {
            System.out.println("catch!");
        }
    }
}
```

このクラスファイルに対して `javap -XDdetails:tryBlocks ...` を実行すると、**バイトコードと一緒に `try[...]` や `end try[...]` といった情報を埋め込んで出力してくれます。**

```java:javap -verbose -XDdetails:tryBlocks Main
Code:
  stack=2, locals=2, args_size=1
    try[0] #21 // class java/lang/Exception
     0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
     3: ldc           #13                 // String try!
     5: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    end try[0] #21 // class java/lang/Exception
     8: goto          20
    catch[0] #21 // class java/lang/Exception
    11: astore_1
    12: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
    15: ldc           #23                 // String catch!
    17: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    20: return
  Exception table:
     from    to  target type
         0     8    11   Class java/lang/Exception
```

## `-XDdetails:typeAnnotations`

`RuntimeVisibleTypeAnnotations` 属性や `RuntimeInvisibleTypeAnnotations` をもとに、型のアノテーションの情報をバイトコードと一緒に出力します。

### 実行例

適当な `@Target` を使ったコードを用意して、コンパイルします。  

```java:Main.java
import java.lang.annotation.*;

public class Main {
    @Target(ElementType.TYPE_PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @interface Foo {    }


    public static void main(String[] args) {
        System.out.println("foo");
    }
}
```

このクラスファイルに対して `javap -XDdetails:typeAnnotations ...` を実行すると、**バイトコードと一緒に情報が…、なぜか出力されませんでした(？）**


```java:javap -verbose -XDdetails:typeAnnotations Main
Code:
  stack=2, locals=2, args_size=1
     0: ldc           #7                  // String foo
     2: astore_1
     3: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
     6: aload_1
     7: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    10: return
  RuntimeVisibleTypeAnnotations:
    0: #27(): LOCAL_VARIABLE, {start_pc=3, length=8, index=1}
      Main$Foo
```

`RuntimeVisibleTypeAnnotations` は含まれているので、コードはあっていそうな気がするのですが…。

# `-XDinner`

インナークラスも一緒に `javap` します。

### 実行例

適当なインナークラスを使ったコードを用意して、コンパイルします。  

```java:Main.java
public class Main {
    public class Foo {
        public static void foo() {
            System.out.println("foo");
        }
    }

    public static void main(String[] args) {
        Foo.foo();
    }
}
```

このクラスファイルに対して `javap -XDinner Main` を実行すると、**Main だけでなくインナークラスの Foo の `javap` した内容を出力してくれます。**
（`javap -v 'Main' 'Main$Foo'` と同じ結果になります）

# `-XDindent:(数値)`

通常、`javap` の出力はスペース2つでインデントします。
これを、任意のスペース数に変更します。

```java
// デフォルト
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object

// -XDindent:8 を指定した場合
Constant pool:
         #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
         #2 = Class              #4             // java/lang/Object
```

# `-XDtab:(数値)`

通常、`javap` のコメント部分は40文字目から出力されます。
これを、任意の位置に変更します。

```java
// デフォルト
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object

// -XDtab:50 を指定した場合
Constant pool:
   #1 = Methodref          #2.#3                    // java/lang/Object."<init>":()V
   #2 = Class              #4                       // java/lang/Object
```

# ちなみに

これらは 2009年ごろに実装されて、Java 7 から使えるようになっていたみたいです。

* [\[JDK-6824493\] experimental support for additional info for instructions - Java Bug System](https://bugs.openjdk.org/browse/JDK-6824493)
* [\[JDK-4880672\] javap does not output inner interfaces of an interface - Java Bug System](https://bugs.openjdk.org/browse/JDK-4880672)
* [\[JDK-6867671\] javap whitespace formatting issues - Java Bug System](https://bugs.openjdk.org/browse/JDK-6867671)