Item 50 Make defensive copies when needed

返回目录

词句

  • it is immune to buffer overruns
    • immune adj. 免疫的;免于……的,免除的 n. 免疫者;免除者
    • overrun n. 泛滥成灾;超出限度 vt. 泛滥;超过;蹂躏 vi. 泛滥;蔓延
  • plague n. 瘟疫;灾祸;麻烦;讨厌的人 vt. 折磨;使苦恼;使得灾祸
  • you aren’t insulated from other classes without some effort on your part
    • insulate vt. 隔离,使孤立;使绝缘,使隔热
  • purport vt. 声称;意图;意指;打算 n. 意义,主旨;意图
  • While it is impossible for another class to modify an object’s internal state without some assistance from the object, it is surprisingly easy to provide such assistance without meaning to do so.
    • without meaning to do 无意间
  • It is, however, easy to violate this invariant by exploiting the fact that Date is mutable
    • exploit vt. 开发,开拓;剥削;开采 n. 勋绩;功绩
  • malicious mischief 故意损害他人财产
    • malicious adj. 恶意的;恶毒的;蓄意的;怀恨的
    • mischief n. 恶作剧;伤害;顽皮;不和
  • This would give the attacker free rein over all instances
    • rein n. 缰绳;驾驭;统治;支配 vt. 控制;驾驭;勒住 vi. 勒住马
  • No matter how malicious or incompetent
    • incompetent adj. 无能力的,不胜任的;不合适的;不适当的;无力的 n. 无能力者
  • without resorting to extralinguistic means such as native methods and reflection
    • extralinguistic [,ɛkstrəlɪŋ'gwɪstɪk] adj. 语言以外的;语言学以外的
  • Chances are, you should return a defensive copy
    • Chances are 有可能
  • There may be a performance penalty associated with defensive copying and it isn’t always justified
    • justified adj. 有正当理由的;合乎情理的;事出有因的 v. 调整(justify的过去分词);证明…正当
  • then it may be appropriate to dispense with defensive copying
    • dispense vt. 分配,分发;免除;执行 vi. 免除,豁免
  • There are some methods and constructors whose invocation indicates an explicit handoff of the object referenced by a parameter
    • handoff n. 手递手传球(美国橄榄球);传送 n. (Handoff)接力

Item 50 Make defensive copies when needed

One thing that makes Java a pleasure to use is that it is a safe language. This means that in the absence of native methods it is immune to buffer overruns, array overruns, wild pointers, and other memory corruption errors that plague unsafe languages such as C and C++. In a safe language, it is possible to write classes and to know with certainty that their invariants will hold, no matter what happens in any other part of the system. This is not possible in languages that treat all of memory as one giant array.

Even in a safe language, you aren’t insulated from other classes without some effort on your part. You must program defensively, with the assumption that clients of your class will do their best to destroy its invariants. This is increasingly true as people try harder to break the security of systems, but more commonly, your class will have to cope with unexpected behavior resulting from the honest mistakes of well-intentioned programmers. Either way, it is worth taking the time to write classes that are robust in the face of ill-behaved clients.

While it is impossible for another class to modify an object’s internal state without some assistance from the object, it is surprisingly easy to provide such assistance without meaning to do so. For example, consider the following class, which purports to represent an immutable time period:

// Broken "immutable" time period class
public final class Period {
  private final Date start;
  private final Date end;
  /**
  * @param start the beginning of the period
  * @param end the end of the period; must not precede start * @throws IllegalArgumentException if start is after end
  * @throws NullPointerException if start or end is null
  */
  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;
  }
  ...  // Remainder omitted
}

At first glance, this class may appear to be immutable and to enforce the invariant that the start of a period does not follow its end. It is, however, easy to violate this invariant by exploiting the fact that Date is mutable:

// Attack the internals of a Period instance
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // Modifies internals of p!

As of Java 8, the obvious way to fix this problem is to use Instant (or LocalDateTime or ZonedDateTime) in place of a Date because Instant (and the other java.time classes) are immutable (Item 17). Date is obsolete and should no longer be used in new code. That said, the problem still exists: there are times when you’ll have to use mutable value types in your APIs and internal representations, and the techniques discussed in this item are appropriate for those times.

To protect the internals of a Period instance from this sort of attack, it is essential to make a defensive copy of each mutable parameter to the constructor and to use the copies as components of the Period instance in place of the originals:

// Repaired constructor - makes defensive copies of parameters
public Period(Date start, Date end) {
  this.start = new Date(start.getTime());
  this.end   = new Date(end.getTime());
  if (this.start.compareTo(this.end) > 0)
    throw new IllegalArgumentException(this.start + " after " + this.end);
}

With the new constructor in place, the previous attack will have no effect on the Period instance. Note that defensive copies are made before checking the validity of the parameters (Item 49), and the validity check is performed on the copies rather than on the originals. While this may seem unnatural, it is necessary. It protects the class against changes to the parameters from another thread during the window of vulnerability between the time the parameters are checked and the time they are copied. In the computer security community, this is known as a time-of-check/time-of-use or TOCTOU attack [Viega01].

Note also that we did not use Date’s clone method to make the defensive copies. Because Date is nonfinal, the clone method is not guaranteed to return an object whose class is java.util.Date: it could return an instance of an untrusted subclass that is specifically designed for malicious mischief. Such a subclass could, for example, record a reference to each instance in a private static list at the time of its creation and allow the attacker to access this list. This would give the attacker free rein over all instances. To prevent this sort of attack, do not use the clone method to make a defensive copy of a parameter whose type is sub- classable by untrusted parties.

While the replacement constructor successfully defends against the previous attack, it is still possible to mutate a Period instance, because its accessors offer access to its mutable internals:

 // Second attack on the internals of a Period instance
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78); // Modifies internals of p!

To defend against the second attack, merely modify the accessors to return defensive copies of mutable internal fields:

// Repaired accessors - make defensive copies of internal fields
public Date start() {
  return new Date(start.getTime());
}
public Date end() {
  return new Date(end.getTime());
}

With the new constructor and the new accessors in place, Period is truly immutable. No matter how malicious or incompetent a programmer, there is simply no way to violate the invariant that the start of a period does not follow its end (without resorting to extralinguistic means such as native methods and reflection). This is true because there is no way for any class other than Period itself to gain access to either of the mutable fields in a Period instance. These fields are truly encapsulated within the object.

In the accessors, unlike the constructor, it would be permissible to use the clone method to make the defensive copies. This is so because we know that the class of Period’s internal Date objects is java.util.Date, and not some untrusted subclass. That said, you are generally better off using a constructor or static factory to copy an instance, for reasons outlined in Item 13.

Defensive copying of parameters is not just for immutable classes. Any time you write a method or constructor that stores a reference to a client-provided object in an internal data structure, think about whether the client-provided object is potentially mutable. If it is, think about whether your class could tolerate a change in the object after it was entered into the data structure. If the answer is no, you must defensively copy the object and enter the copy into the data structure in place of the original. For example, if you are considering using a client-provided object reference as an element in an internal Set instance or as a key in an internal Map instance, you should be aware that the invariants of the set or map would be corrupted if the object were modified after it is inserted.

The same is true for defensive copying of internal components prior to returning them to clients. Whether or not your class is immutable, you should think twice before returning a reference to an internal component that is mutable. Chances are, you should return a defensive copy. Remember that nonzero-length arrays are always mutable. Therefore, you should always make a defensive copy of an internal array before returning it to a client. Alternatively, you could return an immutable view of the array. Both of these techniques are shown in Item 15.

Arguably, the real lesson in all of this is that you should, where possible, use immutable objects as components of your objects so that you that don’t have to worry about defensive copying (Item 17). In the case of our Period example, use Instant (or LocalDateTime or ZonedDateTime), unless you’re using a release prior to Java 8. If you are using an earlier release, one option is to store the primitive long returned by Date.getTime() in place of a Date reference.

There may be a performance penalty associated with defensive copying and it isn’t always justified. If a class trusts its caller not to modify an internal component, perhaps because the class and its client are both part of the same package, then it may be appropriate to dispense with defensive copying. Under these circumstances, the class documentation should make it clear that the caller must not modify the affected parameters or return values.

Even across package boundaries, it is not always appropriate to make a defensive copy of a mutable parameter before integrating it into an object. There are some methods and constructors whose invocation indicates an explicit handoff of the object referenced by a parameter. When invoking such a method, the client promises that it will no longer modify the object directly. A method or constructor that expects to take ownership of a client-provided mutable object must make this clear in its documentation.

Classes containing methods or constructors whose invocation indicates a transfer of control cannot defend themselves against malicious clients. Such classes are acceptable only when there is mutual trust between a class and its client or when damage to the class’s invariants would harm no one but the client. An example of the latter situation is the wrapper class pattern (Item 18). Depending on the nature of the wrapper class, the client could destroy the class’s invariants by directly accessing an object after it has been wrapped, but this typically would harm only the client.

In summary, if a class has mutable components that it gets from or returns to its clients, the class must defensively copy these components. If the cost of the copy would be prohibitive and the class trusts its clients not to modify the components inappropriately, then the defensive copy may be replaced by documentation outlining the client’s responsibility not to modify the affected components.

当当当当,恭喜你,已经看到这里了,真是棒棒哒,希望这篇文章让你有所收获,如果有哪些不懂的地方,欢迎在评论中提出,大家一起探讨,当然,如有你碰到了一些自己不熟悉但是上方没有列出的词句,欢迎在下方评论中给出你查到的解释,这样也可以帮助到后来看文章的人,感激不尽

最后,如果哪位大款能施舍个一分两分的,也是极好的,哈哈哈

你可能感兴趣的:(Item 50 Make defensive copies when needed)