Skip to content

Commit 34ab30a

Browse files
committed
JSTypedArray: annotate swjs_create_typed_array with bounds-safety attributes
On recent clang/Swift toolchains, bounds-safety annotations on `void *` parameters cause the Swift importer to synthesize an `UnsafeRawBufferPointer`-taking overload alongside the original. Adding these annotations to `swjs_create_typed_array` gives us that overload for free, and it lays the foundation for broader `Span` adoption in JavaScriptKit in follow-up PRs. Because `elements_ptr` is `void *`, the byte count is `length * sizeof(element)`. This PR adds a trailing `element_size` parameter and annotates `elements_ptr` as `__sized_by_or_null(length * element_size) __noescape`. Both annotations are correct given the JS implementation: it constructs a typed array view over WASM linear memory at `elementsPtr` with `length` elements and immediately calls `.slice()` to copy before returning. The view constructor reads exactly `length * constructor.BYTES_PER_ELEMENT` bytes, which equals `length * element_size` since Swift passes `MemoryLayout<Element>.stride` — matching `BYTES_PER_ELEMENT` for every `TypedArrayElement` type. And because only the `.slice()` copy is retained and returned, `elementsPtr` never escapes the call. `__sized_by_or_null` rather than `__sized_by` preserves the existing nullable contract: Swift's `Array.baseAddress` is `nil` for empty arrays, and the JS side already handles `length == 0` without dereferencing the pointer. `__sized_by_or_null` is reached transitively through `<sys/cdefs.h>`; `__noescape` requires an explicit `#include <lifetimebound.h>`, which this PR adds. The only coordinated change is in `JSTypedArray.swift`'s `createTypedArray`, which passes `Int32(MemoryLayout<Element>.stride)` as the new argument; the JS dispatch entry is unchanged — extra WASM arguments are silently dropped.
1 parent 403ae95 commit 34ab30a

2 files changed

Lines changed: 20 additions & 5 deletions

File tree

Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,12 @@ public final class JSTypedArray<Traits>: JSBridgedClass, ExpressibleByArrayLiter
6868
private static func createTypedArray(from buffer: UnsafeBufferPointer<Element>) -> JSObject {
6969
// Retain the constructor function to avoid it being released before calling `swjs_create_typed_array`
7070
let jsArrayRef = withExtendedLifetime(Self.constructor!) { ctor in
71-
swjs_create_typed_array(ctor.id, buffer.baseAddress, Int32(buffer.count))
71+
swjs_create_typed_array(
72+
ctor.id,
73+
buffer.baseAddress,
74+
Int32(buffer.count),
75+
Int32(MemoryLayout<Element>.stride)
76+
)
7277
}
7378
return JSObject(id: jsArrayRef)
7479
}

Sources/_CJavaScriptKit/include/_CJavaScriptKit.h

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#endif
99
#include <stdbool.h>
1010
#include <stdint.h>
11+
#include <lifetimebound.h>
1112

1213
/// `JavaScriptObjectRef` represents JavaScript object reference that is referenced by Swift side.
1314
/// This value is an address of `SwiftRuntimeHeap`.
@@ -291,12 +292,21 @@ IMPORT_JS_FUNCTION(swjs_create_oneshot_function, JavaScriptObjectRef, (const Jav
291292
/// This is used to provide an efficient way to create `TypedArray`.
292293
///
293294
/// @param constructor The `TypedArray` constructor.
294-
/// @param elements_ptr The elements pointer to initialize. They are assumed to be the same size of `constructor` elements size.
295-
/// @param length The length of `elements_ptr`
295+
/// @param elements_ptr The elements to copy into the new typed array. May be NULL
296+
/// when `length` is 0 (Swift's `Array.baseAddress` is NULL for
297+
/// empty arrays). The pointee bytes are copied; the pointer
298+
/// does not escape the call.
299+
/// @param length The number of elements at `elements_ptr`.
300+
/// @param element_size The size in bytes of each element. Should match the byte
301+
/// width of `constructor`'s element type
302+
/// (`constructor.BYTES_PER_ELEMENT`). The JS side ignores this
303+
/// value; it exists so clang's bounds-safety analyzer can
304+
/// express the buffer size as `length * element_size`.
296305
/// @returns A reference to the constructed typed array
297306
IMPORT_JS_FUNCTION(swjs_create_typed_array, JavaScriptObjectRef, (const JavaScriptObjectRef constructor,
298-
const void * _Nullable elements_ptr,
299-
const int length))
307+
const void * _Nullable __sized_by_or_null(length * element_size) __noescape elements_ptr,
308+
const int length,
309+
const int element_size))
300310

301311
/// Copies the byte contents of a typed array into a Swift side memory buffer.
302312
///

0 commit comments

Comments
 (0)