eval() 없이 자바스크립트 계산기 만들기

eval() 없이 자바스크립트 계산기 만들기

D
dongAuthor
5 min read

자바스크립트로 코드를 작성하다 보면 동적으로 코드를 실행해야 할 때가 있습니다. 많은 개발자가 이럴 때 eval() 함수를 떠올리곤 합니다. eval()은 문자열을 코드로 인식하고 실행해주는 편리한 기능을 제공하죠. 하지만 이 편리함 뒤에는 심각한 보안 위험과 성능 저하 문제가 숨어 있습니다.

이 글에서는 eval()을 사용하는 것이 왜 위험한지 알아보고, 더 안전하고 효율적인 대안인 new Function()을 사용해 동적 코드를 실행하는 방법을 자세히 살펴보겠습니다. 이 글을 통해 여러분은 더 안전하고 견고한 코드를 작성하는 능력을 기를 수 있을 거예요.

eval()이란 무엇이고 왜 위험할까요?

eval()은 전달받은 문자열을 자바스크립트 코드로 해석하고 실행하는 전역 함수입니다. 예를 들어, eval("2 + 2")는 숫자 4를 반환합니다. 이처럼 문자열로 표현된 코드를 동적으로 실행할 수 있어 계산기 로직 등을 구현할 때 유용해 보일 수 있습니다.

하지만 eval()의 사용은 여러 가지 심각한 문제를 야기할 수 있습니다.

eval()에 대해 더 자세히 알아봅시다 !

1. 보안 취약점

eval()의 가장 큰 문제는 보안입니다. 이 함수는 호출된 스코프의 권한으로 코드를 실행하기 때문에, 만약 사용자가 입력한 값을 검증 없이 eval()로 실행한다면 악의적인 코드가 그대로 실행될 수 있습니다.

예를 들어, 사용자가 입력한 값을 eval()로 처리하는 코드가 있다고 가정해 봅시다.

var userContent = getUserInput(); // 사용자로부터 입력받는 함수
eval(userContent); // 위험! 사용자가 입력한 내용이 그대로 코드로 실행될 수 있습니다.

만약 사용자가 "alert('해킹당했습니다!')"와 같은 문자열을 입력한다면, 해당 스크립트가 그대로 실행되어 예상치 못한 동작을 일으킵니다. 이는 웹사이트의 중요 정보 유출이나 서비스 장애로 이어질 수 있는 심각한 보안 위협입니다.

2. 성능 저하

eval()은 자바스크립트 엔진의 코드 최적화를 방해합니다. 최신 자바스크립트 엔진(JIT 컴파일러 등)은 코드를 실행하기 전에 분석하고 최적화하는 과정을 거칩니다. 하지만 eval()이 사용되면, 엔진은 eval() 내부의 코드가 어떤 변수를 참조하거나 수정할지 예측할 수 없게 됩니다.

결과적으로 엔진은 변수 이름 조회와 같은 작업을 느린 방식으로 처리해야 하고, 잠재적으로 컴파일된 코드를 다시 해석해야 할 수도 있습니다. 이는 전체적인 애플리케이션의 성능 저하로 이어집니다.

더 안전한 대안: new Function()

eval()의 위험성을 피하면서 코드를 동적으로 실행할 수 있는 더 나은 방법이 있습니다. 바로 new Function() 생성자를 사용하는 것입니다.

new Function()은 매개변수 목록과 함수 본문을 문자열 형태로 받아 새로운 함수 객체를 생성하고 반환합니다.

new Function() 사용법

기본적인 문법은 다음과 같습니다.

let func = new Function ([arg1, arg2, ...argN], functionBody);

예를 들어, 두 숫자를 더하는 함수를 new Function()으로 만들어 보겠습니다.

const add = new Function('a', 'b', 'return a + b');
console.log(add(2, 3));  // 출력: 5

이처럼 new Function()을 사용하면 문자열로 정의된 로직을 가진 함수를 동적으로 생성할 수 있습니다.

new Function()은 왜 더 안전할까요?

new Function()eval()의 가장 중요한 차이점은 **실행 컨텍스트(Execution Context)**에 있습니다.

1. 실행 컨텍스트의 차이

  • eval(): 현재 실행 중인 **로컬 스코프(Local Scope)**에서 코드를 실행합니다. 즉, eval()이 호출된 함수 내의 지역 변수에 접근하고 수정할 수 있습니다.
  • new Function(): 항상 **전역 스코프(Global Scope)**에서 실행되는 함수를 생성합니다. new Function()으로 만들어진 함수는 자신이 생성된 시점의 로컬 스코프에 접근할 수 없습니다.

2. 범위 제한으로 인한 보안 강화

new Function()으로 생성된 함수는 클로저(closure)를 형성하지 않으며, 외부 렉시컬 환경(Lexical Environment)을 참조하지 않습니다. 오직 전역 스코프만 참조할 수 있죠.

다음 예제를 통해 차이점을 명확히 확인해 봅시다.

function demo() {
  let localVariable = '저는 지역 변수입니다.';

  // eval()은 로컬 스코프에 접근 가능
  eval("console.log(localVariable);"); // 출력: "저는 지역 변수입니다."

  // new Function()은 로컬 스코프에 접근 불가
  try {
    const myFunction = new Function("console.log(localVariable);");
    myFunction();
  } catch (e) {
    console.error(e); // 출력: ReferenceError: localVariable is not defined
  }
}

demo();

위 코드에서 볼 수 있듯이, eval()demo 함수의 지역 변수인 localVariable에 접근할 수 있지만, new Function()으로 생성된 함수는 접근할 수 없어 ReferenceError가 발생합니다. 이처럼 new Function()은 외부 변수에 대한 접근을 원천적으로 차단하므로, 악성 코드가 지역 변수를 통해 시스템에 영향을 미치는 것을 방지할 수 있습니다.

3. 성능상 이점

eval()이 자바스크립트 엔진의 최적화를 방해하는 반면, new Function()은 상대적으로 성능에 더 유리할 수 있습니다. new Function()으로 생성된 코드는 별도의 함수 몸체 안에 존재하며, 로컬 스코프를 오염시키지 않기 때문에 엔진이 코드를 더 쉽게 분석하고 최적화할 수 있습니다.

물론, new Function() 역시 런타임에 문자열을 파싱하고 컴파일하는 과정이 필요하므로, 정적으로 함수를 선언하는 것보다는 느립니다. 하지만 동적으로 코드를 실행해야 하는 상황에서는 eval()보다 훨씬 나은 선택입니다.

new Function() 사용 시 모범 사례

new Function()eval()보다 안전하지만, 여전히 사용자의 입력을 직접 코드 본문으로 사용하는 것은 위험할 수 있습니다. 동적 코드를 생성할 때는 항상 다음 지침을 따르는 것이 좋습니다.

  1. 사용자 입력을 직접 사용하지 마세요: 계산기 로직을 만든다면, 사용자가 입력한 전체 수식을 그대로 new Function()에 넘기지 마세요. 대신, 입력값을 파싱하고 안전성이 검증된 연산자(+, -, *, /)와 숫자만 조합하여 함수 본문을 생성해야 합니다.
  2. 엄격 모드(Strict Mode) 사용: new Function('"use strict"; ...')와 같이 함수 본문 시작 부분에 'use strict'를 추가하여 더 안전한 코드를 작성하세요. 엄격 모드는 일부 위험한 문법을 에러로 처리해 줍니다.
  3. 대안을 먼저 고려하세요: 동적으로 코드를 생성해야 하는 이유에 대해 다시 한번 생각해 보세요. 많은 경우, 정적인 함수나 데이터 구조(예: 객체나 맵)를 사용하여 동일한 기능을 더 안전하고 효율적으로 구현할 수 있습니다.

더 나은 코드를 향하여

eval()은 강력하고 편리한 기능이지만, 그 이면에는 심각한 보안 및 성능 리스크가 존재합니다. MDN 웹 문서에서는 eval()을 "절대 사용하지 말 것!(Never use eval!)"이라고 강력하게 경고하고 있습니다.

다행히 우리에게는 new Function()이라는 더 안전하고 효율적인 대안이 있습니다. new Function()은 실행 스코프를 전역으로 제한하여 eval()의 주요 보안 취약점을 해결하고, 코드 최적화에도 유리합니다.

물론 가장 좋은 방법은 동적으로 코드를 생성하는 상황 자체를 피하는 것입니다. 하지만 꼭 필요한 경우라면, 이제부터는 eval() 대신 new Function()을 사용하여 더 안전하고 견고한 코드를 작성해 보세요. 여러분의 코드는 한층 더 높은 수준으로 발전할 것입니다. :)