Search code examples
c++v8

V8PP / V8 crashing when adding a new module instance


I would like to be able to create C++ classes and expose them into the V8 JavaScript engine. I'm using the v8pp library to do this, and by following their examples as well as the Google V8 Hello World Script, I have come to this code

  • main.cpp file
  • TestClass1.h - whose class I'd like to expose to JavaScript
  • CMakeList.txt file - so you can see how I've linked V8 if this is important):
// main.cpp

#define V8_COMPRESS_POINTERS

#include <v8.h>
#include <libplatform.h>

#include <v8pp/module.hpp>
#include <v8pp/class.hpp>

#include "src/TestClass1.h"


int main(int argc, char* argv[]) {
    v8::V8::InitializeICUDefaultLocation(argv[0]);
    v8::V8::InitializeExternalStartupData(argv[0]);
    std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
    v8::V8::InitializePlatform(platform.get());
    v8::V8::Initialize();

    v8::Isolate::CreateParams create_params;
    create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
    v8::Isolate *isolate = v8::Isolate::New(create_params);

    {
        v8::Isolate::Scope isolate_scope(isolate);
        v8::HandleScope handle_scope(isolate);
        v8::Local<v8::Context> context = v8::Context::New(isolate);

        v8pp::module window(isolate);
        v8pp::class_<TestClass1> TestClass1_V8(isolate);

        TestClass1_V8
                .ctor<int, int>()
                .set("a", v8pp::property(&TestClass1::getA, &TestClass1::setA))
                .set("b", v8pp::property(&TestClass1::getB, &TestClass1::setB))
                //.set_static("c", 5, true)
                .set("methodA", &TestClass1::testMethod);

        window.set("TestClass1", TestClass1_V8);
        isolate->GetCurrentContext()->Global()->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "window"), window.new_instance());

        v8::Context::Scope context_scope(context);

        {
            v8::Local<v8::String> source = v8::String::NewFromUtf8Literal(isolate, "(function() {let t = new window.TestClass1(); t.a = 5; return t.a})()");
            v8::Local<v8::Script> script = v8::Script::Compile(context, source).ToLocalChecked();
            v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
            v8::String::Utf8Value utf8(isolate, result);
            printf("%s\n", *utf8);
        }
    }
    return 0;
}
// src/TestClass1.h

#ifndef V8PP_TESTCLASS1_H
#define V8PP_TESTCLASS1_H


class TestClass1 {
    friend int main(int argc, char* argv[]);

public:
    static int m_c;

    TestClass1(int a, int b) {
        m_a = a;
        m_b = b;
    }

    int testMethod(int c) {
        return m_a + m_b + c;
    }

private:
    int m_a;
    int m_b;

    int getA() {
        return m_a;
    }

    void setA(int a) {
        m_a = 2 * a;
    }

    int getB() {
        return m_b;
    }

    void setB(int b) {
        m_b = 3 + b;
    }
};

#endif //V8PP_TESTCLASS1_H
# CMakeList.txt
cmake_minimum_required(VERSION 3.19)
project(V8PP)

set(CMAKE_CXX_STANDARD 20)


### V8 ### [MANUAL]
set(CMAKE_CXX_FLAGS "/MT")
set(CMAKE_C_FLAGS "/MT")

if(MSVC)
    add_compile_options(
            $<$<CONFIG:>:/MT> #---------|
            $<$<CONFIG:Debug>:/MTd> #---|-- Statically link the runtime libraries
            $<$<CONFIG:Release>:/MT> #--|
    )
endif()

include_directories(E:/V8/depot_tools/v8/include)
include_directories(E:/V8/depot_tools/v8/include/libplatform)
include_directories(${CMAKE_SOURCE_DIR}/v8pp-master/v8pp-master)

link_directories(E:/V8/depot_tools/v8/out.gn/x64.release/obj/)
link_directories(E:/V8/depot_tools/v8/out.gn/x64.release/obj/third_party)
link_directories(E:/V8/depot_tools/v8/out.gn/x64.release/obj/third_party/icu)


link_libraries(
        v8_libbase
        v8_libplatform
        v8_monolith
        icuuc
        icui18n
)


link_libraries(winmm.lib)
link_libraries(dbghelp.lib)
link_libraries(shlwapi.lib)
### V8 ###


add_executable(V8PP main.cpp)

I've isolated the error down to the line

isolate->GetCurrentContext()->Global()->Set(isolate->GetCurrentContext(), v8pp::to_v8(isolate, "window"), window.new_instance());

Specifically window.new_instance(). Going into the v8pp source code (file module.hpp), the only line in the method is

return obj_->NewInstance(isolate_->GetCurrentContext()).ToLocalChecked();

I separated out the different statements onto separate lines, and the error is coming from the obj_->NewInstance(), where obj_ is a v8::Local<v8::ObjectTemplate>, created in the initializer list of the constructor of the module object. This function call is part of v8 itself, but I only have access to the header files of v8, so I don't know what has caused the error.

The code builds fine, but when it's run, there isn't a traceback, just:

Process finished with exit code -1073741819 (0xC0000005)

implying a memory access error (maybe to do with pointers?)

Does anyone know how to add a new instance of a v8pp module into the v8 engine without this crash occurring?

Edit

Using:

  • Windows 10
  • C++ 20
  • CMake (on CLion)
  • MSVC 2019 64-bit

Solution

  • I found the issue: firstly, I had to move the line of code

    v8::Context::Scope context_scope(context);
    

    to directly under the line

    v8::Local<v8::Context> context = v8::Context::New(isolate);
    

    This did create another error

    #
    # Fatal error in v8::ToLocalChecked
    # Empty MaybeLocal.
    #
    
    <unknown>:21: Uncaught argument count does not match function definition
    

    which was because when I called the constructor in JavaScript, I forgot to add the arguments, so changing the JavaScript code to

    (function() {let t = new window.TestClass1(); t.a = 5; return t.a;})()
    

    and everything works.