Skip to content

JavaScript persistence#101

Merged
tayloraswift merged 2 commits intomasterfrom
javascript-persistence
Jan 21, 2026
Merged

JavaScript persistence#101
tayloraswift merged 2 commits intomasterfrom
javascript-persistence

Conversation

@tayloraswift
Copy link
Member

No description provided.

@gemini-code-assist
Copy link

Summary of Changes

Hello @tayloraswift, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces the JavaScriptPersistence module, a new component designed to facilitate seamless data exchange and type conversion between Swift and JavaScript environments. By providing a Swift-native representation of JavaScript values and mechanisms for converting standard Swift types, this module aims to enable code that typically runs in a JavaScript context to also execute and be tested within a native Swift environment, enhancing flexibility and testability.

Highlights

  • New JavaScriptPersistence Module: Introduces a new Swift module designed for interoperability between Swift and JavaScript values, providing a similar API to JavaScriptKit.
  • JSValue Representation: Defines a JSValue enum to encapsulate various JavaScript types, including booleans, strings, numbers, objects, null, undefined, symbols, and big integers.
  • Conversion Protocols: Implements ConvertibleToJSValue and ConstructibleFromJSValue protocols, allowing Swift types to be converted to and from JSValue instances.
  • Core JavaScript Type Wrappers: Adds JSObject for representing JavaScript objects and arrays, JSString for strings, JSBigInt for large integers, and JSSymbol for unique identifiers.
  • Standard Library Extensions: Provides extensions for common Swift types (e.g., Array, Bool, Int, String, Optional, various numeric types) to seamlessly integrate with the JSValue conversion system.
  • JSON Integration: Includes functionality to convert JSON.Node instances into JSValue, bridging existing JSON parsing with JavaScript value representation.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@tayloraswift tayloraswift merged commit a751f8c into master Jan 21, 2026
8 checks passed
@tayloraswift tayloraswift deleted the javascript-persistence branch January 21, 2026 06:28
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new "JavaScriptPersistence" module, designed to mimic "JavaScriptKit"'s API for use in native Swift environments, which is a valuable addition for testing purposes. However, the current implementation introduces several Denial of Service (DoS) vulnerabilities. Specifically, the "JSObject" implementation is vulnerable to memory exhaustion when setting values at very large array indices or when manually setting a large array length. Additionally, the use of "fatalError" for handling invalid input data (such as invalid array lengths or malformed JSON numbers) can be exploited to crash the application. There are also critical issues in "JSObject" that could lead to application crashes and high-severity inconsistencies in how 64-bit integers are converted to "JSValue". Addressing these vulnerabilities and issues, including implementing bounds checks for array operations and handling data validation errors gracefully without crashing the process, is crucial for the stability and correctness of the module.

Comment on lines +54 to +57
if self.buffer.endIndex < index {
let count: Int = index.distance(to: self.buffer.endIndex)
self.buffer.append(contentsOf: repeatElement(.undefined, count: count))
self.buffer.append(value)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The "subscript(index: Int)" setter allows setting a value at an arbitrary index. If the index is significantly larger than the current buffer size, this can lead to massive memory allocations and an Out-of-Memory (OOM) condition, crashing the application, thus creating a Denial of Service vulnerability. Additionally, the current logic for setting an element at a given index has a critical bug where passing a negative "count" to "repeatElement" will cause a fatal error and crash the application.

Comment on lines +82 to +86
} else if length > self.buffer.count {
self.buffer.reserveCapacity(length)
self.buffer.append(
contentsOf: repeatElement(.undefined, count: length - self.buffer.count)
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The subscript(key: JSString) setter for the "length" property of an array allows an attacker to set an arbitrary length. If the new length is much larger than the current buffer size, the code attempts to expand the buffer and fill it with .undefined values. This can lead to an OOM condition and a Denial of Service.

Comment on lines +3 to +5
@inlinable public var jsValue: JSValue {
.bigInt(JSBigInt.init(int128: Int128.init(self)))
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This implementation unconditionally converts Int64 to JSBigInt. However, Int64 values up to 2^53 can be represented exactly as a Double (JSValue.number). For consistency with the implementation for Int and to better align with JavaScript's number handling, you should check if the value can be represented as a Double first, and only fall back to JSBigInt if it cannot.

    @inlinable public var jsValue: JSValue {
        if let double: Double = .init(exactly: self) {
            .number(double)
        } else {
            .bigInt(JSBigInt.init(int128: Int128.init(self)))
        }
    }

Comment on lines +3 to +5
@inlinable public var jsValue: JSValue {
.bigInt(JSBigInt.init(int128: Int128.init(self)))
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This implementation unconditionally converts UInt64 to JSBigInt. However, UInt64 values up to 2^53 can be represented exactly as a Double (JSValue.number). For consistency with the implementation for UInt and to better align with JavaScript's number handling, you should check if the value can be represented as a Double first, and only fall back to JSBigInt if it cannot.

    @inlinable public var jsValue: JSValue {
        if let double: Double = .init(exactly: self) {
            .number(double)
        } else {
            .bigInt(JSBigInt.init(int128: Int128.init(self)))
        }
    }

Comment on lines +76 to +79
guard
let length: Int = .construct(from: value) else {
fatalError("Invalid array length")
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

Using "fatalError" when setting the "length" property of a "JSObject" that is an array is dangerous. If the provided value cannot be converted to an "Int", it will crash the entire application. This allows an attacker to trigger a Denial of Service. JavaScript engines typically handle such invalid assignments more gracefully (e.g., throwing a "RangeError" or ignoring the assignment) rather than terminating the process. It is recommended to handle this error gracefully, for example, by ignoring the invalid assignment.

Suggested change
guard
let length: Int = .construct(from: value) else {
fatalError("Invalid array length")
}
guard
let length: Int = .construct(from: value) else {
return
}

Comment on lines +55 to +60
if let double: Double = .init(string()) {
return .number(double)
} else {
// this should have never passed parser validation in the first place
fatalError("Unable to reparse JSON number to Double?!")
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The JSValue.json(_:) method converts a JSON.Node to a JSValue. If the JSON.Node contains a number in the .fallback or .inline case that cannot be parsed as a Double, the code calls fatalError. While the parser might prevent such nodes from being created from a string, they can be constructed manually. A library should not crash on malformed input data. Handle the parsing failure gracefully by returning a default value or throwing an error instead of crashing the process.

}

var elements: [Element] = []
; elements.reserveCapacity(object.buffer.count)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This leading semicolon is unnecessary. While it doesn't affect the logic, it's unconventional and harms code readability. It should be removed to adhere to standard Swift styling.

Suggested change
; elements.reserveCapacity(object.buffer.count)
elements.reserveCapacity(object.buffer.count)

tayloraswift added a commit that referenced this pull request Mar 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant