Skip to content
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
15 changes: 14 additions & 1 deletion Sources/Substrata/Internals/Callbacks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,20 @@
JS_FreeAtom(context.ref, instanceAtom)

if classType.waitingToAttach == nil {
instance.construct(args: args)
do {
try instance.construct(args: args)
} catch {
// Clean up the resources we allocated
result.free(context)
ptr.deallocate()

let jsError = JSError.from(error)
if let errorValue = jsError.toJSValue(context: context) {
return JS_Throw(context.ref, errorValue)
}

return JS_Throw(context.ref, JS_NewError(context.ref))

Check warning on line 74 in Sources/Substrata/Internals/Callbacks.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Substrata/Internals/Callbacks.swift#L73-L74

Added lines #L73 - L74 were not covered by tests
}
}

// set up our instance properties ...
Expand Down
10 changes: 10 additions & 0 deletions Sources/Substrata/Internals/Context.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@
}
}

extension JSContext {
func throwError(_ error: Error) -> JSValue {
let jsError = JSError.from(error)
if let errorValue = jsError.toJSValue(context: self) {
return JS_Throw(self.ref, errorValue)
}
return JS_Throw(self.ref, JS_NewError(self.ref))

Check warning on line 79 in Sources/Substrata/Internals/Context.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Substrata/Internals/Context.swift#L79

Added line #L79 was not covered by tests
}
}

extension JSContext {
func newContextClassID() -> Int32 {
return exportLock.perform {
Expand Down
90 changes: 70 additions & 20 deletions Sources/Substrata/Internals/Conversion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,42 @@
// MARK: - Call conversion funcs

internal func returnJSValueRef(context: JSContext, function: JSFunctionDefinition, args: [JSConvertible]) -> JSValue {
// make sure we're consistent with our types.
// nil = undefined in js.
// nsnull = null in js.
var result = JSValue.undefined
let v = function(args) as? JSInternalConvertible
if let v = v?.toJSValue(context: context) {
result = v
do {
let v = try function(args) as? JSInternalConvertible
if let v = v?.toJSValue(context: context) {
return v
}
return JSValue.undefined
} catch {
return context.throwError(error)
}

return result
}

internal func returnJSValueRef(context: JSContext, function: JSPropertyGetterDefinition) -> JSValue {
// make sure we're consistent with our types.
// nil = undefined in js.
// nsnull = null in js.
var result = JSValue.undefined
let v = function() as? JSInternalConvertible
if let v = v?.toJSValue(context: context) {
result = v
do {
let v = try function() as? JSInternalConvertible
if let v = v?.toJSValue(context: context) {
return v
}
return JSValue.undefined

Check warning on line 34 in Sources/Substrata/Internals/Conversion.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Substrata/Internals/Conversion.swift#L34

Added line #L34 was not covered by tests
} catch {
return context.throwError(error)
}

return result
}

internal func returnJSValueRef(context: JSContext, function: JSPropertySetterDefinition, arg: JSConvertible?) -> JSValue {
// make sure we're consistent with our types.
// nil = undefined in js.
// nsnull = null in js.
function(arg)
return JSValue.undefined
do {
try function(arg)
return JSValue.undefined
} catch {
return context.throwError(error)
}
}

internal func jsArgsToTypes(context: JSContext?, argc: Int32, argv: UnsafeMutablePointer<JSValue>?) -> [JSConvertible] {
Expand Down Expand Up @@ -610,11 +615,45 @@
}

internal func toJSValue(context: JSContext) -> JSValue? {
// it's not expected that JS errors are created in native
// and flow back into JS.
return nil
// Create the base error object
let errorValue = JS_NewError(context.ref)

// Set the message if we have it
if let message = self.message {
let messageAtom = JS_NewAtom(context.ref, "message")
let messageValue = JS_NewString(context.ref, message)
JS_SetProperty(context.ref, errorValue, messageAtom, messageValue)
JS_FreeAtom(context.ref, messageAtom)
}

// Set the name if we have it
if let name = self.name {
let nameAtom = JS_NewAtom(context.ref, "name")
let nameValue = JS_NewString(context.ref, name)
JS_SetProperty(context.ref, errorValue, nameAtom, nameValue)
JS_FreeAtom(context.ref, nameAtom)
}

// Set the cause if we have it
if let cause = self.cause {
let causeAtom = JS_NewAtom(context.ref, "cause")
let causeValue = JS_NewString(context.ref, cause)
JS_SetProperty(context.ref, errorValue, causeAtom, causeValue)
JS_FreeAtom(context.ref, causeAtom)
}

Check warning on line 643 in Sources/Substrata/Internals/Conversion.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Substrata/Internals/Conversion.swift#L639-L643

Added lines #L639 - L643 were not covered by tests

// Set the stack if we have it
if let stack = self.stack {
let stackAtom = JS_NewAtom(context.ref, "stack")
let stackValue = JS_NewString(context.ref, stack)
JS_SetProperty(context.ref, errorValue, stackAtom, stackValue)
JS_FreeAtom(context.ref, stackAtom)
}

return errorValue
}


public var string: String {
return """
Javascript Error:
Expand All @@ -624,6 +663,10 @@
"""
}

static func from(_ error: Error) -> JSError {
return JSError(message: error.localizedDescription)
}

internal init(value: JSValue, context: JSContext) {
name = Self.value(for: "name", object: value, context: context)
message = Self.value(for: "message", object: value, context: context)
Expand All @@ -632,6 +675,13 @@
stack = Self.value(for: "stack", object: value, context: context)
}

internal init(name: String? = "Native Code Exception", message: String?, cause: String? = nil, stack: String? = nil) {
self.name = name
self.message = message
self.cause = cause
self.stack = stack
}

internal let name: String?
internal let message: String?
internal let cause: String?
Expand Down
2 changes: 1 addition & 1 deletion Sources/Substrata/Internals/InternalTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal class JSClassInfo {

internal class JSClassInstanceInfo {
let classID: JSClassID
weak var instance: JSExport?
var instance: JSExport?
let type: JSExport.Type

init(type: JSExport.Type, classID: JSClassID, instance: JSExport?) {
Expand Down
10 changes: 5 additions & 5 deletions Sources/Substrata/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@
}
}

public typealias JSFunctionDefinition = ([JSConvertible?]) -> JSConvertible?
public typealias JSFunctionDefinition = ([JSConvertible?]) throws -> JSConvertible?

public protocol JSStatic {
static func staticInit()
}

public typealias JSPropertyGetterDefinition = () -> JSConvertible?
public typealias JSPropertySetterDefinition = (JSConvertible?) -> Void
public typealias JSPropertyGetterDefinition = () throws -> JSConvertible?
public typealias JSPropertySetterDefinition = (JSConvertible?) throws -> Void

public class JSProperty {
internal let getter: JSPropertyGetterDefinition
Expand Down Expand Up @@ -109,7 +109,7 @@
}
let wrappedFunction: JSFunctionDefinition = { [weak self] args in
guard self != nil else { return nil }
return function(args) // function captures 'self' strongly, but our wrapper holds it weakly
return try function(args) // function captures 'self' strongly, but our wrapper holds it weakly
}

_exportedMethods[named] = wrappedFunction
Expand All @@ -135,5 +135,5 @@

// Overrides
public required init() {}
open func construct(args: [JSConvertible?]) {}
open func construct(args: [JSConvertible?]) throws {}

Check warning on line 138 in Sources/Substrata/Types.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Substrata/Types.swift#L138

Added line #L138 was not covered by tests
}
49 changes: 49 additions & 0 deletions Tests/SubstrataTests/ConversionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,55 @@ final class ConversionTests: XCTestCase {
// Put teardown code here. This method is called after the invocation of each test method in the class.
EdgeFunctionJS.reset()
}

func testNativeThrowingConstructor() throws {
let engine = JSEngine()
var exceptionHit = false
engine.exceptionHandler = { error in
exceptionHit = true
print(error.jsDescription())
}

engine.export(type: ThrowingConstructorJS.self, className:"ThrowingConstructor")

// test constructor
let r = engine.evaluate(script: "let x = new ThrowingConstructor(true);")
XCTAssertNotNil(r)
XCTAssertTrue(exceptionHit)
}

func testNativeThrowingOtherStuff() throws {
let engine = JSEngine()
var exceptionHit = false
engine.exceptionHandler = { error in
exceptionHit = true
print(error.jsDescription())
}

engine.export(type: ThrowingConstructorJS.self, className: "ThrowingConstructor")

// test function
exceptionHit = false
var r = engine.evaluate(script: "let y = new ThrowingConstructor(false);")
XCTAssertNil(r)
let b = engine.evaluate(script: "y.noThrowFunc()")?.typed(as: Int.self)
XCTAssertEqual(b, 3)
r = engine.evaluate(script: "y.throwFunc()")
XCTAssertNotNil(r)
XCTAssertTrue(exceptionHit)

// test getter
exceptionHit = false
r = engine.evaluate(script: "y.throwProp")
XCTAssertNotNil(r)
XCTAssertTrue(exceptionHit)

// test setter
exceptionHit = false
r = engine.evaluate(script: "y.throwProp = 5")
XCTAssertNotNil(r)
XCTAssertTrue(exceptionHit)
}

func testMassConversionOut() throws {
let engine = JSEngine()
Expand Down
15 changes: 15 additions & 0 deletions Tests/SubstrataTests/SubstrataTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ class MyJSClass: JSExport, JSStatic {
super.init()

exportMethod(named: "test", function: test)
exportMethod(named: "test2", function: { args in
return 3
})

exportProperty(named: "myInstanceProperty", getter: {
return self.myInstanceProperty
Expand Down Expand Up @@ -207,6 +210,18 @@ final class SubstrataTests: XCTestCase {
XCTAssertEqual(result, 1337)
}

func testExportInstanceMethod() {
let engine = JSEngine()

engine.export(type: MyJSClass.self, className: "MyJSClass")
engine.evaluate(script: "let x = new MyJSClass()")
var result = engine.evaluate(script: "x.test()")?.typed(as: Int.self)
XCTAssertEqual(result, 42)

result = engine.evaluate(script: "x.test2()")?.typed(as: Int.self)
XCTAssertEqual(result, 3)
}

func testStaticProperties() {
let engine = JSEngine()
engine.export(type: MyJSClass.self, className: "MyJSClass")
Expand Down
39 changes: 39 additions & 0 deletions Tests/SubstrataTests/Support/Mocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,45 @@
import Foundation
@testable import Substrata

class ThrowingConstructorJS: JSExport, JSStatic {
static func staticInit() {

}

enum ErrorTest: String, Error {
case constructor
case function
case getter
case setter
}

required init() {
super.init()

exportMethod(named: "noThrowFunc") { args in
print("noThrowFunc")
return 3
}

exportMethod(named: "throwFunc") { args in
print("throwFunc")
throw ErrorTest.function
}

exportProperty(named: "throwProp") {
throw ErrorTest.getter
} setter: { value in
throw ErrorTest.setter
}
}

override func construct(args: [JSConvertible?]) throws {
if let shouldThrow = args.first as? Bool, shouldThrow {
throw ErrorTest.constructor
}
}
}

class EdgeFunctionJS: JSExport, JSStatic {
static var myStaticBool: Bool? = true
var myBool: Bool? = false
Expand Down
4 changes: 2 additions & 2 deletions Tests/SubstrataTests/TypeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ final class TypeTests: XCTestCase {
let r = engine.evaluate(script: "Error('test')")?.typed(as: JSError.self)!
XCTAssertNotNil(r)
let out = r!.toJSValue(context: engine.context)
// we won't make an error on the native side.
XCTAssertNil(out)
// we WILL make an error on the native side.
XCTAssertNotNil(out)
}

func testInt() throws {
Expand Down