Skip to content

Commit

Permalink
Merge pull request #259 from swiftwasm/katei/int-conversion
Browse files Browse the repository at this point in the history
Soft-fail integer conversion from JS values that are not representable
  • Loading branch information
kateinoigakukun authored Jul 31, 2024
2 parents abc05a9 + 181061b commit d7309e2
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 5 deletions.
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

0 comments on commit d7309e2

Please sign in to comment.