(a == 1 && a == 2 && a == 3)を常にtrueにするをJavaでやった

元ネタはStackOverflowのJavaScriptに関するこの記事です。
https://stackoverflow.com/questions/48270127/can-a-1-a-2-a-3-ever-evaluate-to-true

種明かしとしては、
**JavaScriptでの暗黙的な型変換をオーバーライドしてそこに処理を仕込めば比較時に実行できるよ！**
ってことなんで、継承したクラスでオーバーライドすればJavaでも行けそう！

# Javaの暗黙的な型変換
念のためJavaの暗黙的な型変換について確認しておくと、

```java
    @Test
    public void test_integer() {
        Integer a = Integer.valueOf(1);

        if(a == 1){
            System.out.println("true");
        }
    }

```
これはintとの比較時に`Integer`の`intValue()`が呼び出されて比較されます。
Javaだとアンボクシングって呼ばれていますね。

# Integerを継承してintValueをオーバーライドする
Integerクラスがfinalなのでオーバーライドできないので失敗
というかIntegerのintValueオーバーライドしたらいろいろ死にそうですね

# Numberを継承してintValueをオーバーライド
ということでIntegerの親の抽象クラスのNumberクラスを継承して数値クラスを作成してみる。

```java

public class StrangeObject extends Number implements Comparable<Integer>{

    private int value;

    public StrangeObject(int value){
        this.value = value;
    }

    @Override
    public int intValue() {
        value++;
        return value;
    }

    @Override
    public long longValue() {
        value++;
        return value;
    }

    @Override
    public float floatValue() {
        value++;
        return value;
    }

    @Override
    public double doubleValue() {
        value++;
        return value;
    }

    public int compareTo(Integer outerValue) {

        return (value < outerValue) ? -1 : ((value == outerValue) ? 0 : 1);
    }
}

```

intValue()を実装したし、Comparable<Integer>のインターフェースも実装したので
intと比較できそう！

```java
    @Test
    public void test_a1a2a3() throws Exception {

        StrangeObject a = new StrangeObject(0);

        if(a == 1 && a == 2 && a == 3){
            System.out.println("true");
        }
    }
```

```
Error:(22, 14) java: 型StrangeObjectとintは比較できません
```

比較前に型チェックでだめっぽい…

```java
    @Test
    public void test_a1a2a3() throws Exception {

        StrangeObject a = new StrangeObject(0);

        if(a.intValue() == 1 && a.intValue() == 2 && a.intValue() == 3){
            System.out.println("true");
        }
    }
```
これだと通るんですが、intValue()を外す（暗黙的に実行する）にはどうすべきなんでしょう。

# Integerを書き換える
此処から先はかなり闇が深くなるので確認するだけに留めましょう。
※この先の方法で作成したアプリをデプロイすると、Java SE Runtime Environmentバイナリ・コード・ライセンス違反になります。 

一部のシステムクラスでなければアンボクシングされないとコメントで指摘があったので
システムクラスを書き換えます。

Javassistを使い、Integerのクラスファイルを書き換えます。

システムクラスの場合、実行時に書き換える事はできないので、
一旦静的ファイルとして吐き出し、実行時に書き換えたファイルを読み込みます。

```java
    @Test
    public void testIntegerExport() throws Exception {

        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("java.lang.Integer");
        CtMethod method = cc.getDeclaredMethod("intValue");

        // intValueの中にvalue++を追記
        method.insertBefore("value++;");

        // 改造後のクラスファイルを出力
        cc.writeFile();

    }
```
このテストメソッドを実行すると、書き換えたIntegerクラスが`./java/lang/Integer.class`に出力されます。

そして、次のようなmainクラスを作成します。

```java
public class A123 {
    public static void main(String[] args){
        
        Integer a = Integer.valueOf(0);

        if(a == 1 && a == 2 && a == 3){
            System.out.println(true);       
        }
    }
}
```

実行時の引数に
`-Xbootclasspath/p:.`を追加します。

これにより、改造したIntegerクラスが読み込まれます。

ただし、このクラスを読み込んだ場合

```
java.lang.InternalError 
 - klass: 'java/lang/InternalError'
#
# A fatal error has been detected by the Java Runtime Environment:
#
...
```
JVM実行時にIntegerの値が変動してしまうためか、実行できません。

ということでIntegerファイルを更に改造します。

# Integerを書き換える（改）
VM実行開始からmainクラスが実行されるまでは正常に動くIntegerクラスにします。

```java
    @Test
    public void testIntegerExport2() throws Exception {

        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("java.lang.Integer");
        CtMethod m = cc.getDeclaredMethod("intValue");

        // 内部にフラグを持たせる
        CtField isBroken = new CtField(CtClass.booleanType, "isBroken", cc);
        isBroken.setModifiers(Modifier.PUBLIC);
        cc.addField(isBroken, CtField.Initializer.constant(false));

        // フラグが有効の場合にのみ加算
        m.insertBefore("if(isBroken) value++;");

        // 改造後のクラスファイルを出力
        cc.writeFile();

    }
```

mainクラスにはフラグの変更処理を追加します。

```java
public class A123 {
    public static void main(String[] args) {

        Integer a = Integer.valueOf(0);

        try {
            // コンパイルエラー回避のためにisBrokenをリフレクションで書き換える
            Integer.class.getField("isBroken").setBoolean(a, true);

            if (a == 1 && a == 2 && a == 3) {
                System.out.println(true);
            }

        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

    }
}
```

IntegerのisBrokenに対して値を変更しようとした場合、
普通に書き換えた場合にはフィールドが存在しないので、
コンパイルエラーとなってしまいます。
そのため、リフレクションで書き換えることによって存在しない場合に実行時エラーとなるようにしています。
リフレクションの恐ろしさがわかりますね…。

実行結果
また、実行時の引数に`-Xbootclasspath/p:.`を追加して実行すると、

```shell
true
```

無事にJavaでも`(a == 1 && a == 2 && a == 3)`を`true`にできました。

# まとめ
* Javassistでバイナリクラスの書き換えができます
* **標準ライブラリを変更したものをデプロイするとライセンス違反になる**
* リフレクションはやっぱり危ない