도당탕탕
Item50: 필요하면 defensive 복사를 하라. 본문
Java는 C나 C++에 비해 안전한 언어라고 할 수 있다. 하지만 우리가 작성한 클래스의 클라이언트에서 의도적이든, 실수로든 불변성을 파괴할 수 있음을 명심해야 한다.
첫 번째 가능한 공격
아래 코드는 클라이언트가 불변성을 파괴할 수 있는 예시이다.
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;
}
//... 나머지 생략
}
여기서 문제점은 Date
타입이 mutable 하다는 점이다.
Date start = new Date();
Date end = new Date();
Period p = new Period(start,end);
end.setYear(78); // 여기서 p의 내부 필드값을 변경해버렸다!
Java 8에서 이러한 문제를 해결하는 방법은 immutable type인 Instant
(또는 Local-DateTime
또는 ZonedDateTime
) 를 대신 사용하는 것이다. 사실상 Date
는 더이상 사용되어서는 안 된다.
하지만 이러한 API를 사용하지 않고도 보호하는 방법이 있다. 바로 생성자에서 mutable parameter에 대한 defensive copy를 생성해서, Period
인스턴스의 구성 필드로 사용하는 것이다.
바로 다음처럼 말이다.
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);
}
여기서 Date 객체를 복사한 다음에 복사된 객체를 대상으로 validation을 해야 한다. 그렇지 않으면, TOCTOU 공격에 취약해진다.
TOCTOU attack : 파라미터가 체크되는 시간과 복사되는 시간 사이에 다른 스레드에서 파리미터값에 변경을 가하는 것을 말한다.
그리고 여기서 파라미터에 파라미터 타입의 subclass 타입이 들어올 수 있는 경우, clone
함수를 사용해서는 안 된다. 왜냐하면 해당 subclass에서 레퍼런스를 저장할 수 있기 때문이다.
두 번째 가능한 공격
여전히 Period
의 accessor 함수를 통해서 내부 값을 변경할 수 있다.
Date start = new Date();
Date end = new Date();
Period p = new Period(start,end);
p.end().setYear(78);
이를 방어하기 위해서, accessor에서 defensive copy를 반환하도록 하면 된다.
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
Defensive copy를 하지 않아도 좋은 경우
- 만약 클래스가 클라이언트가 절대 자신의 내부 값을 변경하지 않으리라고 확신할 수 있다면 (아마 클래스와 클라이언트가 같은 패키지에 있는 경우), 클래스 문서에 호출 부분에서 절대 영향받는 파라미터를 수정해서는 안 된다고 명시해야 한다.
- 클래스의 불변성이 파괴되더라도, 하나의 클라이언트에게만 타격이 있는 경우, 예를 들어 wrapper class pattern을 들 수 있다. 예를 들어, wrapper class는 wrapped class의 불변성을 파괴할 수 있지만, 오직 자신만이 영향을 받는다.
정리
만약 클래스에 클라이언트로부터 값을 읽거나, 클라이언트에게 리턴하는 mutable component가 있다면, 클래스는 이 component들을 defensive copy 해야 한다. 만약 클라이언트를 신뢰할 수 있거나, copy 비용이 용납되지 않는 경우에는 문서로 해당 component는 수정되어서는 안 된다고 명시해야 한다.
'JAVA' 카테고리의 다른 글
Item52 : 오버로딩을 현명하게 사용하라. (0) | 2023.01.09 |
---|---|
Item51 : 메소드 시그니처를 신중히 설계하라 (0) | 2023.01.09 |
Item49 : 매개변수가 유효한지 검사하라 (0) | 2023.01.06 |
Item48 : STREAM을 병렬로 만들때는 주의하라. (0) | 2023.01.05 |
Item47 : 반환 타입으로는 스트림보다 컬렉션이 낫다 (2) | 2023.01.05 |