pmed / v8pp

Bind C++ functions and classes into V8 JavaScript engine
http://pmed.github.io/v8pp/
Other
886 stars 118 forks source link

Segmentation Fault when calling a method #187

Closed rubenlg closed 1 year ago

rubenlg commented 1 year ago

I tried following the official examples from the README.md and also the website (they are very similar) to wrap a class in v8pp. The class gets created correctly in JS, but when attempting to call a method (get in this case) it crashes.

Here is a minimal repro case. The v8 boilerplate is unavoidable, since I'm not making a node module.

#include <iostream>
#include <libplatform/libplatform.h>
#include <v8/v8.h>
#include <v8pp/module.hpp>
#include <v8pp/class.hpp>

struct X {
  int c;
  X() : c(0) {}
  int get() const { return c; }
};

int main(int argc, char **argv) {
  v8::Isolate::CreateParams create_params;
  std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
  v8::V8::InitializePlatform(platform.get());
  v8::V8::Initialize();
  create_params.array_buffer_allocator =
      v8::ArrayBuffer::Allocator::NewDefaultAllocator();
  v8::Isolate *isolate = v8::Isolate::New(create_params);
  {
    v8::HandleScope handle_scope(isolate);
    v8::Local<v8::Context> context = v8::Context::New(isolate);
    v8::Context::Scope context_scope(context);

    v8pp::module module(isolate);
    v8pp::class_<X> X_class(isolate);
    X_class
      .ctor<>()
      .function("get", &X::get);
    module.class_("X", X_class);

    isolate->GetCurrentContext()->Global()->Set(v8pp::to_v8(isolate, "module"),
                                                module.new_instance());
    v8::Local<v8::String> source =
        v8::String::NewFromUtf8(isolate,
                                "const x = new module.X(); x.get();",
                                v8::NewStringType::kNormal)
            .ToLocalChecked();
    v8::Local<v8::Script> script =
        v8::Script::Compile(context, source).ToLocalChecked();
    std::cout << "About to run" << std::endl;
    script->Run(context).ToLocalChecked();
    std::cout << "Done running" << std::endl;
  }
  isolate->Dispose();
  v8::V8::Dispose();

  return 0;
}

Here is a stack trace taken from gdb (the useful part, since the rest is just ?? all the time):

#0  0x00007ffff7f833bc in v8pp::detail::object_registry<v8pp::raw_ptr_traits>::unwrap_object(v8::Local<v8::Value>) () from ../third_party/v8pp/out/v8pp/libv8pp.so
#1  0x000055555555c076 in v8pp::class_<X, v8pp::raw_ptr_traits>::unwrap_object(v8::Isolate*, v8::Local<v8::Value>) ()
#2  0x000055555555bf44 in decltype(auto) v8pp::detail::invoke<v8pp::raw_ptr_traits, int (X::*)() const, v8pp::detail::function_traits<int (X::*)() const> >(v8::FunctionCallbackInfo<v8::Value> const&) ()
#3  0x000055555555ba48 in void v8pp::detail::forward_function<v8pp::raw_ptr_traits, int (X::*)() const>(v8::FunctionCallbackInfo<v8::Value> const&) ()
#4  0x00007ffff61b3ab9 in v8::internal::FunctionCallbackArguments::Call(v8::internal::CallHandlerInfo) () from /lib/x86_64-linux-gnu/libnode.so.72
#5  0x00007ffff61b3e65 in ?? () from /lib/x86_64-linux-gnu/libnode.so.72
#6  0x00007ffff61b46ba in ?? () from /lib/x86_64-linux-gnu/libnode.so.72
#7  0x00007ffff61b50bd in v8::internal::Builtin_HandleApiCall(int, unsigned long*, v8::internal::Isolate*) () from /lib/x86_64-linux-gnu/libnode.so.72

I'm using the latest version of the v8pp code, I just downloaded it from github yesterday (2022-09-19). For v8, I'm using the libnode-dev library that comes with Ubuntu 22.04 (12.22.9~dfsg-1ubuntu3). I compiled with clang++ -std=c++17. My clang version is 14.0.0-1ubuntu1 (the clang that comes with the latest Ubuntu). Tried with g++ and got the exact same result and stack trace.

The V8 version in ubuntu has the following v8-version.h file:

#define V8_MAJOR_VERSION 7
#define V8_MINOR_VERSION 8
#define V8_BUILD_NUMBER 279
#define V8_PATCH_LEVEL 23
rubenlg commented 1 year ago

FTR: I'm downloading and building the latest stable version of V8, rather than using the libnode package from Ubuntu (which includes libv8 as well). Will follow up with the results.

rubenlg commented 1 year ago

Turns out I can't build against the latest stable V8 because of #181.

rubenlg commented 1 year ago

I just noticed a class.ipp file next to class.hpp. Turns out I had to include that one, which is the one calling SetInternalFieldCount to populate a pointer to the C++ instance inside the v8 one, otherwise it can't unwrap it.

So for others having this issue, the fix is to use this include:

#include <v8pp/class.ipp>

It would be great to have this documented somewhere. I ended up having to understand the V8 API in detail in order to arrive to this conclusion.