|
28 | 28 |
|
29 | 29 | std::atomic<int> success_callbacks{}; |
30 | 30 |
|
| 31 | +struct trampoline_regression_ctx_type |
| 32 | +{ |
| 33 | + std::atomic<unsigned int> resolve_callbacks{ 0 }; |
| 34 | + std::atomic<unsigned int> reject_callbacks{ 0 }; |
| 35 | +} trampoline_ctx; |
| 36 | + |
31 | 37 | class metacall_node_async_test : public testing::Test |
32 | 38 | { |
33 | 39 | public: |
@@ -151,6 +157,71 @@ TEST_F(metacall_node_async_test, DefaultConstructor) |
151 | 157 |
|
152 | 158 | metacall_value_destroy(args[0]); |
153 | 159 |
|
| 160 | + /* Regression #578: trampoline ownership. test */ |
| 161 | + { |
| 162 | + const char trampoline_buffer[] = |
| 163 | + "function f_reg(x) { return new Promise(r => r(x)); }\n" |
| 164 | + "function g_reg(x) { return new Promise((_, r) => r(x)); }\n" |
| 165 | + "async function i_reg() { throw Error('Hi there!'); }\n" |
| 166 | + "module.exports = { f_reg, g_reg, i_reg };\n"; |
| 167 | + |
| 168 | + EXPECT_EQ((int)0, (int)metacall_load_from_memory("node", trampoline_buffer, sizeof(trampoline_buffer), NULL)); |
| 169 | + |
| 170 | + auto trampoline_resolve_cb = [](void *result, void *data) -> void * { |
| 171 | + EXPECT_NE((void *)NULL, (void *)result); |
| 172 | + |
| 173 | + trampoline_regression_ctx_type *ctx = static_cast<trampoline_regression_ctx_type *>(data); |
| 174 | + EXPECT_NE((void *)NULL, (void *)ctx); |
| 175 | + |
| 176 | + ++ctx->resolve_callbacks; |
| 177 | + |
| 178 | + return NULL; |
| 179 | + }; |
| 180 | + |
| 181 | + auto trampoline_reject_cb = [](void *result, void *data) -> void * { |
| 182 | + EXPECT_NE((void *)NULL, (void *)result); |
| 183 | + |
| 184 | + trampoline_regression_ctx_type *ctx = static_cast<trampoline_regression_ctx_type *>(data); |
| 185 | + EXPECT_NE((void *)NULL, (void *)ctx); |
| 186 | + |
| 187 | + ++ctx->reject_callbacks; |
| 188 | + |
| 189 | + return NULL; |
| 190 | + }; |
| 191 | + |
| 192 | + /* Await #1: resolve path — exercises node_loader_trampoline_resolve ownership */ |
| 193 | + void *args_f_reg[] = { metacall_value_create_double(10.0) }; |
| 194 | + |
| 195 | + void *future_f_reg = metacall_await("f_reg", args_f_reg, trampoline_resolve_cb, trampoline_reject_cb, static_cast<void *>(&trampoline_ctx)); |
| 196 | + |
| 197 | + metacall_value_destroy(args_f_reg[0]); |
| 198 | + |
| 199 | + EXPECT_NE((void *)NULL, (void *)future_f_reg); |
| 200 | + EXPECT_EQ((enum metacall_value_id)METACALL_FUTURE, (enum metacall_value_id)metacall_value_id(future_f_reg)); |
| 201 | + |
| 202 | + metacall_value_destroy(future_f_reg); |
| 203 | + |
| 204 | + /* Await #2: reject path — exercises node_loader_trampoline_reject ownership */ |
| 205 | + void *args_g_reg[] = { metacall_value_create_double(20.0) }; |
| 206 | + |
| 207 | + void *future_g_reg = metacall_await("g_reg", args_g_reg, trampoline_resolve_cb, trampoline_reject_cb, static_cast<void *>(&trampoline_ctx)); |
| 208 | + |
| 209 | + metacall_value_destroy(args_g_reg[0]); |
| 210 | + |
| 211 | + EXPECT_NE((void *)NULL, (void *)future_g_reg); |
| 212 | + EXPECT_EQ((enum metacall_value_id)METACALL_FUTURE, (enum metacall_value_id)metacall_value_id(future_g_reg)); |
| 213 | + |
| 214 | + metacall_value_destroy(future_g_reg); |
| 215 | + |
| 216 | + /* Await #3: async throw — exception surfaces as reject path */ |
| 217 | + void *future_i_reg = metacall_await("i_reg", metacall_null_args, trampoline_resolve_cb, trampoline_reject_cb, static_cast<void *>(&trampoline_ctx)); |
| 218 | + |
| 219 | + EXPECT_NE((void *)NULL, (void *)future_i_reg); |
| 220 | + EXPECT_EQ((enum metacall_value_id)METACALL_FUTURE, (enum metacall_value_id)metacall_value_id(future_i_reg)); |
| 221 | + |
| 222 | + metacall_value_destroy(future_i_reg); |
| 223 | + } |
| 224 | + |
154 | 225 | /* Test future */ |
155 | 226 | future = metacall("h"); |
156 | 227 |
|
@@ -259,6 +330,12 @@ TEST_F(metacall_node_async_test, DefaultConstructor) |
259 | 330 | { |
260 | 331 | /* Total amount of successful callbacks must be 4 */ |
261 | 332 | EXPECT_EQ((int)success_callbacks, (int)4); |
| 333 | + |
| 334 | + /* Regression #578: exactly 1 resolve and 2 rejects must have fired. |
| 335 | + * Under ASAN (detect_leaks=1) this also confirms zero heap leaks from |
| 336 | + * the trampoline unique_ptr fix in node_loader_trampoline.cpp. */ |
| 337 | + EXPECT_EQ((unsigned int)1, (unsigned int)trampoline_ctx.resolve_callbacks); |
| 338 | + EXPECT_EQ((unsigned int)2, (unsigned int)trampoline_ctx.reject_callbacks); |
262 | 339 | } |
263 | 340 | #endif /* OPTION_BUILD_LOADERS_NODE */ |
264 | 341 | } |
0 commit comments