clone()メソッドからオーバーライド可能なメソッドを呼び出すのは危険である。第1に、悪意あるサブクラスがメソッドをオーバーライドし、clone()メソッドの動作を操作する危険がある。第2に、サブクラスは信頼できるものだったとしても、構築完了前の、初期化途中のクローンオブジェクトにアクセスしてしまう(そして変更を加えてしまう)危険がある。いずれの場合も、サブクラスは、クローンして作られたオブジェクトとクローンされる側のオブジェクトのいずれかまたは両方を、矛盾した状態にする可能性がある。それゆえ、cloneメソッドはfinalあるいはprivate宣言されたメソッドのみを呼び出すべきである。
このルールは「MET05-J. コンストラクタにおいてオーバーライド可能なメソッドを呼び出さない」と密接に関連している。
違反コード
次の違反コード例は、CloneExampleとSubの2つのクラスを示している。クラスCloneExampleはオーバーライド可能なdoSomething()メソッドを呼び出す。オーバーライドされる側のメソッドではクッキーの値が設定され、オーバーライドする側のメソッドではドメイン名が設定される。サブクラスSubのdoSomething()メソッドは、ポリモーフィズムのため実行時に2度、誤って呼び出される。最初の呼出しはCloneExample.clone()によって、2度目の呼出しはSub.clone()によって行われる。そのため、クッキーの値は初期化されず、ドメイン名だけが2度初期化される。
さらに、サブクラスは矛盾した状態にあるクローンを見るだけでなく、矛盾した状態のコピーが作成されるようにクローンに変更を加えている。このような状況が発生する理由は、deepCopy()メソッドがdoSometing()メソッドの後で呼び出され、オーバーライドするdoSomething()がオブジェクトに誤って変更を加えるからである。
class CloneExample implements Cloneable { HttpCookie[] cookies; CloneExample(HttpCookie[] c) { cookies = c; } public Object clone() throws CloneNotSupportedException { final CloneExample clone = (CloneExample) super.clone(); clone.doSomething(); // オーバーライド可能なメソッドを呼びだす clone.cookies = clone.deepCopy(); return clone; } void doSomething() { // オーバーライド可能 for (int i = 0; i < cookies.length; i++) { cookies[i].setValue("" + i); } } HttpCookie[] deepCopy() { if (cookies == null) { throw new NullPointerException(); } // ディープコピー HttpCookie[] cookiesCopy = new HttpCookie[cookies.length]; for (int i = 0; i < cookies.length; i++) { // 配列の各要素を手作業でコピーする cookiesCopy[i] = (HttpCookie) cookies[i].clone(); } return cookiesCopy; } } class Sub extends CloneExample { Sub(HttpCookie[] c) { super(c); } public Object clone() throws CloneNotSupportedException { final Sub clone = (Sub) super.clone(); clone.doSomething(); return clone; } void doSomething() { // 誤って実行される for (int i = 0;i < cookies.length; i++) { cookies[i].setDomain(i + ".foo.com"); } } public static void main(String[] args) throws CloneNotSupportedException { HttpCookie[] hc = new HttpCookie[20]; for (int i = 0 ; i < hc.length; i++){ hc[i] = new HttpCookie("cookie" + i,"" + i); } CloneExample bc = new Sub(hc); bc.clone(); } }
オブジェクトの浅いコピーに対してオーバーライド可能なメソッドが実行されると、コピー元のオブジェクトも変更されてしまう。
適合コード
以下の適合コードでは、doSomething()メソッドとdeepCopy()メソッドの両方をfinal宣言し、オーバーライドされるのを防止している。
class CloneExample implements Cloneable { final void doSomething() { // ... } final HttpCookie[] deepCopy() { // ... } // ... }
オーバーライドされたメソッドの呼出しを防止する別の方法としては、これらのメソッドを private 宣言する、あるいはこれらのメソッドを含むクラスを final 宣言する、という方法も考えられる。
リスク評価
構築途中のクローンのオーバーライド可能なメソッドの呼出しは、クラスの内部状態を悪意あるコードに晒す恐れがある。また、初期化途中のクローンを信頼できるコードに公開することで、クローンオブジェクトやクローンされるオブジェクト、あるいはその両方の状態が破壊される可能性が生まれ、クラスの不変条件が侵害される恐れもある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
MET06-J | 中 | 中 | 低 | P12 | L1 |
自動検出
自動検出は容易である。
参考文献
[Bloch 2008] | Item 11. Override clone judiciously |
[Gong 2003] |
翻訳元
これは以下のページを翻訳したものです。
MET06-J. Do not invoke overridable methods in clone() (revision 58)