@@ -2823,6 +2823,15 @@ ObjectWrap<T>::DefineClass(Napi::Env env,
2823
2823
const napi_property_descriptor* props,
2824
2824
void * data) {
2825
2825
napi_status status;
2826
+
2827
+ // Before defining the class we can replace static method property descriptors
2828
+ // with value property descriptors such that the value is a function-valued
2829
+ // `napi_value` created with `CreateFunction()`. Note that we have to break
2830
+ // constness to do this.
2831
+ //
2832
+ // This replacement could be made for instance methods as well, but V8 aborts
2833
+ // if we do that, because it expects methods defined on the prototype template
2834
+ // to have `FunctionTemplate`s.
2826
2835
for (size_t index = 0 ; index < props_count; index++) {
2827
2836
napi_property_descriptor* prop =
2828
2837
const_cast <napi_property_descriptor*>(&props[index]);
@@ -2858,6 +2867,41 @@ ObjectWrap<T>::DefineClass(Napi::Env env,
2858
2867
&value);
2859
2868
NAPI_THROW_IF_FAILED (env, status, Function ());
2860
2869
2870
+ // After defining the class we iterate once more over the property descriptors
2871
+ // and attach the data associated with accessors and instance methods to the
2872
+ // newly created JavaScript class.
2873
+ //
2874
+ // TODO: On some engines it might be possible to detach instance methods from
2875
+ // the prototype. This would mean that the detached prototype method would
2876
+ // become a plain function and so its data should be associated with itself
2877
+ // rather than with the class. If this is the case then in this loop, and for
2878
+ // prototype methods, we should retrieve the `napi_value` representing the
2879
+ // resulting function and attach the data to *it* rather than the class.
2880
+ //
2881
+ // IOW if in JavaScript one does this,
2882
+ //
2883
+ // let MyClass = binding.defineMyClass();
2884
+ // const someMethod = MyClass.prototype.someMethod;
2885
+ // delete MyClass;
2886
+ // someMethod();
2887
+ //
2888
+ // then the class, onto which we attached the data, would be gone, and the
2889
+ // data that will be passed to the native implementation of `someMethod()`
2890
+ // would be stale and would cause a segfault if accessed, and so in the
2891
+ // following loop, we may have to
2892
+ //
2893
+ // napi_get_named_property(env, value, "prototype", &proto);
2894
+ // once at the top, and then, for each property, if it is an instance method,
2895
+ // napi_get_named_property(env, value, prop->utf8name, &proto_method);
2896
+ // or
2897
+ // napi_get_property(env, prop->name, &proto_method);
2898
+ // and then
2899
+ // Napi::details::AttachData(env,
2900
+ // proto_method,
2901
+ // static_cast<...*>(prop->data));
2902
+ //
2903
+ // instead of attaching the data to the class. The downside is that all this
2904
+ // retrieving of prototype properties would be very expensive.
2861
2905
for (size_t idx = 0 ; idx < props_count; idx++) {
2862
2906
const napi_property_descriptor* prop = &props[idx];
2863
2907
@@ -2872,17 +2916,19 @@ ObjectWrap<T>::DefineClass(Napi::Env env,
2872
2916
status = Napi::details::AttachData (env,
2873
2917
value,
2874
2918
static_cast <InstanceAccessorCallbackData*>(prop->data ));
2919
+ NAPI_THROW_IF_FAILED (env, status, Function ());
2875
2920
} else if (prop->method != nullptr && !(prop->attributes & napi_static)) {
2876
2921
if (prop->method == T::InstanceVoidMethodCallbackWrapper) {
2877
2922
status = Napi::details::AttachData (env,
2878
2923
value,
2879
2924
static_cast <InstanceVoidMethodCallbackData*>(prop->data ));
2925
+ NAPI_THROW_IF_FAILED (env, status, Function ());
2880
2926
} else if (prop->method == T::InstanceMethodCallbackWrapper) {
2881
2927
status = Napi::details::AttachData (env,
2882
2928
value,
2883
2929
static_cast <InstanceMethodCallbackData*>(prop->data ));
2930
+ NAPI_THROW_IF_FAILED (env, status, Function ());
2884
2931
}
2885
- NAPI_THROW_IF_FAILED (env, status, Function ());
2886
2932
}
2887
2933
}
2888
2934
0 commit comments