Fixing x86 String Corruption in V8
Recently, a bizarre bug was reported on the Chromium issue tracker (Issue 483769488): on official, highly-optimized 32-bit
x86 Windows builds of Google Chrome, calling Intl.DateTimeFormat with certain 2-digit time formatting options (such as { hour: '2-digit', minute: '2-digit', second: '2-digit' }) returned completely
empty strings.
const formatter = new Intl.DateTimeFormat('en-US', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
// Returns "" (empty string) on 32-bit Windows official builds!
console.log(formatter.format(new Date()));
Surprisingly, choosing numeric instead of 2-digit, or using dateStyle/timeStyle options worked perfectly. The issue only manifested in optimized x86 environments, making it
look suspiciously like an aggressive compiler-induced bug or low-level memory corruption.
The Investigation
Diving into V8’s implementation of ECMA-402 (specifically in src/objects/js-date-time-format.cc), V8 handles localized date/time patterns using a mapping data structure called PatternMap. The
PatternMap is responsible for mapping date/time skeleton patterns (like yMd or Hm) to the actual, locale-specific formatting patterns loaded from the ICU library (such as
dd/MM/yyyy).
Under the hood, PatternMap was structured with elements that held keys and values as C++ std::string. During the initialization of a PatternMap, a sequence of copy/move construction
operations occurred on these string members. On almost all modern platforms (64-bit Windows, macOS, or Linux), this worked flawlessly.
However, on official 32-bit Windows builds compiled with MSVC's aggressive link-time optimization (LTCG) and specific alignment flags, something went wrong with the compiler's optimization of the C++ standard library’s Small
String Optimization (SSO) during the copy/move construction chain. Specifically, the string’s internal buffer pointers or state became misaligned or corrupted, silently emptying the strings in the process. As a result,
PatternMap lookups failed to retrieve valid pattern templates, causing Intl.DateTimeFormat to silently fall back and return empty strings.
The Fix
While digging through js-date-time-format.cc, I noticed a crucial property of how PatternMap was being used: every single key and value inserted into the map was actually a static, hardcoded string
literal inside V8 and ICU. Because these strings are string literals with static storage lifetime, their memory is guaranteed to remain valid and reside in the read-only data segment of the binary for the entire
duration of the application.
Instantiating heap-allocated, dynamic std::string instances for these literals was not only completely unnecessary, but also introduced a measurable overhead in terms of memory allocations, destructor
registrations, and runtime copies.
The solution was simple and elegant: refactor the PatternMap structure to store lightweight const char* pointers instead of std::string.
// Before
struct PatternMap {
std::string key;
std::string pattern;
};
// After
struct PatternMap {
const char* key;
const char* pattern;
};
This change completely bypassed the C++ standard library's std::string move/copy constructor chain and SSO code paths, instantly resolving the compiler-induced string corruption on 32-bit Windows official builds.
It also replaced a dynamic, memory-allocating data structure with pointers to static string literals. This removed the heap allocations, reduced V8's memory use, and made Intl.DateTimeFormat lookups faster and
safer.
Conclusion
Sometimes, the most robust fix is not to make a complex system more resilient to bugs, but to realize you didn't need the complexity in the first place. By replacing heap-allocated standard strings with raw pointers to static literals, the platform-specific compiler optimization bug no longer occurred and the code required fewer allocations.
The fix was merged into V8 as CL 7566247 (Commit f59b29229b3445853e280af08ed5bc4aaaca51ab).