diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift index 87ab72bc4..ab11bc017 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift @@ -103,6 +103,12 @@ func expectThrow(_ body: @autoclosure () throws -> T, file: StaticString = #f throw MessageError("Expect to throw an exception", file: file, line: line, column: column) } +func wrapUnsafeThrowableFunction(_ body: @escaping () -> Void, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Error { + JSObject.global.callThrowingClosure.function!(JSClosure { _ in + body() + return .undefined + }) +} func expectNotNil(_ value: T?, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws { switch value { case .some: return diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index e876b60e0..8136f345e 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -700,7 +700,6 @@ try test("Exception") { // } // ``` // - let globalObject1 = JSObject.global.globalObject1 let prop_9: JSValue = globalObject1.prop_9 @@ -735,6 +734,46 @@ try test("Exception") { try expectNotNil(errorObject3) } +try test("Unhandled Exception") { + // ```js + // global.globalObject1 = { + // ... + // prop_9: { + // func1: function () { + // throw new Error(); + // }, + // func2: function () { + // throw "String Error"; + // }, + // func3: function () { + // throw 3.0 + // }, + // }, + // ... + // } + // ``` + // + + let globalObject1 = JSObject.global.globalObject1 + let prop_9: JSValue = globalObject1.prop_9 + + // MARK: Throwing method calls + let error1 = try wrapUnsafeThrowableFunction { _ = prop_9.object!.func1!() } + try expectEqual(error1 is JSValue, true) + let errorObject = JSError(from: error1 as! JSValue) + try expectNotNil(errorObject) + + let error2 = try wrapUnsafeThrowableFunction { _ = prop_9.object!.func2!() } + try expectEqual(error2 is JSValue, true) + let errorString = try expectString(error2 as! JSValue) + try expectEqual(errorString, "String Error") + + let error3 = try wrapUnsafeThrowableFunction { _ = prop_9.object!.func3!() } + try expectEqual(error3 is JSValue, true) + let errorNumber = try expectNumber(error3 as! JSValue) + try expectEqual(errorNumber, 3.0) +} + /// If WebAssembly.Memory is not accessed correctly (i.e. creating a new view each time), /// this test will fail with `TypeError: Cannot perform Construct on a detached ArrayBuffer`, /// since asking to grow memory will detach the backing ArrayBuffer. diff --git a/IntegrationTests/bin/primary-tests.js b/IntegrationTests/bin/primary-tests.js index 79c62776a..597590bd4 100644 --- a/IntegrationTests/bin/primary-tests.js +++ b/IntegrationTests/bin/primary-tests.js @@ -82,6 +82,14 @@ global.Animal = function (name, age, isCat) { } }; +global.callThrowingClosure = (c) => { + try { + c() + } catch (error) { + return error + } +} + const { startWasiTask } = require("../lib"); startWasiTask("./dist/PrimaryTests.wasm").catch((err) => { diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index baf9ffd17..06fecc286 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -14,7 +14,7 @@ export class SwiftRuntime { private _instance: WebAssembly.Instance | null; private _memory: Memory | null; private _closureDeallocator: SwiftClosureDeallocator | null; - private version: number = 705; + private version: number = 706; private textDecoder = new TextDecoder("utf-8"); private textEncoder = new TextEncoder(); // Only support utf-8 @@ -226,6 +226,44 @@ export class SwiftRuntime { this.memory ); }, + swjs_call_function_no_catch: ( + ref: ref, + argv: pointer, + argc: number, + kind_ptr: pointer, + payload1_ptr: pointer, + payload2_ptr: pointer + ) => { + const func = this.memory.getObject(ref); + let isException = true; + try { + const result = Reflect.apply( + func, + undefined, + JSValue.decodeArray(argv, argc, this.memory) + ); + JSValue.write( + result, + kind_ptr, + payload1_ptr, + payload2_ptr, + false, + this.memory + ); + isException = false; + } finally { + if (isException) { + JSValue.write( + undefined, + kind_ptr, + payload1_ptr, + payload2_ptr, + true, + this.memory + ); + } + } + }, swjs_call_function_with_this: ( obj_ref: ref, @@ -265,6 +303,47 @@ export class SwiftRuntime { this.memory ); }, + + swjs_call_function_with_this_no_catch: ( + obj_ref: ref, + func_ref: ref, + argv: pointer, + argc: number, + kind_ptr: pointer, + payload1_ptr: pointer, + payload2_ptr: pointer + ) => { + const obj = this.memory.getObject(obj_ref); + const func = this.memory.getObject(func_ref); + let isException = true; + try { + const result = Reflect.apply( + func, + obj, + JSValue.decodeArray(argv, argc, this.memory) + ); + JSValue.write( + result, + kind_ptr, + payload1_ptr, + payload2_ptr, + false, + this.memory + ); + isException = false + } finally { + if (isException) { + JSValue.write( + undefined, + kind_ptr, + payload1_ptr, + payload2_ptr, + true, + this.memory + ); + } + } + }, swjs_call_new: (ref: ref, argv: pointer, argc: number) => { const constructor = this.memory.getObject(ref); const instance = Reflect.construct( diff --git a/Runtime/src/types.ts b/Runtime/src/types.ts index 4f613cc6a..b291cd913 100644 --- a/Runtime/src/types.ts +++ b/Runtime/src/types.ts @@ -58,6 +58,14 @@ export interface ImportedFunctions { payload1_ptr: pointer, payload2_ptr: pointer ): void; + swjs_call_function_no_catch( + ref: number, + argv: pointer, + argc: number, + kind_ptr: pointer, + payload1_ptr: pointer, + payload2_ptr: pointer + ): void; swjs_call_function_with_this( obj_ref: ref, func_ref: ref, @@ -67,6 +75,15 @@ export interface ImportedFunctions { payload1_ptr: pointer, payload2_ptr: pointer ): void; + swjs_call_function_with_this_no_catch( + obj_ref: ref, + func_ref: ref, + argv: pointer, + argc: number, + kind_ptr: pointer, + payload1_ptr: pointer, + payload2_ptr: pointer + ): void; swjs_call_new(ref: number, argv: pointer, argc: number): number; swjs_call_throwing_new( ref: number, diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift index d9d66ff94..0d3a917c0 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift @@ -19,7 +19,7 @@ public class JSFunction: JSObject { /// - Returns: The result of this call. @discardableResult public func callAsFunction(this: JSObject? = nil, arguments: [ConvertibleToJSValue]) -> JSValue { - try! invokeJSFunction(self, arguments: arguments, this: this) + invokeNonThrowingJSFunction(self, arguments: arguments, this: this) } /// A variadic arguments version of `callAsFunction`. @@ -84,30 +84,27 @@ public class JSFunction: JSObject { } } -internal func invokeJSFunction(_ jsFunc: JSFunction, arguments: [ConvertibleToJSValue], this: JSObject?) throws -> JSValue { - let (result, isException) = arguments.withRawJSValues { rawValues in - rawValues.withUnsafeBufferPointer { bufferPointer -> (JSValue, Bool) in +private func invokeNonThrowingJSFunction(_ jsFunc: JSFunction, arguments: [ConvertibleToJSValue], this: JSObject?) -> JSValue { + arguments.withRawJSValues { rawValues in + rawValues.withUnsafeBufferPointer { bufferPointer -> (JSValue) in let argv = bufferPointer.baseAddress let argc = bufferPointer.count var kindAndFlags = JavaScriptValueKindAndFlags() var payload1 = JavaScriptPayload1() var payload2 = JavaScriptPayload2() if let thisId = this?.id { - _call_function_with_this(thisId, + _call_function_with_this_no_catch(thisId, jsFunc.id, argv, Int32(argc), &kindAndFlags, &payload1, &payload2) } else { - _call_function( + _call_function_no_catch( jsFunc.id, argv, Int32(argc), &kindAndFlags, &payload1, &payload2 ) } + assert(!kindAndFlags.isException) let result = RawJSValue(kind: kindAndFlags.kind, payload1: payload1, payload2: payload2) - return (result.jsValue(), kindAndFlags.isException) + return result.jsValue() } } - if isException { - throw result - } - return result } diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift b/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift index 0fe96d318..adcd82a63 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift @@ -62,3 +62,31 @@ public class JSThrowingFunction { try new(arguments: arguments) } } + +private func invokeJSFunction(_ jsFunc: JSFunction, arguments: [ConvertibleToJSValue], this: JSObject?) throws -> JSValue { + let (result, isException) = arguments.withRawJSValues { rawValues in + rawValues.withUnsafeBufferPointer { bufferPointer -> (JSValue, Bool) in + let argv = bufferPointer.baseAddress + let argc = bufferPointer.count + var kindAndFlags = JavaScriptValueKindAndFlags() + var payload1 = JavaScriptPayload1() + var payload2 = JavaScriptPayload2() + if let thisId = this?.id { + _call_function_with_this(thisId, + jsFunc.id, argv, Int32(argc), + &kindAndFlags, &payload1, &payload2) + } else { + _call_function( + jsFunc.id, argv, Int32(argc), + &kindAndFlags, &payload1, &payload2 + ) + } + let result = RawJSValue(kind: kindAndFlags.kind, payload1: payload1, payload2: payload2) + return (result.jsValue(), kindAndFlags.isException) + } + } + if isException { + throw result + } + return result +} diff --git a/Sources/JavaScriptKit/XcodeSupport.swift b/Sources/JavaScriptKit/XcodeSupport.swift index 5bb02e3a3..013c49e2e 100644 --- a/Sources/JavaScriptKit/XcodeSupport.swift +++ b/Sources/JavaScriptKit/XcodeSupport.swift @@ -53,6 +53,13 @@ import _CJavaScriptKit _: UnsafeMutablePointer!, _: UnsafeMutablePointer! ) { fatalError() } + func _call_function_no_catch( + _: JavaScriptObjectRef, + _: UnsafePointer!, _: Int32, + _: UnsafeMutablePointer!, + _: UnsafeMutablePointer!, + _: UnsafeMutablePointer! + ) { fatalError() } func _call_function_with_this( _: JavaScriptObjectRef, _: JavaScriptObjectRef, @@ -61,6 +68,14 @@ import _CJavaScriptKit _: UnsafeMutablePointer!, _: UnsafeMutablePointer! ) { fatalError() } + func _call_function_with_this_no_catch( + _: JavaScriptObjectRef, + _: JavaScriptObjectRef, + _: UnsafePointer!, _: Int32, + _: UnsafeMutablePointer!, + _: UnsafeMutablePointer!, + _: UnsafeMutablePointer! + ) { fatalError() } func _call_new( _: JavaScriptObjectRef, _: UnsafePointer!, _: Int32 diff --git a/Sources/_CJavaScriptKit/_CJavaScriptKit.c b/Sources/_CJavaScriptKit/_CJavaScriptKit.c index 38329ff14..c263b8f71 100644 --- a/Sources/_CJavaScriptKit/_CJavaScriptKit.c +++ b/Sources/_CJavaScriptKit/_CJavaScriptKit.c @@ -36,7 +36,7 @@ void swjs_cleanup_host_function_call(void *argv_buffer) { /// this and `SwiftRuntime.version` in `./Runtime/src/index.ts`. __attribute__((export_name("swjs_library_version"))) int swjs_library_version(void) { - return 705; + return 706; } int _library_features(void); diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index 8979cee56..ce0bf5862 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -170,6 +170,21 @@ extern void _call_function(const JavaScriptObjectRef ref, const RawJSValue *argv JavaScriptPayload1 *result_payload1, JavaScriptPayload2 *result_payload2); +/// `_call_function` calls JavaScript function with given arguments list without capturing any exception +/// +/// @param ref The target JavaScript function to call. +/// @param argv A list of `RawJSValue` arguments to apply. +/// @param argc The length of `argv``. +/// @param result_kind A result pointer of JavaScript value kind of returned result or thrown exception. +/// @param result_payload1 A result pointer of first payload of JavaScript value of returned result or thrown exception. +/// @param result_payload2 A result pointer of second payload of JavaScript value of returned result or thrown exception. +__attribute__((__import_module__("javascript_kit"), + __import_name__("swjs_call_function_no_catch"))) +extern void _call_function_no_catch(const JavaScriptObjectRef ref, const RawJSValue *argv, + const int argc, JavaScriptValueKindAndFlags *result_kind, + JavaScriptPayload1 *result_payload1, + JavaScriptPayload2 *result_payload2); + /// `_call_function_with_this` calls JavaScript function with given arguments list and given `_this`. /// /// @param _this The value of `this` provided for the call to `func_ref`. @@ -188,6 +203,24 @@ extern void _call_function_with_this(const JavaScriptObjectRef _this, JavaScriptPayload1 *result_payload1, JavaScriptPayload2 *result_payload2); +/// `_call_function_with_this` calls JavaScript function with given arguments list and given `_this` without capturing any exception. +/// +/// @param _this The value of `this` provided for the call to `func_ref`. +/// @param func_ref The target JavaScript function to call. +/// @param argv A list of `RawJSValue` arguments to apply. +/// @param argc The length of `argv``. +/// @param result_kind A result pointer of JavaScript value kind of returned result or thrown exception. +/// @param result_payload1 A result pointer of first payload of JavaScript value of returned result or thrown exception. +/// @param result_payload2 A result pointer of second payload of JavaScript value of returned result or thrown exception. +__attribute__((__import_module__("javascript_kit"), + __import_name__("swjs_call_function_with_this_no_catch"))) +extern void _call_function_with_this_no_catch(const JavaScriptObjectRef _this, + const JavaScriptObjectRef func_ref, + const RawJSValue *argv, const int argc, + JavaScriptValueKindAndFlags *result_kind, + JavaScriptPayload1 *result_payload1, + JavaScriptPayload2 *result_payload2); + /// `_call_new` calls JavaScript object constructor with given arguments list. /// /// @param ref The target JavaScript constructor to call.