denoland / rusty_v8

Rust bindings for the V8 JavaScript engine
https://crates.io/crates/v8
MIT License
3.28k stars 308 forks source link

ICU Error in DateTimePatternGeneratorCache::CreateGenerator #1444

Closed piercefreeman closed 6 months ago

piercefreeman commented 6 months ago

Javascript code that attempts to access internal ICU hooks (like Intl.DateTimeFormat) will throw a heap error, even though there is sufficient space left on the heap:

const value = new Intl.DateTimeFormat(void 0, {
  timeZone: "America/Los_Angeles",
});
console.log("VALUE", value);
<--- Last few GCs --->

<--- JS stacktrace --->

#
# Fatal process out of memory: DateTimePatternGeneratorCache::CreateGenerator
#

==== C stack trace ===============================

    0   ssr_benchmark-0c09b39144e98e6e      0x0000000100a578c0 v8::base::debug::StackTrace::StackTrace() + 24
    1   ssr_benchmark-0c09b39144e98e6e      0x0000000100a5c254 v8::platform::(anonymous namespace)::PrintStackTrace() + 24
    2   ssr_benchmark-0c09b39144e98e6e      0x0000000100a4d7b8 v8::base::FatalOOM(v8::base::OOMType, char const*) + 68
    3   ssr_benchmark-0c09b39144e98e6e      0x0000000100aab314 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, v8::OOMDetails const&) + 624
    4   ssr_benchmark-0c09b39144e98e6e      0x0000000100e295e4 v8::internal::(anonymous namespace)::DateTimePatternGeneratorCache::CreateGenerator(v8::internal::Isolate*, icu_73::Locale const&) + 472
    5   ssr_benchmark-0c09b39144e98e6e      0x0000000100e27d84 v8::internal::JSDateTimeFormat::CreateDateTimeFormat(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Map>, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, v8::internal::JSDateTimeFormat::RequiredOption, v8::internal::JSDateTimeFormat::DefaultsOption, char const*) + 2184
    6   ssr_benchmark-0c09b39144e98e6e      0x0000000100af1238 v8::internal::Builtin_DateTimeFormatConstructor(int, unsigned long*, v8::internal::Isolate*) + 268
    7   ssr_benchmark-0c09b39144e98e6e      0x0000000101ade6d4 Builtins_CEntry_Return1_ArgvOnStack_BuiltinExit + 84
    8   ssr_benchmark-0c09b39144e98e6e      0x0000000101a4dd18 construct_stub_invoke_deopt_addr + 256
    9   ssr_benchmark-0c09b39144e98e6e      0x0000000101bc1ab0 Builtins_ConstructHandler + 880
    10  ssr_benchmark-0c09b39144e98e6e      0x0000000101a4cf90 Builtins_InterpreterEntryTrampoline + 272
    11  ssr_benchmark-0c09b39144e98e6e      0x0000000101a4acac Builtins_JSEntryTrampoline + 172
    12  ssr_benchmark-0c09b39144e98e6e      0x0000000101a4a994 Builtins_JSEntry + 148
    13  ssr_benchmark-0c09b39144e98e6e      0x0000000100ba3668 v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) + 1628
    14  ssr_benchmark-0c09b39144e98e6e      0x0000000100ba3c18 v8::internal::Execution::CallScript(v8::internal::Isolate*, v8::internal::Handle<v8::internal::JSFunction>, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>) + 132
    15  ssr_benchmark-0c09b39144e98e6e      0x0000000100aadf04 v8::Script::Run(v8::Local<v8::Context>, v8::Local<v8::Data>) + 692
    16  ssr_benchmark-0c09b39144e98e6e      0x00000001006490f8 mountaineer::ssr::Ssr::render_to_string::h926652397d01bc78 + 2708
    17  ssr_benchmark-0c09b39144e98e6e      0x00000001006ec19c _ZN9criterion7bencher16Bencher$LT$M$GT$4iter17h9cbdc3c1427afdf0E + 348
    18  ssr_benchmark-0c09b39144e98e6e      0x00000001006f3980 _ZN106_$LT$criterion..routine..Function$LT$M$C$F$C$T$GT$$u20$as$u20$criterion..routine..Routine$LT$M$C$T$GT$$GT$7warm_up17hd8608e68a9c485b8E + 96
    19  ssr_benchmark-0c09b39144e98e6e      0x00000001006f5520 criterion::routine::Routine::sample::h69ae686ab63289bc + 1124
    20  ssr_benchmark-0c09b39144e98e6e      0x00000001006ec71c criterion::analysis::common::h4af94e40a8459efb + 268
    21  ssr_benchmark-0c09b39144e98e6e      0x00000001006fa7f0 ssr_benchmark::main::h68f1ecec5ce8a544 + 5064
    22  ssr_benchmark-0c09b39144e98e6e      0x00000001006fba18 std::sys_common::backtrace::__rust_begin_short_backtrace::h7240a5317e45c703 + 12
    23  ssr_benchmark-0c09b39144e98e6e      0x00000001006f8fe0 main + 588
    24  dyld                                0x00000001836650e0 start + 2360

Similar to this upstream Chromium issue, but definitely present in rusty-v8 0.82.0 onward. Seems to be related to compile-time flags and linking. The source issue lies in this DateTimePatternGeneratorCache V8 class where it's failing to allocate.

Full rust code to reproduce:

let platform = v8::new_default_platform(0, false).make_shared();
v8::V8::initialize_platform(platform);
v8::V8::initialize();

let create_params = v8::Isolate::create_params().heap_limits(
    0,
    8 * 1024 * 1_048_576,
);

let mut isolate = v8::Isolate::new(create_params);
let mut handle_scope = v8::HandleScope::new(&mut isolate);
let context = v8::Context::new(&mut handle_scope);
let mut context_scope = v8::ContextScope::new(&mut handle_scope, context);

let source = r#"
    const value = new Intl.DateTimeFormat(void 0, {
        timeZone: "America/Los_Angeles",
    });
    console.log("VALUE", value);
"#;

let code = match v8::String::new(&mut context_scope, source)
{
    Some(code) => code,
    None => {
        // This typically shouldn't fail unless there's a serious issue (like out of memory),
        // so we don't handle it specifically with try_catch.
        return Err(AppError::V8ExceptionError(
            "Failed to create code string".into(),
        ));
    }
};

let script = v8::Script::compile(&mut context_scope, code, None).unwrap();

let result = script
    .run(&mut context_scope)
    .expect("Failed to run script");

let result_str = result
    .to_string(&mut context_scope)
    .unwrap()
    .to_rust_string_lossy(&mut context_scope);
println!("Will return: {}", result_str);

System Config:

macOS: 14.3.1
Platform: M1
Rusty-V8: 0.82.0 & 0.89.0
piercefreeman commented 6 months ago

I see now that there are separate flags to link ICU at runtime. For anyone else that stumbles on this crash in the future:

https://github.com/denoland/rusty_v8/pull/603 https://github.com/denoland/deno_core/blob/d8e13061571e587b92487d391861faa40bd84a6f/core/runtime/setup.rs#L21

The fix ends up being pretty simple, you just need to link the common .dat file with v8::icu::set_common_data_73. The deno_core_icudata publishes this file to an isolated crate as well.

v8::icu::set_common_data_73(deno_core_icudata::ICU_DATA).unwrap();