クラスファイルの ACC_SUPER フラグの意味

Java クラスファイルのアクセスフラグの一つに `ACC_SUPER` というものがあります。
[Class access and property modifiers - Java Virtual Machine Specification](https://docs.oracle.com/javase/specs/jvms/se22/html/jvms-4.html#jvms-4.1-200-E.1)

このフラグの意味は、ドキュメントに以下のように書かれています。
 
> `ACC_SUPER` フラグは、このクラスまたはインタフェースに `invokespecial` 命令 ([§invokespecial](https://docs.oracle.com/javase/specs/jvms/se22/html/jvms-6.html#jvms-6.5.invokespecial)) が出現した場合に、2 つの代替セマンティクスのどちらが表現されるかを示します。
> [Chapter 4. The class File Format](https://docs.oracle.com/javase/specs/jvms/se22/html/jvms-4.html#jvms-4.1)


これは何を意味しているのでしょうか。

結論としては、**もはやなんの意味もありません。**
以前はこのフラグの有無で少し挙動が変わっていたのですが、Java 7 Update 13 から （正式には Java 8 から）クラスファイルにこのフラグがセットされていてもいなくても同じ挙動になったためです。


> Java SE 8 以降では、Java 仮想マシンは、クラス ファイル内のフラグの実際の値やクラスファイルのバージョンに関係なく、**すべてのクラス ファイルで `ACC_SUPER` フラグが設定されているものと見なします。**
> [Chapter 4. The class File Format](https://docs.oracle.com/javase/specs/jvms/se22/html/jvms-4.html#jvms-4.1)

そのため、このフラグを気にする必要はありません。

---

以下、Java 考古学者向けの補足です。

# `ACC_SUPER` 誕生の経緯

この StackOverflow の回答を元に、補足しながら説明します。

https://stackoverflow.com/a/8950564/1932017

元々、`ACC_SUPER` は `invokespecial` の挙動を変更するために生まれました。

## Java 1.0.2 の `invokespecial` の仕様

`invokespecial` はコンストラクタの呼び出しや、**オーバーライドした親クラスのメソッド呼び出し**などで使われる命令です。**Java 1.0.2 の仕様では、`invokespecial` 命令は単純に指定されたメソッドを呼び出すとなっていました。**


例えば、以下のクラスは Java の最初のバージョン（1.0.2）では `C` クラスの `super.foo()` の部分が `invokespecial` 命令で `A.foo()` を呼び出すというコンパイル結果になります。実行すると `C.foo()` → `A.foo()` の順番で実行されるので "CA" と表示されます。[^invoke]

[^invoke]: ちなみに、検証したところ Java 1.2 からは `invokespecial` の実行対象が `B:foo()` に変わっていました。

```java:A.java
class A {
    public void foo() {
        System.out.print("A");
    }
}
```

```java:B.java
class B extends A {
    // foo メソッドを A から継承するが、 オーバーライドしない
}
```

```java:C.java
class C extends B {
    public void foo() {
        System.out.print("C");
        super.foo();   // invokespecial A.foo():V
    }
}
```

```java:Main.java
public class Main {
    public static void main(String[] args) {
        new C().foo();
    }
}
```

![invokespecial](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205989/d230ca13-a9c3-eaef-e77a-06e02d4e4731.png)

## Java 1.1 での `invokespecial` の仕様変更

ところが、「`invokespecial` は指定されたメソッドを呼び出す」という仕様だと、オーバーライドが追加された場合に問題が起こることが発覚しました。
例えば、上記の `B` クラスに以下のように `foo()` メソッドが追加された場合に問題が起きます。

```java:B.java
class B extends A {
    public void foo() {
        System.out.print("B");
        super.foo();
    }
}
```

この場合、**`C` クラスの `super.foo()` では追加された `B.foo()` が実行されるべき**ですが、`C` クラスをコンパイルし直さない限り `invokespecial A.foo():V` となっているので **依然として `A.foo()` が実行されてしまっていました。**
![invokespecial (ACC_SUPERなし)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205989/3d6a9cbc-7559-f992-1449-89beb1f2a37e.png)

そこで、Java 1.1 で `invokespecial` の仕様に手を加えて、**`invokevirtual` と同様にオーバーライドを考慮して呼び出すメソッドを決めるようになりました。** 今回の場合だと、`invokespecial A.foo():V` という命令で `A.foo()` ではなく、`C` クラスのスーパークラスから順に `foo()` を探すようになり、 `B.foo()` があるのでそれを呼び出すようになりました。 
![invokespecial (ACC_SUPERあり)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205989/45635f1b-e66a-f198-f899-c2ec5943010a.png)

しかし、単純に仕様を変更すると些細な違いですが Java 1.0.2 と挙動が変わり、互換性がなくなってしまいます。
そのため、以下の仕様となりました。
* クラスファイルにアクセスフラグ `ACC_SUPER` を追加
    * **Java 1.1 以降のコンパイラでは、このフラグを必ず設定する**
* `ACC_SUPER` の有無で、そのクラス内の `invokespecial` 命令の挙動を変更する
    * `ACC_SUPER` フラグが設定されている（= Java 1.1 以降でコンパイルした）
        * 指定されたメソッドの**オーバーライドを考慮して**呼び出す
            * 上記の例では、`invokespecial A.foo():V` で `B.foo()` を呼び出し、"CBA" という表示になる
    * `ACC_SUPER` フラグが設定されていない（= Java 1.0.2 でコンパイルした）
        * Java 1.0.2 と同じ挙動にする
        * 指定されたメソッドを**オーバーライドを考慮せず**呼び出す
            * 上記の例では、`invokespecial A.foo():V` で `A.foo()` を呼び出し、"CA" という表示になる

## `ACC_SUPER` の変更

このような経緯で誕生した `ACC_SUPER` ですが、**Java 7 Update 13 から（正式な仕様としては Java 8 から）クラスファイルの `ACC_SUPER` が設定されていなくても、`ACC_SUPER` フラグが設定されているものとして扱うようになりました。**

つまり、**Java 1.0.2 でコンパイルした（`ACC_SUPER` フラグが設定されていない）クラスファイルの挙動が変わりました。** 具体的には、`C` クラスの `invokespecial A.foo():V` で `ACC_SUPER` フラグが設定されていないので `A.foo()` を呼び出していたのが、Java 7 Update 13 からは `B.foo()` を呼び出すように変わりました。

:::note info
この変更理由は明らかにされていませんが、上記の StackOverflow のコメントではおそらくセキュリティ上の問題があったからではないかと言われています。
具体的な例として、以下のブログでは Java 7 以降では Thread クラスのクローンができないように `Thread.clone()` で `CloneNotSupportedException` をスローするように実装されているにもかかわらず、それをスキップして `Object.clone()` を呼べてしまうという点が指摘されています。
[JDK 7 Thread Cloning Vulnerability - IKVM.NET Weblog](https://web.archive.org/web/20210919162220/http://weblog.ikvm.net/PermaLink.aspx?guid=23cced47-ccdb-460d-acc9-ce16154ab6a5)
:::


# まとめ

基本的に Java 仮想マシン仕様では後方互換性を重視しているので、どのバージョンでコンパイルされたクラスファイルであっても、最新の Java 仮想マシンで実行した際には変わらずに動くようになっています。

それにもかかわらず、一度入れた仕様を打ち消す変更をして挙動を変えるというのはとても珍しいです。もしかしたら、唯一かもしれません。