フフフ、ついに奥の手 javap を使う時が来たようだな・・・

# はじめに

しょうもないタイトルから始まってますが、内容もしょうもないです。時間に余裕のない方は「そっ閉じ」してください。

実は先日(2024/12/21)、私が関わっているプロジェクトのリリース作業で問題が発生いたしまして、つい先程(2024/12/22)まで色々と調査、今後の方針等の対応を行っていました。
色々と思うところがありまして、記事として残しておいてもいいかなと思い至った結果、急遽記事の内容を変更してお届けしております。

**いいですか？決して時間が足りなくなったわけじゃないですからね。**

# 前提条件

よくあるアーキテクチャなのですが、この話で登場するシステムでは「ライブラリ管理システム」といった**ビルド資産を一括管理するシステム**がありまして、そのシステム経由でライブラリリリースが行われています。

# 何が起こったのか？

Production 環境に既にリリースされている jar ファイルと、ライブラリ管理システム上に保管されている jar ファイルのハッシュ値が異なるといった事象が発生しました。おい！なんでやねん・・・。

``` bash
# 今回のリリースファイル
0ddb496abee187fb0b5acd27947a5e77 /production/hogehoge/xxx_1.jar
4478efb2cedb25882d6d5d842af65966 /production/hogehoge/tmp/release/xxx_1.jar

# 今回修正していないファイル
cf4dd51e1c28a9d201805ad648e74721 /production/hogehoge/xxx_2.jar
bdf1959685c4ce0095f0bfb490e567af /production/hogehoge/tmp/release/xxx_2.jar

# develop と production のライブラリ管理システム上は同じ
bdf1959685c4ce0095f0bfb490e567af /develop/hogehoge/xxx_2.jar
bdf1959685c4ce0095f0bfb490e567af /develop/hogehoge/tmp/release/xxx_2.jar
```

:::note warn
当然ですが xxx_2.jar は、ライブラリ管理システムもファイルは更新していません。
:::

この問題の経緯については、マジしょうもない理由のため言及しません。またの機会に。

# Misson!! 稼動中のライブラリを担保せよ！

上記の通り、Production 環境だけが、どのライブラリとも一致していない状況であり「デグレードなのか？」と、現場に戦慄が走ります。~~気絶する人はいませんでした。~~
当然ながら既に稼働中のシステムですから、「まぁこっちが最新やろ」の勢いだけで資産を更新するわけにもいきません。

## [1st] -> unzip/md5sum

ある程度の java 知識がある方であれば、すぐ思いつく手段かと思います。
jar ファイルは zip によって圧縮されたファイルであり、unzip 等のコマンドで class ファイルを展開して、class ファイルのハッシュ値を取り比較する。といった手段があります。

> jarコマンドは、ZIPおよびZLIBの圧縮形式に基づいた汎用のアーカイブおよび圧縮ツールです。

https://docs.oracle.com/javase/jp/21/docs/specs/man/jar.html

## [2nd] -> javap（※タイトルの回収ポイント）

上記手段が一致していれば、大抵は納得してもらえるとは思います。ただし万が一、結果が異なっていた時に、そもそもどうデグレードしているのかコードレベルで解析しなさい！となった時に備え、私はこう構えていたのです・・・。

**フフフ、ついにあのコマンド(javap)を使う時が来たようだな**

# javap コマンドとは

まぁ調べればわかる事ではあるのですが、javap コマンドは javac でコンパイルされた class ファイルを逆アセンブルするコマンドです。

https://docs.oracle.com/javase/jp/21/docs/specs/man/javap.html

:::note info
Java の逆アセンブルとは、雑に言うとコンパイルされた class ファイル（JVM 中間コード）から、ギリ人間が読めそうな JVM アセンブリコードに変換することです。
:::

# どんな感じで出力されるの？

下記のような JVM アセンブリコードが出力されます。見ての通りメソッドのシンボル単位でコードが出てくるため、これと java ソースコードを睨めっこしながら頑張ってデグレードした箇所を特定しましょう！

```
kook@vdeb:~/workspaces/test/classes$ javap -c xxx.class 

Compiled from "xxx.java"
public class xxx {
  public xxx();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static boolean doSomething1(java.lang.String);
    Code:
       0: iconst_1
       1: ireturn

  public static void doSomething2(java.lang.String[]);
    Code:
       0: aload_0
       1: arraylength
       2: iconst_4
       3: if_icmpge     14
       6: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       9: aload_0
      10: arraylength
      11: invokevirtual #13                 // Method java/io/PrintStream.print:(I)V
      14: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: ldc           #19                 // int 1000000
       3: invokestatic  #20                 // InterfaceMethod java/util/stream/IntStream.range:(II)Ljava/util/stream/IntStream;
       6: invokedynamic #26,  0             // InvokeDynamic #0:accept:()Ljava/util/function/IntConsumer;
      11: invokeinterface #30,  2           // InterfaceMethod java/util/stream/IntStream.forEach:(Ljava/util/function/IntConsumer;)V
      16: return
}
```

# あとがき

結論からいうと、~~ざんねんながら~~今回のインシデントは javap コマンドを使って確認するまでにはなりませんでした。

そもそも、**こんな状況になるような管理してんなよ**って話でしょうけど、みなさんも明日は我が身です。万が一同じ状況に至り、リリース済ライブラリの担保が必要だとなった時、この記事を思い出していただければ幸いです。

ちなみに、本来 javap はこんな用途で使うものではありません