1

I am embedding V8 (version 8.1.307) into an application. I am using the API reference made available by node source.com, alongside Google's embedding guide.

I am hitting a road block while trying to do a seemingly simple thing: to generate an error object in response to an invalid call to a C++ function from Javascript. The docs indicate that it should be as simple as constructing a v8::Local <v8::String> and passing it to v8::Exception::RangeError() or similar function, but this triggers a segfault inside of V8.

I have pored over the available samples, but they only offer demonstrations of how to throw raw strings as exceptions, which I'd rather not do if at all possible. Am I missing something simple, or is this really broken or unsupported?

//Note: ErrorFactory is defined as:
    typedef v8::Local <v8::Value>(*ErrorFactory)(v8::Local <v8::String>);

void ScriptManager::ConvertNativeExceptionToJavascriptError(ErrorFactory NewError, std::exception& exception)
{
    v8::Local <v8::String> errstr = v8::String::NewFromUtf8(mIsolate, exception.what(), v8::NewStringType::kNormal).ToLocalChecked();
    
    mIsolate->ThrowException(NewError(errstr));
    
}

Later invoked as follows:

catch(std::exception& e)
        {
//Just pick RangeError for demonstration's sake.
            engine->ConvertNativeExceptionToJavascriptError(v8::Exception::RangeError, e);
            
        }

It crashes inside of V8 during the RangeError call:


AddressSanitizer:DEADLYSIGNAL
=================================================================
==7680==ERROR: AddressSanitizer: SEGV on unknown address 0x00000000a058 (pc 0x00010e07c771 bp 0x7ffee1c0baf0 sp 0x7ffee1c0ba90 T0)
==7680==The signal is caused by a READ memory access.
    #0 0x10e07c770 in v8::Exception::RangeError(v8::Local<v8::String>) api.cc:9199
    #1 0x10dff9904 in ScriptManager::ConvertNativeExceptionToJavascriptError(v8::Local<v8::Value> (*)(v8::Local<v8::String>), std::exception&) javascript.cpp:316
    #2 0x10dff7cca in CallFunction(v8::FunctionCallbackInfo<v8::Value> const&) javascript.cpp:66
    #3 0x10e0bce80 in v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) api-arguments-inl.h:158
    #4 0x10e0bc353 in v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) builtins-api.cc:111
    #5 0x10e0bb932 in v8::internal::Builtin_Impl_HandleApiCall(v8::internal::BuiltinArguments, v8::internal::Isolate*) builtins-api.cc:141
    #6 0x10ec9ac38 in Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_BuiltinExit (test:x86_64+0x100ca8c38)
    #7 0x10ec33452 in Builtins_InterpreterEntryTrampoline (test:x86_64+0x100c41452)
    #8 0x10ec310b9 in Builtins_JSEntryTrampoline (test:x86_64+0x100c3f0b9)
    #9 0x10ec30e97 in Builtins_JSEntry (test:x86_64+0x100c3ee97)
    #10 0x10e180f90 in v8::internal::(anonymous namespace)::Invoke(v8::internal::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) execution.cc:372
    #11 0x10e180297 in v8::internal::Execution::Call(v8::internal::Isolate*, v8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::internal::Object>, int, v8::internal::Handle<v8::internal::Object>*) execution.cc:466
    #12 0x10e051399 in v8::Script::Run(v8::Local<v8::Context>) api.cc:2158
    #13 0x10e004b55 in ScriptContext::ExecuteString(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) javascript.cpp:448
    #14 0x10e00572c in ScriptContext::ExecuteFile(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >) javascript.cpp:466
    #15 0x10dff3b23 in main test.cpp:28
    #16 0x7fff6a527cc8 in start (libdyld.dylib:x86_64+0x1acc8)

==7680==Register values:
rax = 0x0000000000000000  rbx = 0x0000000000000000  rcx = 0x0000000000000000  rdx = 0x0000100000000000  
rdi = 0x000062500000c9d0  rsi = 0x00007ffee1c0bb40  rbp = 0x00007ffee1c0baf0  rsp = 0x00007ffee1c0ba90  
 r8 = 0x000062500000c9d0   r9 = 0xffffebffffff7748  r10 = 0x0000000000000043  r11 = 0x0000000000000060  
r12 = 0x000060e000000520  r13 = 0x00007ffee1c0d7e0  r14 = 0x000062500000c9d0  r15 = 0x00001fffdc381afd  
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV api.cc:9199 in v8::Exception::RangeError(v8::Local<v8::String>)
==7680==ABORTING
Abort trap: 6
James Z
  • 12,209
  • 10
  • 24
  • 44
Caturria
  • 13
  • 3

1 Answers1

1

The code looks alright. I suggest that you use a Debug build of both V8 and your app and see if you get any DCHECK failures before the crash. Those might provide valuable insight into what the problem is.

One possible source of such crashes is a mismatch in "pointer compression" configuration. By default, V8 enables this feature in recent versions. If you don't change that default, you'll have to make sure you compile any of your own compilation units that #include v8.h with -DV8_COMPRESS_POINTERS -DV8_31BIT_SMIS_ON_64BIT_ARCH in your C++ compiler flags.

Also, note that V8 is usually compiled with -fno-exceptions. I have no experience with linking it with an app that uses C++ exceptions; so I'm not sure whether that might be causing any trouble.

(Any v8::String will do; whether you construct it with NewFromOneByte or NewFromUtf8 only affects how the input you provide is decoded, the resulting v8::String will be the same.)

jmrk
  • 34,271
  • 7
  • 59
  • 74
  • Thank you very much. I finally got a debug build to actually complete, and it revealed that RangeError was calling V8::Isolate::GetCurrent, which was returning nullptr. Invoking Enter() on the isolate solves it. I have added mIsolate->Enter() to my ScriptManager constructor, and likewise mIsolate->Exit() to its destructor prior to calling Dispose(). Is this in line with best practice, or should I be Enter()ing just before the call to the error constructor and Exit()ing afterwards? – Caturria Oct 14 '20 at 22:00
  • When you're working with a single Isolate, Entering and Exiting it only once at the beginning/end of your program is totally fine. You only need finer grained Enter/Exit calls if you're jumping around between different Isolates. – jmrk Oct 15 '20 at 17:02