Skip to content

JS Exception Support #102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,20 @@ func expectString(_ value: JSValue, file: StaticString = #file, line: UInt = #li
throw MessageError("Type of \(value) should be \"string\"", file: file, line: line, column: column)
}
}

func expectThrow<T>(_ body: @autoclosure () throws -> T, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> Error {
do {
_ = try body()
} catch {
return error
}
throw MessageError("Expect to throw an exception", file: file, line: line, column: column)
}

func expectNotNil<T>(_ value: T?, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws {
switch value {
case .some: return
case .none:
throw MessageError("Expect a non-nil value", file: file, line: line, column: column)
}
}
54 changes: 54 additions & 0 deletions IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -517,3 +517,57 @@ try test("JSValue accessor") {
try expectEqual(globalObject1.prop_4[0], .number(3))
try expectEqual(globalObject1.prop_4[1], .number(4))
}

try test("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 expectThrow(try prop_9.object!.throwing.func1!())
try expectEqual(error1 is JSValue, true)
let errorObject = JSError(from: error1 as! JSValue)
try expectNotNil(errorObject)

let error2 = try expectThrow(try prop_9.object!.throwing.func2!())
try expectEqual(error2 is JSValue, true)
let errorString = try expectString(error2 as! JSValue)
try expectEqual(errorString, "String Error")

let error3 = try expectThrow(try prop_9.object!.throwing.func3!())
try expectEqual(error3 is JSValue, true)
let errorNumber = try expectNumber(error3 as! JSValue)
try expectEqual(errorNumber, 3.0)

// MARK: Simple function calls
let error4 = try expectThrow(try prop_9.func1.function!.throws())
try expectEqual(error4 is JSValue, true)
let errorObject2 = JSError(from: error4 as! JSValue)
try expectNotNil(errorObject2)

// MARK: Throwing constructor call
let Animal = JSObject.global.Animal.function!
_ = try Animal.throws.new("Tama", 3, true)
let ageError = try expectThrow(try Animal.throws.new("Tama", -3, true))
try expectEqual(ageError is JSValue, true)
let errorObject3 = JSError(from: ageError as! JSValue)
try expectNotNil(errorObject3)
}
14 changes: 14 additions & 0 deletions IntegrationTests/bin/primary-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,23 @@ global.globalObject1 = {
},
prop_7: 3.14,
prop_8: [0, , 2, 3, , , 6],
prop_9: {
func1: function () {
throw new Error();
},
func2: function () {
throw "String Error";
},
func3: function () {
throw 3.0
},
},
};

global.Animal = function (name, age, isCat) {
if (age < 0) {
throw new Error("Invalid age " + age);
}
this.name = name;
this.age = age;
this.bark = () => {
Expand Down
117 changes: 89 additions & 28 deletions Runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class SwiftRuntimeHeap {
export class SwiftRuntime {
private instance: WebAssembly.Instance | null;
private heap: SwiftRuntimeHeap;
private version: number = 700;
private version: number = 701;

constructor() {
this.instance = null;
Expand Down Expand Up @@ -151,7 +151,7 @@ export class SwiftRuntime {
for (let index = 0; index < args.length; index++) {
const argument = args[index];
const base = argv + 16 * index;
writeValue(argument, base, base + 4, base + 8);
writeValue(argument, base, base + 4, base + 8, false);
}
let output: any;
const callback_func_ref = this.heap.retain(function (result: any) {
Expand Down Expand Up @@ -251,39 +251,59 @@ export class SwiftRuntime {
value: any,
kind_ptr: pointer,
payload1_ptr: pointer,
payload2_ptr: pointer
payload2_ptr: pointer,
is_exception: boolean
) => {
const exceptionBit = (is_exception ? 1 : 0) << 31;
if (value === null) {
writeUint32(kind_ptr, JavaScriptValueKind.Null);
writeUint32(kind_ptr, exceptionBit | JavaScriptValueKind.Null);
return;
}
switch (typeof value) {
case "boolean": {
writeUint32(kind_ptr, JavaScriptValueKind.Boolean);
writeUint32(
kind_ptr,
exceptionBit | JavaScriptValueKind.Boolean
);
writeUint32(payload1_ptr, value ? 1 : 0);
break;
}
case "number": {
writeUint32(kind_ptr, JavaScriptValueKind.Number);
writeUint32(
kind_ptr,
exceptionBit | JavaScriptValueKind.Number
);
writeFloat64(payload2_ptr, value);
break;
}
case "string": {
writeUint32(kind_ptr, JavaScriptValueKind.String);
writeUint32(
kind_ptr,
exceptionBit | JavaScriptValueKind.String
);
writeUint32(payload1_ptr, this.heap.retain(value));
break;
}
case "undefined": {
writeUint32(kind_ptr, JavaScriptValueKind.Undefined);
writeUint32(
kind_ptr,
exceptionBit | JavaScriptValueKind.Undefined
);
break;
}
case "object": {
writeUint32(kind_ptr, JavaScriptValueKind.Object);
writeUint32(
kind_ptr,
exceptionBit | JavaScriptValueKind.Object
);
writeUint32(payload1_ptr, this.heap.retain(value));
break;
}
case "function": {
writeUint32(kind_ptr, JavaScriptValueKind.Function);
writeUint32(
kind_ptr,
exceptionBit | JavaScriptValueKind.Function
);
writeUint32(payload1_ptr, this.heap.retain(value));
break;
}
Expand Down Expand Up @@ -332,7 +352,7 @@ export class SwiftRuntime {
) => {
const obj = this.heap.referenceHeap(ref);
const result = Reflect.get(obj, readString(name));
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr);
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false);
},
swjs_set_subscript: (
ref: ref,
Expand All @@ -353,7 +373,7 @@ export class SwiftRuntime {
) => {
const obj = this.heap.referenceHeap(ref);
const result = Reflect.get(obj, index);
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr);
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false);
},
swjs_encode_string: (ref: ref, bytes_ptr_result: pointer) => {
const bytes = textEncoder.encode(this.heap.referenceHeap(ref));
Expand Down Expand Up @@ -383,12 +403,24 @@ export class SwiftRuntime {
payload2_ptr: pointer
) => {
const func = this.heap.referenceHeap(ref);
const result = Reflect.apply(
func,
undefined,
decodeValues(argv, argc)
);
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr);
let result: any;
try {
result = Reflect.apply(
func,
undefined,
decodeValues(argv, argc)
);
} catch (error) {
writeValue(
error,
kind_ptr,
payload1_ptr,
payload2_ptr,
true
);
return;
}
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false);
},
swjs_call_function_with_this: (
obj_ref: ref,
Expand All @@ -401,12 +433,20 @@ export class SwiftRuntime {
) => {
const obj = this.heap.referenceHeap(obj_ref);
const func = this.heap.referenceHeap(func_ref);
const result = Reflect.apply(
func,
obj,
decodeValues(argv, argc)
);
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr);
let result: any;
try {
result = Reflect.apply(func, obj, decodeValues(argv, argc));
} catch (error) {
writeValue(
error,
kind_ptr,
payload1_ptr,
payload2_ptr,
true
);
return;
}
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false);
},
swjs_create_function: (
host_func_id: number,
Expand All @@ -420,6 +460,31 @@ export class SwiftRuntime {
});
writeUint32(func_ref_ptr, func_ref);
},
swjs_call_throwing_new: (
ref: ref,
argv: pointer,
argc: number,
result_obj: pointer,
exception_kind_ptr: pointer,
exception_payload1_ptr: pointer,
exception_payload2_ptr: pointer
) => {
const obj = this.heap.referenceHeap(ref);
let result: any;
try {
result = Reflect.construct(obj, decodeValues(argv, argc));
} catch (error) {
writeValue(
error,
exception_kind_ptr,
exception_payload1_ptr,
exception_payload2_ptr,
true
);
return;
}
writeUint32(result_obj, this.heap.retain(result));
},
swjs_call_new: (
ref: ref,
argv: pointer,
Expand All @@ -428,10 +493,6 @@ export class SwiftRuntime {
) => {
const obj = this.heap.referenceHeap(ref);
const result = Reflect.construct(obj, decodeValues(argv, argc));
if (typeof result != "object")
throw Error(
`Invalid result type of object constructor of "${obj}": "${result}"`
);
writeUint32(result_obj, this.heap.retain(result));
},
swjs_instanceof: (obj_ref: ref, constructor_ref: ref) => {
Expand Down
Loading