Search code examples
v8embedded-v8

How to write a setter with newer V8 API?


In the V8 embedding guide in the section for accessing dynamic variables, it describes how to write a setter for a wrapped C++ object. But the documentation seems to have rotted a bit in its usage of ToInt32 compared to the current (v14.1) API.

This example...

void SetPointX(v8::Local<v8::String> property, v8::Local<v8::Value> value,
               const v8::PropertyCallbackInfo<void>& info) {
  v8::Local<v8::Object> self = info.Holder();
  v8::Local<v8::External> wrap =
      v8::Local<v8::External>::Cast(self->GetInternalField(0));
  void* ptr = wrap->Value();
  static_cast<Point*>(ptr)->x_ = value->Int32Value();
}

...calls v8::Local<v8::Value>::Int32Value() but no such parameterless overload returning something that can be converted to an int exists any more. In the current API, the function requires a v8::Local<v8::Context> argument, and returns a v8::Maybe<int32_t>.

How would one modify this SetPointX function to gain access to the Context and deal with the Maybe that ToInt32 returns?


Solution

  • For the Context: it's best if you keep track of the correct context to use. If you only have one, then that's very easy :-) Just store it in a v8::Persistent when you create it, and create a v8::Local<v8::Context> from it whenever you need to. If you happen to have several contexts, you'll (hopefully) appreciate the amount of control that this gives you -- it's especially important for ruling out security-relevant cross-context accesses, for applications that need to care about such things.

    For the Maybe<int32_t>: check if it's Nothing, and handle the error if so.

    // On initialization:
    v8::Isolate* isolate = ...;
    v8::Persistent context(v8::Context::New(isolate));
    
    // And then later:
    v8::Maybe<int32_t> maybe_value = value->Int32Value(context.Get(isolate));
    
    // Alternative 1 (more elegant, can inline `maybe_value`):
    int32_t int_value;
    if (!maybe_value.To(&value)) {
      // Handle exception and return.
    }
    
    // Alternative 2 (easier to follow):
    if (maybe_value.IsNothing()) {
      // Handle exception and return.
    }
    int32_t int_value = maybe_value.FromJust();  // Crashes if maybe_value.IsNothing()
    
    static_cast<Point*>(ptr)->x_ = int_value;
    

    The reason why the Maybe dance is necessary is for JavaScript code like:

    my_point.x = {valueOf: () => throw "this is why we can't have nice things"; }
    

    where the old version value->Int32Value() doesn't have a way to signal that an exception was thrown while trying to get the int32 value. Of course this doesn't just apply to such artificial examples, but also to other exceptions like stack overflows or ReferenceErrors etc.