Skip to content

Commit 6f31fc3

Browse files
Merge pull request #102 from swiftwasm/katei/exception
JS Exception Support
2 parents 16ec18d + 0702441 commit 6f31fc3

File tree

10 files changed

+379
-58
lines changed

10 files changed

+379
-58
lines changed

IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift

+17
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,20 @@ func expectString(_ value: JSValue, file: StaticString = #file, line: UInt = #li
8484
throw MessageError("Type of \(value) should be \"string\"", file: file, line: line, column: column)
8585
}
8686
}
87+
88+
func expectThrow<T>(_ body: @autoclosure () throws -> T, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Error {
89+
do {
90+
_ = try body()
91+
} catch {
92+
return error
93+
}
94+
throw MessageError("Expect to throw an exception", file: file, line: line, column: column)
95+
}
96+
97+
func expectNotNil<T>(_ value: T?, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws {
98+
switch value {
99+
case .some: return
100+
case .none:
101+
throw MessageError("Expect a non-nil value", file: file, line: line, column: column)
102+
}
103+
}

IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift

+54
Original file line numberDiff line numberDiff line change
@@ -517,3 +517,57 @@ try test("JSValue accessor") {
517517
try expectEqual(globalObject1.prop_4[0], .number(3))
518518
try expectEqual(globalObject1.prop_4[1], .number(4))
519519
}
520+
521+
try test("Exception") {
522+
// ```js
523+
// global.globalObject1 = {
524+
// ...
525+
// prop_9: {
526+
// func1: function () {
527+
// throw new Error();
528+
// },
529+
// func2: function () {
530+
// throw "String Error";
531+
// },
532+
// func3: function () {
533+
// throw 3.0
534+
// },
535+
// },
536+
// ...
537+
// }
538+
// ```
539+
//
540+
541+
let globalObject1 = JSObject.global.globalObject1
542+
let prop_9: JSValue = globalObject1.prop_9
543+
544+
// MARK: Throwing method calls
545+
let error1 = try expectThrow(try prop_9.object!.throwing.func1!())
546+
try expectEqual(error1 is JSValue, true)
547+
let errorObject = JSError(from: error1 as! JSValue)
548+
try expectNotNil(errorObject)
549+
550+
let error2 = try expectThrow(try prop_9.object!.throwing.func2!())
551+
try expectEqual(error2 is JSValue, true)
552+
let errorString = try expectString(error2 as! JSValue)
553+
try expectEqual(errorString, "String Error")
554+
555+
let error3 = try expectThrow(try prop_9.object!.throwing.func3!())
556+
try expectEqual(error3 is JSValue, true)
557+
let errorNumber = try expectNumber(error3 as! JSValue)
558+
try expectEqual(errorNumber, 3.0)
559+
560+
// MARK: Simple function calls
561+
let error4 = try expectThrow(try prop_9.func1.function!.throws())
562+
try expectEqual(error4 is JSValue, true)
563+
let errorObject2 = JSError(from: error4 as! JSValue)
564+
try expectNotNil(errorObject2)
565+
566+
// MARK: Throwing constructor call
567+
let Animal = JSObject.global.Animal.function!
568+
_ = try Animal.throws.new("Tama", 3, true)
569+
let ageError = try expectThrow(try Animal.throws.new("Tama", -3, true))
570+
try expectEqual(ageError is JSValue, true)
571+
let errorObject3 = JSError(from: ageError as! JSValue)
572+
try expectNotNil(errorObject3)
573+
}

IntegrationTests/bin/primary-tests.js

+14
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,23 @@ global.globalObject1 = {
3636
},
3737
prop_7: 3.14,
3838
prop_8: [0, , 2, 3, , , 6],
39+
prop_9: {
40+
func1: function () {
41+
throw new Error();
42+
},
43+
func2: function () {
44+
throw "String Error";
45+
},
46+
func3: function () {
47+
throw 3.0
48+
},
49+
},
3950
};
4051

4152
global.Animal = function (name, age, isCat) {
53+
if (age < 0) {
54+
throw new Error("Invalid age " + age);
55+
}
4256
this.name = name;
4357
this.age = age;
4458
this.bark = () => {

Runtime/src/index.ts

+89-28
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class SwiftRuntimeHeap {
118118
export class SwiftRuntime {
119119
private instance: WebAssembly.Instance | null;
120120
private heap: SwiftRuntimeHeap;
121-
private version: number = 700;
121+
private version: number = 701;
122122

123123
constructor() {
124124
this.instance = null;
@@ -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,59 @@ 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(
265+
kind_ptr,
266+
exceptionBit | JavaScriptValueKind.Boolean
267+
);
263268
writeUint32(payload1_ptr, value ? 1 : 0);
264269
break;
265270
}
266271
case "number": {
267-
writeUint32(kind_ptr, JavaScriptValueKind.Number);
272+
writeUint32(
273+
kind_ptr,
274+
exceptionBit | JavaScriptValueKind.Number
275+
);
268276
writeFloat64(payload2_ptr, value);
269277
break;
270278
}
271279
case "string": {
272-
writeUint32(kind_ptr, JavaScriptValueKind.String);
280+
writeUint32(
281+
kind_ptr,
282+
exceptionBit | JavaScriptValueKind.String
283+
);
273284
writeUint32(payload1_ptr, this.heap.retain(value));
274285
break;
275286
}
276287
case "undefined": {
277-
writeUint32(kind_ptr, JavaScriptValueKind.Undefined);
288+
writeUint32(
289+
kind_ptr,
290+
exceptionBit | JavaScriptValueKind.Undefined
291+
);
278292
break;
279293
}
280294
case "object": {
281-
writeUint32(kind_ptr, JavaScriptValueKind.Object);
295+
writeUint32(
296+
kind_ptr,
297+
exceptionBit | JavaScriptValueKind.Object
298+
);
282299
writeUint32(payload1_ptr, this.heap.retain(value));
283300
break;
284301
}
285302
case "function": {
286-
writeUint32(kind_ptr, JavaScriptValueKind.Function);
303+
writeUint32(
304+
kind_ptr,
305+
exceptionBit | JavaScriptValueKind.Function
306+
);
287307
writeUint32(payload1_ptr, this.heap.retain(value));
288308
break;
289309
}
@@ -332,7 +352,7 @@ export class SwiftRuntime {
332352
) => {
333353
const obj = this.heap.referenceHeap(ref);
334354
const result = Reflect.get(obj, readString(name));
335-
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr);
355+
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false);
336356
},
337357
swjs_set_subscript: (
338358
ref: ref,
@@ -353,7 +373,7 @@ export class SwiftRuntime {
353373
) => {
354374
const obj = this.heap.referenceHeap(ref);
355375
const result = Reflect.get(obj, index);
356-
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr);
376+
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false);
357377
},
358378
swjs_encode_string: (ref: ref, bytes_ptr_result: pointer) => {
359379
const bytes = textEncoder.encode(this.heap.referenceHeap(ref));
@@ -383,12 +403,24 @@ export class SwiftRuntime {
383403
payload2_ptr: pointer
384404
) => {
385405
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);
406+
let result: any;
407+
try {
408+
result = Reflect.apply(
409+
func,
410+
undefined,
411+
decodeValues(argv, argc)
412+
);
413+
} catch (error) {
414+
writeValue(
415+
error,
416+
kind_ptr,
417+
payload1_ptr,
418+
payload2_ptr,
419+
true
420+
);
421+
return;
422+
}
423+
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false);
392424
},
393425
swjs_call_function_with_this: (
394426
obj_ref: ref,
@@ -401,12 +433,20 @@ export class SwiftRuntime {
401433
) => {
402434
const obj = this.heap.referenceHeap(obj_ref);
403435
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);
436+
let result: any;
437+
try {
438+
result = Reflect.apply(func, obj, decodeValues(argv, argc));
439+
} catch (error) {
440+
writeValue(
441+
error,
442+
kind_ptr,
443+
payload1_ptr,
444+
payload2_ptr,
445+
true
446+
);
447+
return;
448+
}
449+
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false);
410450
},
411451
swjs_create_function: (
412452
host_func_id: number,
@@ -420,6 +460,31 @@ export class SwiftRuntime {
420460
});
421461
writeUint32(func_ref_ptr, func_ref);
422462
},
463+
swjs_call_throwing_new: (
464+
ref: ref,
465+
argv: pointer,
466+
argc: number,
467+
result_obj: pointer,
468+
exception_kind_ptr: pointer,
469+
exception_payload1_ptr: pointer,
470+
exception_payload2_ptr: pointer
471+
) => {
472+
const obj = this.heap.referenceHeap(ref);
473+
let result: any;
474+
try {
475+
result = Reflect.construct(obj, decodeValues(argv, argc));
476+
} catch (error) {
477+
writeValue(
478+
error,
479+
exception_kind_ptr,
480+
exception_payload1_ptr,
481+
exception_payload2_ptr,
482+
true
483+
);
484+
return;
485+
}
486+
writeUint32(result_obj, this.heap.retain(result));
487+
},
423488
swjs_call_new: (
424489
ref: ref,
425490
argv: pointer,
@@ -428,10 +493,6 @@ export class SwiftRuntime {
428493
) => {
429494
const obj = this.heap.referenceHeap(ref);
430495
const result = Reflect.construct(obj, decodeValues(argv, argc));
431-
if (typeof result != "object")
432-
throw Error(
433-
`Invalid result type of object constructor of "${obj}": "${result}"`
434-
);
435496
writeUint32(result_obj, this.heap.retain(result));
436497
},
437498
swjs_instanceof: (obj_ref: ref, constructor_ref: ref) => {

0 commit comments

Comments
 (0)