Skip to content
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

Soft-fail integer conversion from JS values that are not representable #259

Merged
merged 2 commits into from
Jul 31, 2024
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
19 changes: 18 additions & 1 deletion IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,22 @@ try test("Value Construction") {
let prop_7 = getJSValue(this: globalObject1Ref, name: "prop_7")
try expectEqual(Double.construct(from: prop_7), 3.14)
try expectEqual(Float.construct(from: prop_7), 3.14)

for source: JSValue in [
.number(.infinity), .number(.nan),
.number(Double(UInt64.max).nextUp), .number(Double(Int64.min).nextDown)
] {
try expectNil(Int.construct(from: source))
try expectNil(Int8.construct(from: source))
try expectNil(Int16.construct(from: source))
try expectNil(Int32.construct(from: source))
try expectNil(Int64.construct(from: source))
try expectNil(UInt.construct(from: source))
try expectNil(UInt8.construct(from: source))
try expectNil(UInt16.construct(from: source))
try expectNil(UInt32.construct(from: source))
try expectNil(UInt64.construct(from: source))
}
}

try test("Array Iterator") {
Expand Down Expand Up @@ -244,10 +260,11 @@ try test("Closure Lifetime") {
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
// Check diagnostics of use-after-free
do {
let c1Line = #line + 1
let c1 = JSClosure { $0[0] }
c1.release()
let error = try expectThrow(try evalClosure.throws(c1, JSValue.number(42.0))) as! JSValue
try expect("Error message should contains definition location", error.description.hasSuffix("PrimaryTests/main.swift:247"))
try expect("Error message should contains definition location", error.description.hasSuffix("PrimaryTests/main.swift:\(c1Line)"))
}
#endif

Expand Down
60 changes: 56 additions & 4 deletions Sources/JavaScriptKit/ConstructibleFromJSValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,41 @@ extension Double: ConstructibleFromJSValue {}
extension Float: ConstructibleFromJSValue {}

extension SignedInteger where Self: ConstructibleFromJSValue {
/// Construct an instance of `SignedInteger` from the given `JSBigIntExtended`.
///
/// If the value is too large to fit in the `Self` type, `nil` is returned.
///
/// - Parameter bigInt: The `JSBigIntExtended` to decode
public init?(exactly bigInt: JSBigIntExtended) {
self.init(exactly: bigInt.int64Value)
}

/// Construct an instance of `SignedInteger` from the given `JSBigIntExtended`.
///
/// Crash if the value is too large to fit in the `Self` type.
///
/// - Parameter bigInt: The `JSBigIntExtended` to decode
public init(_ bigInt: JSBigIntExtended) {
self.init(bigInt.int64Value)
}

/// Construct an instance of `SignedInteger` from the given `JSValue`.
///
/// Returns `nil` if one of the following conditions is met:
/// - The value is not a number or a bigint.
/// - The value is a number that does not fit or cannot be represented
/// in the `Self` type (e.g. NaN, Infinity).
/// - The value is a bigint that does not fit in the `Self` type.
///
/// If the value is a number, it is rounded towards zero before conversion.
///
/// - Parameter value: The `JSValue` to decode
public static func construct(from value: JSValue) -> Self? {
if let number = value.number {
return Self(number)
return Self(exactly: number.rounded(.towardZero))
}
if let bigInt = value.bigInt as? JSBigIntExtended {
return Self(bigInt)
return Self(exactly: bigInt)
}
return nil
}
Expand All @@ -55,15 +81,41 @@ extension Int32: ConstructibleFromJSValue {}
extension Int64: ConstructibleFromJSValue {}

extension UnsignedInteger where Self: ConstructibleFromJSValue {

/// Construct an instance of `UnsignedInteger` from the given `JSBigIntExtended`.
///
/// Returns `nil` if the value is negative or too large to fit in the `Self` type.
///
/// - Parameter bigInt: The `JSBigIntExtended` to decode
public init?(exactly bigInt: JSBigIntExtended) {
self.init(exactly: bigInt.uInt64Value)
}

/// Construct an instance of `UnsignedInteger` from the given `JSBigIntExtended`.
///
/// Crash if the value is negative or too large to fit in the `Self` type.
///
/// - Parameter bigInt: The `JSBigIntExtended` to decode
public init(_ bigInt: JSBigIntExtended) {
self.init(bigInt.uInt64Value)
}

/// Construct an instance of `UnsignedInteger` from the given `JSValue`.
///
/// Returns `nil` if one of the following conditions is met:
/// - The value is not a number or a bigint.
/// - The value is a number that does not fit or cannot be represented
/// in the `Self` type (e.g. NaN, Infinity).
/// - The value is a bigint that does not fit in the `Self` type.
/// - The value is negative.
///
/// - Parameter value: The `JSValue` to decode
public static func construct(from value: JSValue) -> Self? {
if let number = value.number {
return Self(number)
return Self(exactly: number.rounded(.towardZero))
}
if let bigInt = value.bigInt as? JSBigIntExtended {
return Self(bigInt)
return Self(exactly: bigInt)
}
return nil
}
Expand Down
Loading