2021/03/21

[筆記] Effective Java #50 必要時進行保護性拷貝

Effective Java 3rd 簡體中文版筆記 #50 必要時進行保護性拷貝
Java 是一門安全的語言 (safe language),它對於指標及記憶體的管理上,相較於 C/C++ 容易許多。即使在安全的語言中,有時仍須採取保護手段,假設 class 的調用者會不計代價地破壞該 class 的約束規則,所以開發者在必要時需作出保護性的設計,確保在被破壞的情況下,依然正常運作。
Periodfinal 類,裡面的兩個 Date 也設定為 final,沒有 setter,好像不會有問題。
public final class Period {
    private final Date start;
    private final Date end;
    
    public Period(Date start, Date end) {
        if (start.compareTo(end) > 0)
            throw new IllegalArgumentException(start + " after " + end);
        this.start = start;
        this.end = end;
    }
    
    public Date start() { return start; }
    public Date end() { return end; }
}
其實將 startend 傳入 Period 後,再對 end 作修改,這樣狀態就被改變了,從 Java 8 開始,Date 已漸漸由 Instant 取代。要保護 Period 實體避免受到這種攻擊,對於 constructor 的每個可變參數進行保護性拷貝 (defensive copy) 是必要的。
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // Modifies internals of p!
先進行保護性拷貝,再進行參數有效性的檢查,檢查的對象是拷貝後的對象,不是針對原始對象檢查。這樣是想要避免參數在檢查有效性時,被其它 thread 修改的機會。
拷貝沒有選擇 clone,反而重新生成實體的原因為,對於有機會被繼承的 class (non-final class),攻擊者有可能繼承後覆寫 clone,你無法確定 clone 回傳的是否為標準 class 或是被攻擊者實作的 subclass。
public final class Period {
    private final Date start;
    private final Date end;
    
    public Period(Date start, Date end) {
        if (start.compareTo(end) > 0)
            throw new IllegalArgumentException(start + " after " + end);
        this.start = new Date(start);
        this.end = new Date(end);
    }
    
    public Date start() { return new Date(start); }
    public Date end() { return new Date(end); }
}
TOCTOU attack
https://winsys88.wordpress.com/2011/08/26/檢查時間使用時間(time-of-check-toc-time-of-use-tou)/
什麼時候需要保護性拷貝? 如果物件的內部資料中有可變類存在,且一旦客戶修改該實體後,會破壞邏輯,這時就應該在 constructor 中對傳入參數使用保護性拷貝。另外就是在回傳可變類的方法時,為避免讓客戶拿到可變類,所以必須要作保護性拷貝。如果客戶端可以保證不會修改可變類,或是即使可變類被修改也不會影響邏輯,那就不需要保護性拷貝。

轉載請註明原文網址 https://cookieandcoketw.blogspot.com/2021/03/effective-java-50-defensive-copy.html

沒有留言:

張貼留言