기여 목록 / 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(¤t, end)) {
return JunkStringValue();
}발굴 과정
- 이슈 확인 중에 발견함
테스트 방법
- 수정이 매우 간단하고 명료하여 별도의 테스트는 작성하지 않았습니다.
배운 점
- 사실 이런 Edge case는 일반적인 상황에서 나오지 않지만, 그럼에도 이런 이슈를 분석하고 추적하는 과정이 흥미로웠습니다.
참고 자료
-