Skip to content

Commit 9db9885

Browse files
Implement JSThrowingFunction
1 parent 34f2f75 commit 9db9885

File tree

5 files changed

+196
-53
lines changed

5 files changed

+196
-53
lines changed

Runtime/src/index.ts

+61-24
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ interface ExportedMemory {
55
type ref = number;
66
type pointer = number;
77

8-
interface GlobalVariable {}
8+
interface GlobalVariable { }
99
declare const window: GlobalVariable;
1010
declare const global: GlobalVariable;
1111
let globalVariable: any;
@@ -151,7 +151,7 @@ export class SwiftRuntime {
151151
for (let index = 0; index < args.length; index++) {
152152
const argument = args[index];
153153
const base = argv + 16 * index;
154-
writeValue(argument, base, base + 4, base + 8);
154+
writeValue(argument, base, base + 4, base + 8, false);
155155
}
156156
let output: any;
157157
const callback_func_ref = this.heap.retain(function (result: any) {
@@ -251,39 +251,41 @@ export class SwiftRuntime {
251251
value: any,
252252
kind_ptr: pointer,
253253
payload1_ptr: pointer,
254-
payload2_ptr: pointer
254+
payload2_ptr: pointer,
255+
is_exception: boolean
255256
) => {
257+
const exceptionBit = (is_exception ? 1 : 0) << 31
256258
if (value === null) {
257-
writeUint32(kind_ptr, JavaScriptValueKind.Null);
259+
writeUint32(kind_ptr, exceptionBit | JavaScriptValueKind.Null);
258260
return;
259261
}
260262
switch (typeof value) {
261263
case "boolean": {
262-
writeUint32(kind_ptr, JavaScriptValueKind.Boolean);
264+
writeUint32(kind_ptr, exceptionBit | JavaScriptValueKind.Boolean);
263265
writeUint32(payload1_ptr, value ? 1 : 0);
264266
break;
265267
}
266268
case "number": {
267-
writeUint32(kind_ptr, JavaScriptValueKind.Number);
269+
writeUint32(kind_ptr, exceptionBit | JavaScriptValueKind.Number);
268270
writeFloat64(payload2_ptr, value);
269271
break;
270272
}
271273
case "string": {
272-
writeUint32(kind_ptr, JavaScriptValueKind.String);
274+
writeUint32(kind_ptr, exceptionBit | JavaScriptValueKind.String);
273275
writeUint32(payload1_ptr, this.heap.retain(value));
274276
break;
275277
}
276278
case "undefined": {
277-
writeUint32(kind_ptr, JavaScriptValueKind.Undefined);
279+
writeUint32(kind_ptr, exceptionBit | JavaScriptValueKind.Undefined);
278280
break;
279281
}
280282
case "object": {
281-
writeUint32(kind_ptr, JavaScriptValueKind.Object);
283+
writeUint32(kind_ptr, exceptionBit | JavaScriptValueKind.Object);
282284
writeUint32(payload1_ptr, this.heap.retain(value));
283285
break;
284286
}
285287
case "function": {
286-
writeUint32(kind_ptr, JavaScriptValueKind.Function);
288+
writeUint32(kind_ptr, exceptionBit | JavaScriptValueKind.Function);
287289
writeUint32(payload1_ptr, this.heap.retain(value));
288290
break;
289291
}
@@ -332,7 +334,7 @@ export class SwiftRuntime {
332334
) => {
333335
const obj = this.heap.referenceHeap(ref);
334336
const result = Reflect.get(obj, readString(name));
335-
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr);
337+
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false);
336338
},
337339
swjs_set_subscript: (
338340
ref: ref,
@@ -353,7 +355,7 @@ export class SwiftRuntime {
353355
) => {
354356
const obj = this.heap.referenceHeap(ref);
355357
const result = Reflect.get(obj, index);
356-
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr);
358+
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false);
357359
},
358360
swjs_encode_string: (ref: ref, bytes_ptr_result: pointer) => {
359361
const bytes = textEncoder.encode(this.heap.referenceHeap(ref));
@@ -383,12 +385,18 @@ export class SwiftRuntime {
383385
payload2_ptr: pointer
384386
) => {
385387
const func = this.heap.referenceHeap(ref);
386-
const result = Reflect.apply(
387-
func,
388-
undefined,
389-
decodeValues(argv, argc)
390-
);
391-
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr);
388+
let result: any;
389+
try {
390+
result = Reflect.apply(
391+
func,
392+
undefined,
393+
decodeValues(argv, argc)
394+
);
395+
} catch (error) {
396+
writeValue(error, kind_ptr, payload1_ptr, payload2_ptr, true);
397+
return;
398+
}
399+
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false);
392400
},
393401
swjs_call_function_with_this: (
394402
obj_ref: ref,
@@ -401,12 +409,18 @@ export class SwiftRuntime {
401409
) => {
402410
const obj = this.heap.referenceHeap(obj_ref);
403411
const func = this.heap.referenceHeap(func_ref);
404-
const result = Reflect.apply(
405-
func,
406-
obj,
407-
decodeValues(argv, argc)
408-
);
409-
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr);
412+
let result: any;
413+
try {
414+
result = Reflect.apply(
415+
func,
416+
obj,
417+
decodeValues(argv, argc)
418+
);
419+
} catch (error) {
420+
writeValue(error, kind_ptr, payload1_ptr, payload2_ptr, true);
421+
return;
422+
}
423+
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false);
410424
},
411425
swjs_create_function: (
412426
host_func_id: number,
@@ -420,6 +434,29 @@ export class SwiftRuntime {
420434
});
421435
writeUint32(func_ref_ptr, func_ref);
422436
},
437+
swjs_call_throwing_new: (
438+
ref: ref,
439+
argv: pointer,
440+
argc: number,
441+
result_obj: pointer,
442+
exception_kind_ptr: pointer,
443+
exception_payload1_ptr: pointer,
444+
exception_payload2_ptr: pointer
445+
) => {
446+
const obj = this.heap.referenceHeap(ref);
447+
let result: any
448+
try {
449+
result = Reflect.construct(obj, decodeValues(argv, argc));
450+
if (typeof result != "object")
451+
throw Error(
452+
`Invalid result type of object constructor of "${obj}": "${result}"`
453+
);
454+
} catch (error) {
455+
writeValue(error, exception_kind_ptr, exception_payload1_ptr, exception_payload2_ptr, true);
456+
return;
457+
}
458+
writeUint32(result_obj, this.heap.retain(result));
459+
},
423460
swjs_call_new: (
424461
ref: ref,
425462
argv: pointer,

Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift

+92-19
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,7 @@ public class JSFunction: JSObject {
1919
/// - Returns: The result of this call.
2020
@discardableResult
2121
public func callAsFunction(this: JSObject? = nil, arguments: [ConvertibleToJSValue]) -> JSValue {
22-
let result = arguments.withRawJSValues { rawValues in
23-
rawValues.withUnsafeBufferPointer { bufferPointer -> RawJSValue in
24-
let argv = bufferPointer.baseAddress
25-
let argc = bufferPointer.count
26-
var result = RawJSValue()
27-
if let thisId = this?.id {
28-
_call_function_with_this(thisId,
29-
self.id, argv, Int32(argc),
30-
&result.kind, &result.payload1, &result.payload2)
31-
} else {
32-
_call_function(
33-
self.id, argv, Int32(argc),
34-
&result.kind, &result.payload1, &result.payload2
35-
)
36-
}
37-
return result
38-
}
39-
}
40-
return result.jsValue()
22+
try! invokeJSFunction(self, arguments: arguments, this: this)
4123
}
4224

4325
/// A variadic arguments version of `callAsFunction`.
@@ -87,6 +69,97 @@ public class JSFunction: JSObject {
8769
}
8870
}
8971

72+
/// A `JSFunction` wrapper that enables throwing function calls.
73+
/// Exceptions produced by JavaScript functions will be thrown as `JSValue`.
74+
public class JSThrowingFunction {
75+
private let base: JSFunction
76+
public init(_ base: JSFunction) {
77+
self.base = base
78+
}
79+
80+
/// Call this function with given `arguments` and binding given `this` as context.
81+
/// - Parameters:
82+
/// - this: The value to be passed as the `this` parameter to this function.
83+
/// - arguments: Arguments to be passed to this function.
84+
/// - Returns: The result of this call.
85+
@discardableResult
86+
public func callAsFunction(this: JSObject? = nil, arguments: [ConvertibleToJSValue]) throws -> JSValue {
87+
try invokeJSFunction(base, arguments: arguments, this: this)
88+
}
89+
90+
/// A variadic arguments version of `callAsFunction`.
91+
@discardableResult
92+
public func callAsFunction(this: JSObject? = nil, _ arguments: ConvertibleToJSValue...) throws -> JSValue {
93+
try self(this: this, arguments: arguments)
94+
}
95+
96+
/// Instantiate an object from this function as a throwing constructor.
97+
///
98+
/// Guaranteed to return an object because either:
99+
///
100+
/// - a. the constructor explicitly returns an object, or
101+
/// - b. the constructor returns nothing, which causes JS to return the `this` value, or
102+
/// - c. the constructor returns undefined, null or a non-object, in which case JS also returns `this`.
103+
///
104+
/// - Parameter arguments: Arguments to be passed to this constructor function.
105+
/// - Returns: A new instance of this constructor.
106+
public func new(arguments: [ConvertibleToJSValue]) throws -> JSObject {
107+
try arguments.withRawJSValues { rawValues -> Result<JSObject, JSValue> in
108+
rawValues.withUnsafeBufferPointer { bufferPointer in
109+
let argv = bufferPointer.baseAddress
110+
let argc = bufferPointer.count
111+
112+
var exceptionKind: JavaScriptValueKind = .invalid
113+
var exceptionPayload1 = JavaScriptPayload1()
114+
var exceptionPayload2 = JavaScriptPayload2()
115+
var resultObj = JavaScriptObjectRef()
116+
_call_throwing_new(
117+
self.base.id, argv, Int32(argc),
118+
&resultObj, &exceptionKind, &exceptionPayload1, &exceptionPayload2
119+
)
120+
if exceptionKind != .invalid {
121+
let exception = RawJSValue(kind: exceptionKind, payload1: exceptionPayload1, payload2: exceptionPayload2)
122+
return .failure(exception.jsValue())
123+
}
124+
return .success(JSObject(id: resultObj))
125+
}
126+
}.get()
127+
}
128+
129+
/// A variadic arguments version of `new`.
130+
public func new(_ arguments: ConvertibleToJSValue...) throws -> JSObject {
131+
try new(arguments: arguments)
132+
}
133+
}
134+
135+
fileprivate func invokeJSFunction(_ jsFunc: JSFunction, arguments: [ConvertibleToJSValue], this: JSObject?) throws -> JSValue {
136+
let (result, isException) = arguments.withRawJSValues { rawValues in
137+
rawValues.withUnsafeBufferPointer { bufferPointer -> (JSValue, Bool) in
138+
let argv = bufferPointer.baseAddress
139+
let argc = bufferPointer.count
140+
var kindAndFlags = JavaScriptValueKindAndFlags()
141+
var payload1 = JavaScriptPayload1()
142+
var payload2 = JavaScriptPayload2()
143+
if let thisId = this?.id {
144+
_call_function_with_this(thisId,
145+
jsFunc.id, argv, Int32(argc),
146+
&kindAndFlags, &payload1, &payload2)
147+
} else {
148+
_call_function(
149+
jsFunc.id, argv, Int32(argc),
150+
&kindAndFlags, &payload1, &payload2
151+
)
152+
}
153+
let result = RawJSValue(kind: kindAndFlags.kind, payload1: payload1, payload2: payload2)
154+
return (result.jsValue(), kindAndFlags.isException)
155+
}
156+
}
157+
if isException {
158+
throw result
159+
}
160+
return result
161+
}
162+
90163
/// `JSClosure` represents a JavaScript function the body of which is written in Swift.
91164
/// This type can be passed as a callback handler to JavaScript functions.
92165
/// Note that the lifetime of `JSClosure` should be managed by users manually

Sources/JavaScriptKit/JSValue.swift

+2
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ extension JSValue {
102102
}
103103
}
104104

105+
extension JSValue: Swift.Error {}
106+
105107
extension JSValue {
106108
public func fromJSValue<Type>() -> Type? where Type: ConstructibleFromJSValue {
107109
return Type.construct(from: self)

Sources/JavaScriptKit/XcodeSupport.swift

+10-2
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ import _CJavaScriptKit
4949
func _call_function(
5050
_: JavaScriptObjectRef,
5151
_: UnsafePointer<RawJSValue>!, _: Int32,
52-
_: UnsafeMutablePointer<JavaScriptValueKind>!,
52+
_: UnsafeMutablePointer<JavaScriptValueKindAndFlags>!,
5353
_: UnsafeMutablePointer<JavaScriptPayload1>!,
5454
_: UnsafeMutablePointer<JavaScriptPayload2>!
5555
) { fatalError() }
5656
func _call_function_with_this(
5757
_: JavaScriptObjectRef,
5858
_: JavaScriptObjectRef,
5959
_: UnsafePointer<RawJSValue>!, _: Int32,
60-
_: UnsafeMutablePointer<JavaScriptValueKind>!,
60+
_: UnsafeMutablePointer<JavaScriptValueKindAndFlags>!,
6161
_: UnsafeMutablePointer<JavaScriptPayload1>!,
6262
_: UnsafeMutablePointer<JavaScriptPayload2>!
6363
) { fatalError() }
@@ -66,6 +66,14 @@ import _CJavaScriptKit
6666
_: UnsafePointer<RawJSValue>!, _: Int32,
6767
_: UnsafeMutablePointer<JavaScriptObjectRef>!
6868
) { fatalError() }
69+
func _call_throwing_new(
70+
_: JavaScriptObjectRef,
71+
_: UnsafePointer<RawJSValue>!, _: Int32,
72+
_: UnsafeMutablePointer<JavaScriptObjectRef>!,
73+
_: UnsafeMutablePointer<JavaScriptValueKind>!,
74+
_: UnsafeMutablePointer<JavaScriptPayload1>!,
75+
_: UnsafeMutablePointer<JavaScriptPayload2>!
76+
) { fatalError() }
6977
func _instanceof(
7078
_: JavaScriptObjectRef,
7179
_: JavaScriptObjectRef

0 commit comments

Comments
 (0)