Skip to content

Commit 30bb265

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. Fallback no-op macros (via `<ptrcheck.h>` + `#ifndef` guards) keep the header portable across toolchains that predate bounds-safety support. 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 30bb265

2 files changed

Lines changed: 30 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: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@
99
#include <stdbool.h>
1010
#include <stdint.h>
1111

12+
// Bounds-safety annotation macros; no-ops on toolchains without <ptrcheck.h>.
13+
#if __has_include(<ptrcheck.h>)
14+
#include <ptrcheck.h>
15+
#endif
16+
#ifndef __sized_by_or_null
17+
#define __sized_by_or_null(N)
18+
#endif
19+
#ifndef __noescape
20+
#define __noescape
21+
#endif
22+
1223
/// `JavaScriptObjectRef` represents JavaScript object reference that is referenced by Swift side.
1324
/// This value is an address of `SwiftRuntimeHeap`.
1425
typedef unsigned int JavaScriptObjectRef;
@@ -291,12 +302,21 @@ IMPORT_JS_FUNCTION(swjs_create_oneshot_function, JavaScriptObjectRef, (const Jav
291302
/// This is used to provide an efficient way to create `TypedArray`.
292303
///
293304
/// @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`
305+
/// @param elements_ptr The elements to copy into the new typed array. May be NULL
306+
/// when `length` is 0 (Swift's `Array.baseAddress` is NULL for
307+
/// empty arrays). The pointee bytes are copied; the pointer
308+
/// does not escape the call.
309+
/// @param length The number of elements at `elements_ptr`.
310+
/// @param element_size The size in bytes of each element. Should match the byte
311+
/// width of `constructor`'s element type
312+
/// (`constructor.BYTES_PER_ELEMENT`). The JS side ignores this
313+
/// value; it exists so clang's bounds-safety analyzer can
314+
/// express the buffer size as `length * element_size`.
296315
/// @returns A reference to the constructed typed array
297316
IMPORT_JS_FUNCTION(swjs_create_typed_array, JavaScriptObjectRef, (const JavaScriptObjectRef constructor,
298-
const void * _Nullable elements_ptr,
299-
const int length))
317+
const void * _Nullable __sized_by_or_null(length * element_size) __noescape elements_ptr,
318+
const int length,
319+
const int element_size))
300320

301321
/// Copies the byte contents of a typed array into a Swift side memory buffer.
302322
///

0 commit comments

Comments
 (0)