1#include <js_native_api.h>
2#include <stdint.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6#include "../common.h"
7#include "../entry_point.h"
8
9typedef struct {
10  int32_t finalize_count;
11  napi_ref js_func;
12} FinalizerData;
13
14static void finalizerOnlyCallback(node_api_nogc_env env,
15                                  void* finalize_data,
16                                  void* finalize_hint) {
17  FinalizerData* data = (FinalizerData*)finalize_data;
18  int32_t count = ++data->finalize_count;
19
20  // It is safe to access instance data
21  NODE_API_NOGC_CALL_RETURN_VOID(env,
22                                 napi_get_instance_data(env, (void**)&data));
23  NODE_API_NOGC_ASSERT_RETURN_VOID(count = data->finalize_count,
24                                   "Expected to be the same FinalizerData");
25}
26
27static void finalizerCallingJSCallback(napi_env env,
28                                       void* finalize_data,
29                                       void* finalize_hint) {
30  napi_value js_func, undefined;
31  FinalizerData* data = (FinalizerData*)finalize_data;
32  NODE_API_CALL_RETURN_VOID(
33      env, napi_get_reference_value(env, data->js_func, &js_func));
34  NODE_API_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined));
35  NODE_API_CALL_RETURN_VOID(
36      env, napi_call_function(env, undefined, js_func, 0, NULL, NULL));
37  NODE_API_CALL_RETURN_VOID(env, napi_delete_reference(env, data->js_func));
38  data->js_func = NULL;
39  ++data->finalize_count;
40}
41
42// Schedule async finalizer to run JavaScript-touching code.
43static void finalizerWithJSCallback(node_api_nogc_env env,
44                                    void* finalize_data,
45                                    void* finalize_hint) {
46  NODE_API_NOGC_CALL_RETURN_VOID(
47      env,
48      node_api_post_finalizer(
49          env, finalizerCallingJSCallback, finalize_data, finalize_hint));
50}
51
52static void finalizerWithFailedJSCallback(node_api_nogc_env nogc_env,
53                                          void* finalize_data,
54                                          void* finalize_hint) {
55  // Intentionally cast to a napi_env to test the fatal failure.
56  napi_env env = (napi_env)nogc_env;
57  napi_value obj;
58  FinalizerData* data = (FinalizerData*)finalize_data;
59  ++data->finalize_count;
60  NODE_API_CALL_RETURN_VOID(env, napi_create_object(env, &obj));
61}
62
63static napi_value addFinalizer(napi_env env, napi_callback_info info) {
64  size_t argc = 1;
65  napi_value argv[1] = {0};
66  FinalizerData* data;
67
68  NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
69  NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data));
70  NODE_API_CALL(env,
71                napi_add_finalizer(
72                    env, argv[0], data, finalizerOnlyCallback, NULL, NULL));
73  return NULL;
74}
75
76// This finalizer is going to call JavaScript from finalizer and succeed.
77static napi_value addFinalizerWithJS(napi_env env, napi_callback_info info) {
78  size_t argc = 2;
79  napi_value argv[2] = {0};
80  napi_valuetype arg_type;
81  FinalizerData* data;
82
83  NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
84  NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data));
85  NODE_API_CALL(env, napi_typeof(env, argv[1], &arg_type));
86  NODE_API_ASSERT(
87      env, arg_type == napi_function, "Expected function as the second arg");
88  NODE_API_CALL(env, napi_create_reference(env, argv[1], 1, &data->js_func));
89  NODE_API_CALL(env,
90                napi_add_finalizer(
91                    env, argv[0], data, finalizerWithJSCallback, NULL, NULL));
92  return NULL;
93}
94
95// This finalizer is going to call JavaScript from finalizer and fail.
96static napi_value addFinalizerFailOnJS(napi_env env, napi_callback_info info) {
97  size_t argc = 1;
98  napi_value argv[1] = {0};
99  FinalizerData* data;
100
101  NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
102  NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data));
103  NODE_API_CALL(
104      env,
105      napi_add_finalizer(
106          env, argv[0], data, finalizerWithFailedJSCallback, NULL, NULL));
107  return NULL;
108}
109
110static napi_value getFinalizerCallCount(napi_env env, napi_callback_info info) {
111  size_t argc = 1;
112  napi_value argv[1];
113  FinalizerData* data;
114  napi_value result;
115
116  NODE_API_CALL(env, napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
117  NODE_API_CALL(env, napi_get_instance_data(env, (void**)&data));
118  NODE_API_CALL(env, napi_create_int32(env, data->finalize_count, &result));
119  return result;
120}
121
122static void finalizeData(napi_env env, void* data, void* hint) {
123  free(data);
124}
125
126EXTERN_C_START
127napi_value Init(napi_env env, napi_value exports) {
128  FinalizerData* data = (FinalizerData*)malloc(sizeof(FinalizerData));
129  NODE_API_ASSERT(env, data != NULL, "Failed to allocate memory");
130  memset(data, 0, sizeof(FinalizerData));
131  NODE_API_CALL(env, napi_set_instance_data(env, data, finalizeData, NULL));
132  napi_property_descriptor descriptors[] = {
133      DECLARE_NODE_API_PROPERTY("addFinalizer", addFinalizer),
134      DECLARE_NODE_API_PROPERTY("addFinalizerWithJS", addFinalizerWithJS),
135      DECLARE_NODE_API_PROPERTY("addFinalizerFailOnJS", addFinalizerFailOnJS),
136      DECLARE_NODE_API_PROPERTY("getFinalizerCallCount",
137                                getFinalizerCallCount)};
138
139  NODE_API_CALL(
140      env,
141      napi_define_properties(env,
142                             exports,
143                             sizeof(descriptors) / sizeof(*descriptors),
144                             descriptors));
145
146  return exports;
147}
148EXTERN_C_END
149