Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check for correct type when calling ObjectWrap::Unwrap in a Nodejs add-on?

Tags:

node.js

add-on

I have 2 C++ classes exposed as javascript classes, VanillaOption and NoomraEngine, both inheriting from ObjectWrap.

In the following method in NoomraEngine, I m supposed to receive a previously "wrapped" VanillaOption:

Handle<Value> 
NoomraEngine::Price(const Arguments& args) {
    HandleScope scope;
    Local<Object> object = args[0]->ToObject(); //  VanillaOption expected in args[0] 

    VanillaOption* equityOption = ObjectWrap::Unwrap<VanillaOption>(object);

    Local<Number> x = Number::New(this->price(equityOption));
    return scope.Close(x);
}

Everything works fine except that if I pass the wrong type to the method, node crashes in ObjectWrap::Unwrap.

My question is how can I make sure that I've received the correct type in args[0] ?

like image 777
BigONotation Avatar asked Jan 24 '12 21:01

BigONotation


1 Answers

EDIT: a better method than the bare V8 one below is to use NanHasInstance (https://github.com/rvagg/nan#api_nan_has_instance)

In MyObject::Init:

Local<FunctionTemplate> tpl = NanNew<FunctionTemplate>(New);
tpl->SetClassName(NanNew<String>("MyObject"));
...
NanAssignPersistent(prototype, tpl);

where prototype is a static Persistent<FunctionTemplate> member of MyObject.

Use like this:

if (NanHasInstance(prototype, handle)) {
    MyObject* obj = ObjectWrap::Unwrap<MyObject>(handle);
    ...
}

With the caveat that this is my first go at writing a Node addon, I solved this exact problem by checking the prototype of the object with my own wrapper around UnWrap.

Here's a patch to the addon factory class demo showing the method: https://github.com/petli/node-addon-examples/commit/d3e92cd060a26da2623690718e78f9005db060a8

It will only support factory-generated objects, and not ones where a constructor is exposed so that users could inherit from the base class. However, that could be generalised by walking the prototype chain.

In summary, it grabs hold of the reference to the expected class prototype in MyObject::Init:

Local<Object> obj = constructor->NewInstance();
prototype = Persistent<Value>::New(obj->GetPrototype());

And then checks that before dereferencing the object:

MyObject* MyObject::CheckedUnWrap(Handle<Object> handle)
{
  if (!handle.IsEmpty() && handle->InternalFieldCount() == 1) {
    Handle<Value> objproto = handle->GetPrototype();
    if (objproto == prototype) {
      // OK, this is us
      return ObjectWrap::Unwrap<MyObject>(handle);
    }
  }

  ThrowException(Exception::TypeError(String::New("<this> is not a MyObject")));
  return NULL;
}

All functions then use CheckedUnWrap instead:

Handle<Value> MyObject::PlusOne(const Arguments& args) {
  HandleScope scope;

  MyObject* obj = CheckedUnWrap(args.This());
  if (obj) {
    obj->counter_ += 1;
    return scope.Close(Number::New(obj->counter_));
  }
  else {
    // Invalid type, an exception has been thrown so return an empty value
    return Handle<Value>();
  }
}

I was also considering adding an internal field and set that to some some magic pointer, but then the code would have depended on that node::ObjectWrap wouldn't change how it uses the internal fields.

like image 178
Peter Liljenberg Avatar answered Oct 31 '22 12:10

Peter Liljenberg