c++undefined-behaviornode-addon-api

Is there an "undefined behavior" problem with some APIs in "node-addon-api"?


In <napi.h>, there are some overloads declaration for the static method Accessor in the class PropertyDescriptor.

One of such overloads declaration is here:

template <typename Getter>
static PropertyDescriptor Accessor(Napi::Env env,
                                   Napi::Object object,
                                   const std::string& utf8name,
                                   Getter getter,
                                   napi_property_attributes attributes = napi_default,
                                   void* data = nullptr);

That declaration wants a reference to a const std::string

The definition of that declaration is in <napi-inl.h>:

template <typename Getter>
inline PropertyDescriptor PropertyDescriptor::Accessor(Napi::Env env,
                                                       Napi::Object object,
                                                       const std::string& utf8name,
                                                       Getter getter,
                                                       napi_property_attributes attributes,
                                                       void* data) {
  return Accessor(env, object, utf8name.c_str(), getter, attributes, data);
}

As you can see, the implementation takes the .c_str() of the passed std::string, as the implementation in fact uses another overload which wants a const char*.

The overload used is also in <napi-inl.h>:

template <typename Getter>
inline PropertyDescriptor
PropertyDescriptor::Accessor(Napi::Env env,
                             Napi::Object object,
                             const char* utf8name,
                             Getter getter,
                             napi_property_attributes attributes,
                             void* data) {
  using CbData = details::CallbackData<Getter, Napi::Value>;
  auto callbackData = new CbData({ getter, data });

  napi_status status = AttachData(env, object, callbackData);
  if (status != napi_ok) {
    delete callbackData;
    NAPI_THROW_IF_FAILED(env, status, napi_property_descriptor());
  }

  return PropertyDescriptor({
    utf8name,
    nullptr,
    nullptr,
    CbData::Wrapper,
    nullptr,
    nullptr,
    attributes,
    callbackData
  });
}

That implementation uses Node-API, as node-addon-api is just a C++ wrapper arround Node-API. The fact is that the const char* utf8name obtained from .c_str() is passed as is to the constructor and is stored in the private member napi_property_descriptor _desc;

Conclusion: each time you use Napi::PropertyDescriptor::Accessor passing a std::string and then storing the PD in a vector, or just making the string goes out of scope with the PD still here, you trigger an undefined behavior!

Example:

Napi::Object TheNewObject = Napi::Object::New(env);
std::vector<Napi::PropertyDescriptor> vProps;
for (size_t index = 0; index < 2; ++index ) {
    std::string strName("MyName_");
    strName += std::to_string(index);
    auto PD = Napi::PropertyDescriptor::Accessor(env, TheNewObject, strName, Caller);
    vProps.push_back(PD);
} 

I opened an issue here, 12 days ago, with no avail.

EDIT: After reading comments, I should add that:

  1. The napi_property_descriptor is a simple C structure.
  2. Address of that struct will later be passed to the Node-API napi_define_properties

Solution

  • There is indeed a problem with current implementations of PropertyDescriptor. See https://github.com/nodejs/node-addon-api/issues/1127#issuecomment-1036394322

    Glad I wasn't completely delirious.