# unsafe-pointer

Unsafely turn `ArrayBuffer` values into raw pointers and raw pointers into
`ArrayBuffer` aliases.

Warning: invalid pointer use can crash the process, corrupt memory, or let
attackers control the machine. Using this package voids your warranty.

May be useful for interacting with native libraries or doing light witchcraft,
especially via FFI like [koffi](https://koffi.dev).

## API

```ts
import {
  unsafePointerOf,
  unsafeBigIntPointerOf,
  unsafeArrayBufferAt,
  unsafeCountNonNullBytes,
} from "unsafe-pointer"

const view = new Uint8Array([1, 2, 3, 4])

const ptr = unsafePointerOf(view)
const bigPtr = unsafeBigIntPointerOf(view)

const alias = new Uint8Array(unsafeArrayBufferAt(ptr, 1, 2))

console.log([...alias]) // [ 2, 3 ]
alias[0] = 99
console.log([...view])  // [ 1, 99, 3, 4 ]

function unsafeStringAt(ptr: number) {
  const length = unsafeCountNonNullBytes(ptr, -1)
  return new TextDecoder().decode(new Uint8Array(unsafeArrayBufferAt(ptr, 0, length)))
}

const cstring = new TextEncoder().encode("Hello, world!\0")
const pointer = unsafePointerOf(cstring)
console.log(unsafeStringAt(pointer)) // "Hello, world!"
```

- `unsafePointerOf<T extends number>(buf: ArrayBuffer | ArrayBufferView): T`
  Returns a pointer to the first byte of `buf` as a number.
- `unsafeBigIntPointerOf<T extends bigint>(buf: ArrayBuffer | ArrayBufferView): T`
  Returns a pointer to the first byte of `buf` as a bigint.
- `unsafeArrayBufferAt<T extends number | bigint>(ptr: T, offset: number | undefined, byteLength: number): ArrayBuffer`
  Creates an `ArrayBuffer` alias for memory at `ptr + offset` with the given `byteLength`.
- `unsafeCountNonNullBytes<T extends number | bigint>(ptr: T, maxBytes: number): number`
  Iterates from `ptr` until the first null byte is found, or until `maxBytes`
  bytes are reached. Returns the number of bytes iterated, or `-1` if no null
  byte is found before `maxBytes`. Pass `-1` for `maxBytes` to count without a
  bound.

All APIs are unsafe. The runtime may move or reclaim memory. Invalid pointer
access can lead to crashes, memory corruption, or attacker-controlled execution.

### TypeScript

You may use `number` or `bigint` branded sub-types of your choosing to represent
pointers. This can be useful for type safety (not! runtime! safety!).

Examples:

```ts
/**
 * Emulate bun:ffi interface.
 * 
 * https://bun.com/docs/runtime/ffi
 */
import { unsafePointerOf, unsafeArrayBufferAt } from "unsafe-pointer"
export type Pointer = number & { __pointer__: null }
export const ptr = unsafePointerOf<Pointer>
export const toArrayBuffer = unsafeArrayBufferAt<Pointer>
```

```ts
/**
 * Do fancy generics.
 */
import * as unsafe from "unsafe-pointer"
const PointerBrand = Symbol("Pointer")
type Pointer<T = unknown> = number & { [PointerBrand]: true, __type__: T }

export function unsafePointerOf<T extends ArrayBufferView>(view: T): Pointer<T> {
  return unsafe.unsafePointerOf(view)
}

type TypedArrayConstructor<T extends ArrayBufferView> = {
  new (buffer: ArrayBuffer, byteOffset: number, length: number): T
  BYTES_PER_ELEMENT: number
}

export function unsafeTypedArrayAt<T extends ArrayBufferView>(TypedArray: TypedArrayConstructor<T>, ptr: Pointer<T>, length: number): T {
  const arrayBuffer = unsafe.unsafeArrayBufferAt(ptr, 0, length * TypedArray.BYTES_PER_ELEMENT)
  return new TypedArray(arrayBuffer, 0, length)
}
```

## Development

```sh
# Install tools.
curl https://mise.run | sh
mise install

# Develop.
bun install
bun run typecheck
bun run rebuild
bun test
```

`bun run rebuild` rebuilds the local addon with `node-gyp`.

## Prebuilds

```sh
bun run build
```

`bun run build` builds the full prebuild matrix with `zig-build` and stages
artifacts under `build/zig-build/` before copying them into `prebuilds/`.

To build a subset of targets:

```sh
./scripts/zig-build-prebuilds.mts darwin-arm64 linux-x64-glibc win32-x64
```

Supported targets:

- `darwin-x64`
- `darwin-arm64`
- `linux-x64-glibc`
- `linux-arm64-glibc`
- `linux-x64-musl`
- `linux-arm64-musl`
- `win32-x64`
- `win32-arm64`

At runtime, `node-gyp-build` loads the matching prebuild from `prebuilds/` when
available. If no matching prebuild is present, source builds via `node-gyp`
should work.
