@@ -191,10 +191,76 @@ struct FunctionContext: public ContextGlobalObject {
191191 });
192192 }
193193
194+ kj::String testTryCatch2 (Lock& js, jsg::Function<int ()> thrower) {
195+ // Here we prove that the macro is if-else friendly.
196+ if (true ) JSG_TRY (js) {
197+ return kj::str (thrower (js));
198+ }
199+ JSG_CATCH (exception) {
200+ auto handle = exception.getHandle (js);
201+ return kj::str (" caught: " , handle);
202+ }
203+ else {
204+ KJ_UNREACHABLE;
205+ }
206+ }
207+
208+ kj::String testTryCatchWithOptions (Lock& js, jsg::Function<void ()> thrower) {
209+ // Test that JSG_CATCH can accept ExceptionToJsOptions.
210+ JSG_TRY (js) {
211+ thrower (js);
212+ return kj::str (" no exception" );
213+ }
214+ JSG_CATCH (exception, {.ignoreDetail = true }) {
215+ auto handle = exception.getHandle (js);
216+ return kj::str (" caught with options: " , handle);
217+ }
218+ }
219+
220+ kj::String testNestedTryCatchInnerCatches (Lock& js, jsg::Function<void ()> thrower) {
221+ // Test nested JSG_TRY/JSG_CATCH where inner catches, outer doesn't see exception.
222+ JSG_TRY (js) {
223+ kj::String innerResult;
224+ JSG_TRY (js) {
225+ thrower (js);
226+ innerResult = kj::str (" inner: no exception" );
227+ }
228+ JSG_CATCH (innerException) {
229+ innerResult = kj::str (" inner caught: " , innerException.getHandle (js));
230+ }
231+ return kj::str (" outer: no exception, " , innerResult);
232+ }
233+ JSG_CATCH (outerException) {
234+ return kj::str (" outer caught: " , outerException.getHandle (js));
235+ }
236+ }
237+
238+ kj::String testNestedTryCatchOuterCatches (Lock& js, jsg::Function<void ()> thrower) {
239+ // Test nested JSG_TRY/JSG_CATCH where inner rethrows, outer catches.
240+ JSG_TRY (js) {
241+ JSG_TRY (js) {
242+ thrower (js);
243+ return kj::str (" inner: no exception" );
244+ }
245+ JSG_CATCH (innerException) {
246+ // Rethrow so outer can catch
247+ js.throwException (kj::mv (innerException));
248+ }
249+ return kj::str (" outer: no exception" );
250+ }
251+ JSG_CATCH (outerException) {
252+ return kj::str (" outer caught: " , outerException.getHandle (js));
253+ }
254+ }
255+
194256 JSG_RESOURCE_TYPE (FunctionContext) {
195257 JSG_METHOD (test);
196258 JSG_METHOD (test2);
197259 JSG_METHOD (testTryCatch);
260+ JSG_METHOD (testTryCatch2);
261+ JSG_METHOD (testTryCatchWithOptions);
262+ JSG_METHOD (testNestedTryCatchInnerCatches);
263+ JSG_METHOD (testNestedTryCatchOuterCatches);
198264
199265 JSG_READONLY_PROTOTYPE_PROPERTY (square, getSquare);
200266 JSG_READONLY_PROTOTYPE_PROPERTY (gcLambda, getGcLambda);
@@ -220,6 +286,57 @@ KJ_TEST("jsg::Function<T>") {
220286
221287 e.expectEval (" testTryCatch(() => { return 123; })" , " string" , " 123" );
222288 e.expectEval (" testTryCatch(() => { throw new Error('foo'); })" , " string" , " caught: Error: foo" );
289+
290+ e.expectEval (" testTryCatch2(() => { return 123; })" , " string" , " 123" );
291+ e.expectEval (" testTryCatch2(() => { throw new Error('foo'); })" , " string" , " caught: Error: foo" );
292+
293+ e.expectEval (" testTryCatchWithOptions(() => {})" , " string" , " no exception" );
294+ e.expectEval (" testTryCatchWithOptions(() => { throw new Error('bar'); })" , " string" ,
295+ " caught with options: Error: bar" );
296+
297+ // Nested JSG_TRY/JSG_CATCH tests
298+ e.expectEval (" testNestedTryCatchInnerCatches(() => {})" , " string" ,
299+ " outer: no exception, inner: no exception" );
300+ e.expectEval (" testNestedTryCatchInnerCatches(() => { throw new Error('inner'); })" , " string" ,
301+ " outer: no exception, inner caught: Error: inner" );
302+
303+ e.expectEval (" testNestedTryCatchOuterCatches(() => {})" , " string" , " inner: no exception" );
304+ e.expectEval (" testNestedTryCatchOuterCatches(() => { throw new Error('rethrown'); })" , " string" ,
305+ " outer caught: Error: rethrown" );
306+ }
307+
308+ KJ_TEST (" JSG_TRY/JSG_CATCH with TerminateExecution" ) {
309+ Evaluator<FunctionContext, FunctionIsolate> e (v8System);
310+
311+ // TerminateExecution should propagate through JSG_CATCH without being caught.
312+ // The Evaluator's run() method will detect the termination and throw.
313+ KJ_EXPECT_THROW_MESSAGE (" TerminateExecution() was called" , e.run ([](auto & js) {
314+ // Test single-level JSG_TRY/JSG_CATCH with TerminateExecution
315+ JSG_TRY (js) {
316+ js.terminateExecutionNow ();
317+ }
318+ JSG_CATCH (exception) {
319+ (void )exception;
320+ KJ_FAIL_ASSERT (" TerminateExecution was caught by JSG_CATCH" );
321+ }
322+ }));
323+
324+ KJ_EXPECT_THROW_MESSAGE (" TerminateExecution() was called" , e.run ([](auto & js) {
325+ // Test nested JSG_TRY/JSG_CATCH with TerminateExecution - should propagate through both
326+ JSG_TRY (js) {
327+ JSG_TRY (js) {
328+ js.terminateExecutionNow ();
329+ }
330+ JSG_CATCH (innerException) {
331+ (void )innerException;
332+ KJ_FAIL_ASSERT (" TerminateExecution was caught by inner JSG_CATCH" );
333+ }
334+ }
335+ JSG_CATCH (outerException) {
336+ (void )outerException;
337+ KJ_FAIL_ASSERT (" TerminateExecution was caught by outer JSG_CATCH" );
338+ }
339+ }));
223340}
224341
225342} // namespace
0 commit comments