Maximum call stack size exceeded가 재귀를 안 쓴 코드에서 난 이유
문제 발생
재귀 함수를 쓴 적이 없는데 이 오류가 났습니다.
RangeError: Maximum call stack size exceeded엔진마다 문구가 다릅니다 — Chrome과 Safari는 위 메시지, Firefox는 InternalError: too much recursion입니다.
문제의 코드는 이랬습니다.
class Person {
set name(name) {
this.name = name; // setter 안에서 다시 setter를 호출한다
}
}
new Person().name = "지수"; // 스택 오버플로원인 분석
이 오류는 호출 스택이 한계를 넘었다는 뜻입니다. 함수 호출이 너무 깊어지면 발생하고, 원인은 명시적인 재귀만이 아닙니다.
setter 안에서 같은 속성에 대입. this.name = name이 다시 name setter를 호출합니다. 코드에 재귀가 보이지 않지만 실제로는 무한 재귀입니다.
종료 조건이 없거나 닿지 않는 재귀.
function loop(x) { loop(x + 1); } // 종료 조건 없음
function loop2(x) { if (x >= 1e12) return; loop2(x + 1); } // 닿기 전에 스택이 넘침서로를 호출하는 함수 쌍. a()가 b()를, b()가 a()를 부르면 한 함수만 봐서는 재귀가 보이지 않습니다.
깊은 자료구조의 순회. 순환 참조가 있는 객체를 재귀로 훑으면 끝나지 않습니다.
해결 방안
- 스택 트레이스에서 반복되는 패턴을 찾습니다. 같은 함수 이름이 계속 나오면 그 함수가 범인이고, 두 이름이 번갈아 나오면 상호 재귀입니다.
- setter는 다른 속성에 저장합니다.
class Person {
set name(name) { this._name = name; }
get name() { return this._name; }
}- 재귀에는 종료 조건과 진행 보장을 함께 둡니다. 종료 조건이 있어도 매 호출이 그쪽으로 가까워지지 않으면 소용없습니다.
- 깊이가 큰 순회는 반복문과 명시적 스택으로 바꿉니다. 호출 스택 대신 배열을 쓰면 한계가 메모리까지 늘어납니다.
function walk(root) {
const stack = [root];
while (stack.length > 0) {
const node = stack.pop();
visit(node);
stack.push(...node.children);
}
}- 순환 참조를 방문 집합으로 막습니다.
WeakSet에 방문한 객체를 기록하면 같은 노드로 되돌아가지 않습니다 — 깊은 복사나 직렬화를 직접 구현할 때 필요합니다. - 꼬리 호출 최적화에 기대지 않습니다. 명세에는 있지만 실제로 구현한 엔진이 제한적이라, "꼬리 재귀로 썼으니 괜찮다"는 가정은 서지 않습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.