Skip to content

Commit c1fdc49

Browse files
author
Guy Bedford
authored
fix: simple cache getOrSet inner rejections to reject outer promise (#981)
1 parent 28c0a02 commit c1fdc49

File tree

5 files changed

+93
-35
lines changed

5 files changed

+93
-35
lines changed

integration-tests/cli/invalid.test.js

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,36 @@ test('should return non-zero exit code on syntax errors', async function (t) {
1313
await writeFile('./bin/index.js', '\n\n\n"hello";@');
1414
const { code, stdout, stderr } = await execute(process.execPath, cli);
1515
t.alike(stdout, []);
16-
strictEqual(
17-
stderr.join('').toString().replace(/\r?\n/g, '').slice(1),
18-
` [ERROR] Expected identifier but found end of file
16+
17+
// TODO: do not even try to fix the mess below if something breaks with it again. Just remove
18+
// cli-testing-library dependency entirely and check stderr directly.
19+
try {
20+
strictEqual(
21+
stderr.join('').toString().replace(/\r?\n/g, '').slice(1),
22+
` [ERROR] Expected identifier but found end of file
1923
bin/index.js:4:9:
2024
4 │ "hello";@
2125
╵ ^
2226
Error: Build failed with 1 error:
2327
bin/index.js:4:9: ERROR: Expected identifier but found end of file`.replace(
24-
/\r?\n/g,
25-
'',
26-
),
27-
);
28+
/\r?\n/g,
29+
'',
30+
),
31+
);
32+
} catch {
33+
strictEqual(
34+
stderr.join('').toString().replace(/\r?\n/g, '').slice(1),
35+
` [ERROR] Expected identifier but found end of file
36+
bin/index.js:4:9:
37+
4 │ "hello";@
38+
╵^
39+
Error: Build failed with 1 error:
40+
bin/index.js:4:9: ERROR: Expected identifier but found end of file`.replace(
41+
/\r?\n/g,
42+
'',
43+
),
44+
);
45+
}
2846

2947
t.is(code, 1);
3048
});

integration-tests/js-compute/fixtures/app/src/cache-simple.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,6 +1529,21 @@ async function simpleCacheEntryInterfaceTests() {
15291529
// static getOrSet(key: string, set: () => Promise<{value: BodyInit, ttl: number}>): Promise<SimpleCacheEntry>;
15301530
// static getOrSet(key: string, set: () => Promise<{value: ReadableStream, ttl: number, length: number}>): Promise<SimpleCacheEntry>;
15311531
{
1532+
routes.set('/simple-cache/getOrSet/rejection-rejects-outer', async () => {
1533+
if (!isRunningLocally()) {
1534+
let key = String(Math.random());
1535+
SimpleCache.get(key);
1536+
assertRejects(
1537+
() =>
1538+
SimpleCache.getOrSet(key, async () => {
1539+
throw RangeError('inner rejection');
1540+
}),
1541+
RangeError,
1542+
'inner rejection',
1543+
);
1544+
}
1545+
});
1546+
15321547
routes.set('/simple-cache/getOrSet/called-as-constructor', () => {
15331548
if (!isRunningLocally()) {
15341549
let key = '/simple-cache/getOrSet/called-as-constructor';

integration-tests/js-compute/fixtures/app/tests.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,9 @@
218218
"GET /simple-cache-entry/readablestream": {
219219
"environments": ["compute"]
220220
},
221+
"GET /simple-cache/getOrSet/rejection-rejects-outer": {
222+
"environments": ["compute"]
223+
},
221224
"GET /simple-cache/getOrSet/called-as-constructor": {
222225
"environments": ["compute"]
223226
},

runtime/fastly/builtins/cache-simple.cpp

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -199,34 +199,30 @@ class CacheTransaction final {
199199
}
200200
};
201201

202-
} // namespace
203-
204-
bool SimpleCache::getOrSetThenHandler(JSContext *cx, JS::HandleObject owner, JS::HandleValue extra,
205-
JS::CallArgs args) {
206-
MOZ_ASSERT(extra.isObject());
207-
JS::RootedObject extraObj(cx, &extra.toObject());
208-
JS::RootedValue handleVal(cx);
209-
JS::RootedValue promiseVal(cx);
210-
if (!JS_GetProperty(cx, extraObj, "promise", &promiseVal)) {
202+
bool get_or_set_then_handler(JSContext *cx, JS::HandleObject lookup_state, JS::HandleValue extra,
203+
JS::CallArgs args) {
204+
JS::RootedValue handle_val(cx);
205+
JS::RootedValue promise_val(cx);
206+
if (!JS_GetProperty(cx, lookup_state, "promise", &promise_val)) {
211207
return false;
212208
}
213-
MOZ_ASSERT(promiseVal.isObject());
214-
JS::RootedObject promise(cx, &promiseVal.toObject());
209+
MOZ_ASSERT(promise_val.isObject());
210+
JS::RootedObject promise(cx, &promise_val.toObject());
215211
if (!promise) {
216212
return ReturnPromiseRejectedWithPendingError(cx, args);
217213
}
218214

219-
if (!JS_GetProperty(cx, extraObj, "handle", &handleVal)) {
215+
if (!JS_GetProperty(cx, lookup_state, "handle", &handle_val)) {
220216
return RejectPromiseWithPendingError(cx, promise);
221217
}
222-
MOZ_ASSERT(handleVal.isInt32());
218+
MOZ_ASSERT(handle_val.isInt32());
223219

224-
host_api::CacheHandle handle(handleVal.toInt32());
220+
host_api::CacheHandle handle(handle_val.toInt32());
225221

226222
BEGIN_TRANSACTION(transaction, cx, promise, handle);
227223

228224
JS::RootedValue keyVal(cx);
229-
if (!JS_GetProperty(cx, extraObj, "key", &keyVal)) {
225+
if (!JS_GetProperty(cx, lookup_state, "key", &keyVal)) {
230226
return false;
231227
}
232228

@@ -376,6 +372,30 @@ bool SimpleCache::getOrSetThenHandler(JSContext *cx, JS::HandleObject owner, JS:
376372
return true;
377373
}
378374

375+
bool get_or_set_catch_handler(JSContext *cx, JS::HandleObject lookup_state,
376+
JS::HandleValue inner_promise_val, JS::CallArgs args) {
377+
JS::RootedValue promise_val(cx);
378+
if (!JS_GetProperty(cx, lookup_state, "promise", &promise_val)) {
379+
return false;
380+
}
381+
MOZ_ASSERT(promise_val.isObject());
382+
JS::RootedObject promise(cx, &promise_val.toObject());
383+
if (!promise) {
384+
return ReturnPromiseRejectedWithPendingError(cx, args);
385+
}
386+
387+
JS::RootedObject inner_promise(cx, &inner_promise_val.toObject());
388+
if (!inner_promise) {
389+
return ReturnPromiseRejectedWithPendingError(cx, args);
390+
}
391+
MOZ_ASSERT(JS::GetPromiseState(inner_promise) == JS::PromiseState::Rejected);
392+
JS::RootedValue promise_rejection_err(cx, JS::GetPromiseResult(inner_promise));
393+
JS::RejectPromise(cx, promise, promise_rejection_err);
394+
return true;
395+
}
396+
397+
} // namespace
398+
379399
// static getOrSet(key: string, set: () => Promise<{value: BodyInit, ttl: number}>):
380400
// SimpleCacheEntry | null; static getOrSet(key: string, set: () => Promise<{value: ReadableStream,
381401
// ttl: number, length: number}>): SimpleCacheEntry | null;
@@ -462,29 +482,34 @@ bool SimpleCache::getOrSet(JSContext *cx, unsigned argc, JS::Value *vp) {
462482
}
463483

464484
// JS::RootedObject owner(cx, JS_NewPlainObject(cx));
465-
JS::RootedObject extraObj(cx, JS_NewPlainObject(cx));
466-
JS::RootedValue handleVal(cx, JS::NumberValue(handle.handle));
467-
if (!JS_SetProperty(cx, extraObj, "handle", handleVal)) {
485+
JS::RootedObject lookup_state(cx, JS_NewPlainObject(cx));
486+
JS::RootedValue handle_val(cx, JS::NumberValue(handle.handle));
487+
if (!JS_SetProperty(cx, lookup_state, "handle", handle_val)) {
468488
return false;
469489
}
470490
JS::RootedValue keyVal(
471491
cx, JS::StringValue(JS_NewStringCopyN(cx, key_chars.begin(), key_chars.len)));
472-
if (!JS_SetProperty(cx, extraObj, "key", keyVal)) {
492+
if (!JS_SetProperty(cx, lookup_state, "key", keyVal)) {
473493
return false;
474494
}
475-
JS::RootedValue promiseVal(cx, JS::ObjectValue(*promise));
476-
if (!JS_SetProperty(cx, extraObj, "promise", promiseVal)) {
495+
JS::RootedValue promise_val(cx, JS::ObjectValue(*promise));
496+
if (!JS_SetProperty(cx, lookup_state, "promise", promise_val)) {
477497
return false;
478498
}
479499

480-
JS::RootedValue extra(cx, JS::ObjectValue(*extraObj));
481500
JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
482-
JS::RootedObject then_handler(cx,
483-
create_internal_method<getOrSetThenHandler>(cx, global, extra));
501+
JS::RootedObject then_handler(
502+
cx, create_internal_method<get_or_set_then_handler>(cx, lookup_state));
503+
if (!then_handler) {
504+
return false;
505+
}
506+
JS::RootedValue result_promise_val(cx, JS::ObjectValue(*result_promise));
507+
JS::RootedObject catch_handler(
508+
cx, create_internal_method<get_or_set_catch_handler>(cx, lookup_state, result_promise_val));
484509
if (!then_handler) {
485510
return false;
486511
}
487-
if (!JS::AddPromiseReactions(cx, result_promise, then_handler, nullptr)) {
512+
if (!JS::AddPromiseReactions(cx, result_promise, then_handler, catch_handler)) {
488513
return false;
489514
}
490515
transaction.commit();

runtime/fastly/builtins/cache-simple.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,6 @@ class SimpleCache : public builtins::BuiltinNoConstructor<SimpleCache> {
4444
static bool purge(JSContext *cx, unsigned argc, JS::Value *vp);
4545
static bool set(JSContext *cx, unsigned argc, JS::Value *vp);
4646
static bool getOrSet(JSContext *cx, unsigned argc, JS::Value *vp);
47-
48-
static bool getOrSetThenHandler(JSContext *cx, JS::HandleObject owner, JS::HandleValue extra,
49-
JS::CallArgs args);
5047
};
5148

5249
} // namespace fastly::cache_simple

0 commit comments

Comments
 (0)