OSSCA Chromium

기여 목록 / 8361850

Fix parseInt radix-32 exponent overflow returning 0 instead of Infinity

V8fixv8/v8Merged

문제 설명

  • issue url: https://crbug.com/547118098
  • parseInt(s, 32) 처럼 radix가 2의 거듭제곱일때 V8은 InternalStringToIntDouble() 함수를 통과합니다
  • exponent 는 int, number는 int64_t 자료형을 가집니다
  • 어차피 반환할때 double로 cast해서 반환하고 double의 가수는 53비트입니다
  • 그래서 V8엔진은 number가 53비트에 도달하면 건드리지않고, 남은 문자를 셀때마다 exponent에 5를 더합니다
  • 예를들어 이진수로 11111 를 예시로 들었을때, '11111'.repeat(11) => 55비트 => 53비트를 넘었으니 number는 상위 53비트를 갖고 하위 2비트는 exponent = 2로 기록됩니다. 이를 일반화하면

E = 2 + 5 × (N - 11) = 5N - 53

  • N = 429496740을 넣으면

E = 5 × 429,496,740 - 52 = 2,147,483,648 = 2^31

  • 해당 값이 INT_MAX보다 1 큽니다. 이 경우 Infinity로 표기되어야하는데, 오버플로우 되어 −2,147,483,648 값이 되어버리고, 이게 마지막 리턴

return std::ldexp(static_cast(number), exponent);

  • 여기를 통과하며 0이 되어버립니다 (지수 -21억은 double로 표현 불가 => 언더플로 => 0으로 표기)

해결 내용

  • 지수를 std::numeric_limits<double>::max_exponent까지만 누적하도록 상한을 걸었습니다.
      int dropped_bits_mask = ((1 << overflow_bits_count) - 1);
      int dropped_bits = static_cast<int>(number) & dropped_bits_mask;
      number >>= overflow_bits_count;
      exponent = overflow_bits_count;

+      // Cap exponent to avoid int overflow on huge digit runs
+      constexpr int kMaxExponent = std::numeric_limits<double>::max_exponent;
      bool zero_tail = true;
      while (true) {
        ++current;
        if (current == end || !isDigit(*current, radix)) break;
        zero_tail = zero_tail && *current == '0';
+        if (exponent <= kMaxExponent) exponent += radix_log_2;
      }

      if (!allow_trailing_junk && AdvanceToNonspace(&current, end)) {
        return JunkStringValue();
      }

발굴 과정

  • 이슈 확인 중에 발견함

테스트 방법

  • 수정이 매우 간단하고 명료하여 별도의 테스트는 작성하지 않았습니다.

배운 점

  • 사실 이런 Edge case는 일반적인 상황에서 나오지 않지만, 그럼에도 이런 이슈를 분석하고 추적하는 과정이 흥미로웠습니다.

참고 자료

-