Skip to content

Latest commit

 

History

History
224 lines (159 loc) · 6.8 KB

적시에_방어적_복사본을_만들라.md

File metadata and controls

224 lines (159 loc) · 6.8 KB

아이템 50 : 적시에 방어적 복사본을 만들라

자바는 안전한 언어다

  1. 네이티브 메서드를 사용하지 않음
    > 버퍼 오버런, 배열 오버런, 와일드 포인터 같은 메모리 충돌 오류에서부터 안전

  2. 자바의 클래스는 불변식이 지켜짐


왜 방어적으로 프로그래밍 해야할까?

  1. 아무리 자바라고 해도 다른 클래스로부터의 침범을 아무런 노력없이 막을 수 있는 것은 아님

  2. 악의적인 의도를 가진 사람들이 시스템의 보안을 뚫으려는 시도가 늘고 있음

  3. 평범한 프로그래머도 순전히 실수로 클래스를 오작동하게 만들 수 있음

🌟 클라이언트가 불변식을 깨뜨리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍 해야 함


객체가 자기도 모르게 내부를 수정하도록 허락하는 경우

예시 : 시작 시간(start)이 종료 시간(end) 보다 빨라야 하는 Period 객체

😣 첫번째 코드 : 불변인척 하는 중

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 + "가 " + end + "보다 늦다."
			);
		}
		this.start = start;
		this.end = end;
	}

	public Date start() {
		return start;
	}

	public Date end() {
		return end;
	}
}

🤩 첫번째 공격 : Date가 가변이다!!

Date start = new Date();
Date end = new Date();
Period period = new Period(start, end);
end.setYear(78); // period 내부 변경 성공!

자바 8 이후로 Date 대신 불변 인스턴스를 사용하면 쉽게 해결할 수 있음
(혹은 LocalDateTime 이나 ZonedDateTime 을 사용해도 됨)

Date 는 낡은 API 이니 새로운 코드를 작성할 때는 더 이상 사용하면 안됨

코드를 작성할 때 setYear() 에 취소선과 함께 'setYear(int) is deprecated' 라고 뜸

변경 불가능을 위해 사용을 막은 것인지 찾아보니,
국제화와 잘 맞지 않고, 월(Month) 계산이 불편하고, mutable 하다는 이유로
더 이상 사용되지 않는다고 함

그래서 자바 8에 추가된 것이 LocalDate, LocalTime, LocalDateTime !

Date 는 final 이 아니지만, 해당 객체들은 final 임

그렇지만, 예전에 작성된 낡은 코드들은?

😫 두번째 코드 : 여전히 불변인척 하는 중

생성자에서 받은 가변 매개변수 각각을 방어적으로 복사
Period 인스턴스 안에서 원본이 아닌 복사본 사용

public final class Period {
	private final Date start;
	private final Date end;

	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 + "가 " + this.end + "보다 늦다."
			);
		}
	}

	public Date start() {
		return start;
	}

	public Date end() {
		return end;
	}
}

매개변수의 유효성 검사 전 방어적 복사본을 만들고, 복사본으로 유효성 검사

멀티스레드 환경에서 원본 객체의 유효성 검사 후 복사본을 만드는 찰나에
다른 스레드가 원본 객체를 수정할 위험이 있음

> 검사시점/사용시점(time-of-check/time-of-use) 공격 혹은 TOCTOU 공격이라 함

매개변수가 제 3자에 의해 확장될 수 있는 타입이라면 방어적 복사본을 clone을 사용해 만들면 안됨

Date 는 final이 아니므로 clone 이 Date 가 정의한 것이 아닐 수 있음
즉, clone이 악의를 가진 하위 클래스의 인스턴스를 반환할 수도 있음

제 3자에 의해 확장될 수 있는 타입 = final 클래스가 아닌 경우
clone() 을 사용한 예시

전달 받은 객체가 Date가 아니라 이를 상속한 객체(Data2)의 clone이 실행될 수 있음

public Period(Date start, Date end) {
		this.start = (Date)start.clone();
		this.end = (Date)end.clone();

		if (this.start.compareTo(this.end) > 0) {
			throw new IllegalArgumentException(
				this.start + "가 " + this.end + "보다 늦다."
			);
		}
	}
class Date2 extends Date {
	public Date2() {
		super();
	}

	@Override
	public Object clone() {
		System.out.println("나지롱");
		return super.clone();
	}
}
public static void main(String[] args) {
	Date start = new Date();
	Date2 end = new Date2();
	Period period = new Period(start, end);
	end.setYear(78);
}
스크린샷 2022-03-06 오전 1 50 56

🤩 두번째 공격 : 접근자 메서드가 내부의 가변정보를 드러낸다!!

Date start = new Date();
Date end = new Date();
Period period = new Period(start, end);
period.end().setYear(78); // period 내부 변경 성공!

두번째 공격을 막으려면, 단순히 접근자가 가변 필드의 방어적 복사본을 반환하면 됨

😎 최종 코드 : 불변인척 아니고 불변

public Date start() {
	return new Date(start.getTime);
}

public Date end() {
	return new Date(end.getTime);
}

새로운 접근자까지 갖추면 Period는 완벽한 불변임

시작시각이 종료시각보다 나중일 수 없다는 불변식을 위배하게 할 방법은 없음
예외 : 네이티브 메서드나 리플렉션 같은 언어 외적인 수단을 동원하는 경우

Java Native method
다른 언어로 작성된 코드를 자바에서 호출하도록 만들어진 규약
현재는 C/C++ 에 대한 호출만을 정확하게 지원

Java Reflection
객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법

자신 말고는 가변 필드에 접근할 방법이 없고, 모든 필드가 객체 안에 완벽히 캡슐화 됨

생성자와 달리 접근자 메서드에서는 방어적 복사에 clone을 사용해도 됨

Period가 가지고 있는 Date 객체가 java.util.Date 임이 확실하기 때문
하지만 인스턴스 복사에는 일반적으로 생성자나, 정적 팩터리를 쓰는 것이 좋음 (아이템 13)

어떻게 사용해야 할까?

> 클래스가 클라이언트로부터 받는 혹은 클라이언트로 반환하는 구성요소가 가변이라면
그 요소는 반드시 방어적으로 복사해야 함

> 복사 비용이 너무 크거나, 클라이언트가 그 요소를 잘못 수정할 일이 없음을 신뢰한다면
방어적 복사를 수행하는 대신 구성요소를 수정했을 때의 책임이 클라이언트에 있음을 문서에 명시해야 함