はじめに
しょうもないタイトルから始まってますが、内容もしょうもないです。時間に余裕のない方は「そっ閉じ」してください。
実は先日(2024/12/21)、私が関わっているプロジェクトのリリース作業で問題が発生いたしまして、つい先程(2024/12/22)まで色々と調査、今後の方針等の対応を行っていました。
色々と思うところがありまして、記事として残しておいてもいいかなと思い至った結果、急遽記事の内容を変更してお届けしております。
いいですか?決して時間が足りなくなったわけじゃないですからね。
前提条件
よくあるアーキテクチャなのですが、この話で登場するシステムでは「ライブラリ管理システム」といったビルド資産を一括管理するシステムがありまして、そのシステム経由でライブラリリリースが行われています。
何が起こったのか?
Production 環境に既にリリースされている jar ファイルと、ライブラリ管理システム上に保管されている jar ファイルのハッシュ値が異なるといった事象が発生しました。おい!なんでやねん・・・。
# 今回のリリースファイル
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
当然ですが xxx_2.jar は、ライブラリ管理システムもファイルは更新していません。
この問題の経緯については、マジしょうもない理由のため言及しません。またの機会に。
Misson!! 稼動中のライブラリを担保せよ!
上記の通り、Production 環境だけが、どのライブラリとも一致していない状況であり「デグレードなのか?」と、現場に戦慄が走ります。気絶する人はいませんでした。
当然ながら既に稼働中のシステムですから、「まぁこっちが最新やろ」の勢いだけで資産を更新するわけにもいきません。
[1st] -> unzip/md5sum
ある程度の java 知識がある方であれば、すぐ思いつく手段かと思います。
jar ファイルは zip によって圧縮されたファイルであり、unzip 等のコマンドで class ファイルを展開して、class ファイルのハッシュ値を取り比較する。といった手段があります。
jarコマンドは、ZIPおよびZLIBの圧縮形式に基づいた汎用のアーカイブおよび圧縮ツールです。
[2nd] -> javap(※タイトルの回収ポイント)
上記手段が一致していれば、大抵は納得してもらえるとは思います。ただし万が一、結果が異なっていた時に、そもそもどうデグレードしているのかコードレベルで解析しなさい!となった時に備え、私はこう構えていたのです・・・。
フフフ、ついにあのコマンド(javap)を使う時が来たようだな
javap コマンドとは
まぁ調べればわかる事ではあるのですが、javap コマンドは javac でコンパイルされた class ファイルを逆アセンブルするコマンドです。
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 はこんな用途で使うものではありません