diff options
25 files changed, 796 insertions, 386 deletions
diff --git a/.github/workflows/bun-test.yml b/.github/workflows/bun-test.yml new file mode 100644 index 000000000..02a9a2383 --- /dev/null +++ b/.github/workflows/bun-test.yml @@ -0,0 +1,43 @@ +name: bun-test +on: + push: + branches: + - main + - "test/*" + paths: + - "src/**/*" + - "test/**/*" + pull_request: + branches: + - main + - "test/*" + paths: + - "src/**/*" + - "test/**/*" + workflow_dispatch: + inputs: + release: + type: string + default: canary +jobs: + bun: + name: Bun + runs-on: ubuntu-latest + defaults: + run: + working-directory: packages/bun-test + steps: + - id: checkout + name: Checkout + uses: actions/checkout@v3 + - id: setup-bun + name: Setup Bun + uses: oven-sh/setup-bun@v0.1.8 + with: + bun-version: ${{ github.event.inputs.release || 'canary' }} + - id: setup-dependencies + name: Setup Dependencies + run: bun install + - id: test + name: Test + run: bun run test diff --git a/Dockerfile b/Dockerfile index 479fb44be..9e2c66e9d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -122,8 +122,8 @@ ARG BUN_RELEASE_DIR ARG BUN_DEPS_OUT_DIR ARG BUN_DIR ARG CPU_TARGET - ENV CPU_TARGET=${CPU_TARGET} + ENV CCACHE_DIR=/ccache ENV JSC_BASE_DIR=${WEBKIT_DIR} ENV LIB_ICU_PATH=${WEBKIT_DIR}/lib @@ -149,6 +149,9 @@ ARG BUN_RELEASE_DIR ARG BUN_DEPS_OUT_DIR ARG BUN_DIR +ARG CPU_TARGET +ENV CPU_TARGET=${CPU_TARGET} + COPY Makefile ${BUN_DIR}/Makefile COPY src/deps/lol-html ${BUN_DIR}/src/deps/lol-html @@ -303,8 +306,6 @@ ARG BUN_RELEASE_DIR ARG BUN_DEPS_OUT_DIR ARG BUN_DIR ARG CPU_TARGET - - ENV CPU_TARGET=${CPU_TARGET} COPY Makefile ${BUN_DIR}/Makefile @@ -484,6 +485,9 @@ ARG BUN_RELEASE_DIR ARG BUN_DEPS_OUT_DIR ARG BUN_DIR +ARG CPU_TARGET +ENV CPU_TARGET=${CPU_TARGET} + ENV CCACHE_DIR=/ccache COPY Makefile ${BUN_DIR}/Makefile diff --git a/packages/bun-release/bun.lockb b/packages/bun-release/bun.lockb Binary files differindex 769154b6c..9ae1c3b95 100755 --- a/packages/bun-release/bun.lockb +++ b/packages/bun-release/bun.lockb diff --git a/packages/bun-test/bun.lockb b/packages/bun-test/bun.lockb Binary files differindex ac8b84c5f..b5a1b7c3e 100755 --- a/packages/bun-test/bun.lockb +++ b/packages/bun-test/bun.lockb diff --git a/packages/bun-test/package.json b/packages/bun-test/package.json index 531b93ec8..6504f57b4 100644 --- a/packages/bun-test/package.json +++ b/packages/bun-test/package.json @@ -1,6 +1,8 @@ { "private": true, - "dependencies": {}, + "dependencies": { + "@actions/core": "^1.10.0" + }, "devDependencies": { "bun-types": "canary", "prettier": "^2.8.2" diff --git a/packages/bun-test/src/runner.ts b/packages/bun-test/src/runner.ts index 37ae1ed05..256d0c87f 100644 --- a/packages/bun-test/src/runner.ts +++ b/packages/bun-test/src/runner.ts @@ -1,6 +1,7 @@ import { spawn } from "bun"; import { readdirSync } from "node:fs"; import { resolve } from "node:path"; +import * as action from "@actions/core"; const cwd = resolve("../.."); const isAction = !!process.env["GITHUB_ACTION"]; @@ -38,7 +39,7 @@ async function runTest(path: string): Promise<void> { const prefix = exitCode === 0 ? "PASS" : `FAIL (exit code ${exitCode})`; - console.log(`::group::${prefix} - ${name}`); + action.startGroup(`${prefix} - ${name}`); } for (const stdout of [runner.stdout, runner.stderr]) { if (!stdout) { @@ -48,7 +49,10 @@ async function runTest(path: string): Promise<void> { while (true) { const { value, done } = await reader.read(); if (value) { - write(value); + console.write(value); + if (isAction) { + findErrors(value); + } } if (done) { break; @@ -56,19 +60,21 @@ async function runTest(path: string): Promise<void> { } } if (isAction) { - console.log("::endgroup::"); + action.endGroup(); } } -function write(data: Uint8Array): void { - console.write(data); - if (!isAction) { - return; - } +let failed = false; + +function findErrors(data: Uint8Array): void { const text = new TextDecoder().decode(data); for (const [message, _, path, line, col] of text.matchAll(errorPattern)) { - const name = path.replace(cwd, "").slice(1); - console.log(`::error file=${name},line=${line},col=${col},title=${message}::`); + failed = true; + action.error(message, { + file: path.replace(cwd, "").slice(1), + startLine: parseInt(line), + startColumn: parseInt(col), + }); } } @@ -77,3 +83,4 @@ for (const path of findTests(resolve(cwd, "test/bun.js"))) { tests.push(runTest(path).catch(console.error)); } await Promise.allSettled(tests); +process.exit(failed ? 1 : 0); diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 0b0ceb7eb..ba626c00d 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -767,7 +767,7 @@ declare module "bun" { * const query = UserQuery; * ``` */ - macros?: MacroMap; + macro?: MacroMap; autoImportJSX?: boolean; allowBunRuntime?: boolean; @@ -1785,7 +1785,7 @@ declare module "bun" { * */ // tslint:disable-next-line:unified-signatures - export function file(path: string, options?: BlobPropertyBag): FileBlob; + export function file(path: string | URL, options?: BlobPropertyBag): FileBlob; /** * `Blob` that leverages the fastest system calls available to operate on files. @@ -2152,6 +2152,40 @@ declare module "bun" { } /** + * Resolve a `Promise` after milliseconds. This is like + * {@link setTimeout} except it returns a `Promise`. + * + * @param ms milliseconds to delay resolving the promise. This is a minimum + * number. It may take longer. If a {@link Date} is passed, it will sleep until the + * {@link Date} is reached. + * + * @example + * ## Sleep for 1 second + * ```ts + * import { sleep } from "bun"; + * + * await sleep(1000); + * ``` + * ## Sleep for 10 milliseconds + * ```ts + * await Bun.sleep(10); + * ``` + * ## Sleep until `Date` + * + * ```ts + * const target = new Date(); + * target.setSeconds(target.getSeconds() + 1); + * await Bun.sleep(target); + * ``` + * Internally, `Bun.sleep` is the equivalent of + * ```ts + * await new Promise((resolve) => setTimeout(resolve, ms)); + * ``` + * As always, you can use `Bun.sleep` or the imported `sleep` function interchangeably. + */ + export function sleep(ms: number | Date): Promise<void>; + + /** * Sleep the thread for a given number of milliseconds * * This is a blocking function. diff --git a/packages/bun-types/globals.d.ts b/packages/bun-types/globals.d.ts index 1c91d6b2f..cef3f57ec 100644 --- a/packages/bun-types/globals.d.ts +++ b/packages/bun-types/globals.d.ts @@ -14,17 +14,7 @@ type Platform = | "win32" | "cygwin" | "netbsd"; -type Architecture = - | "arm" - | "arm64" - | "ia32" - | "mips" - | "mipsel" - | "ppc" - | "ppc64" - | "s390" - | "s390x" - | "x64"; +type Architecture = "arm" | "arm64" | "ia32" | "mips" | "mipsel" | "ppc" | "ppc64" | "s390" | "s390x" | "x64"; type Signals = | "SIGABRT" | "SIGALRM" @@ -69,7 +59,7 @@ interface ArrayConstructor { asyncItems: AsyncIterable<T> | Iterable<T> | ArrayLike<T>, mapfn?: (value: any, index: number) => any, thisArg?: any, - ): Array<T>; + ): Promise<Array<T>>; } interface Console { @@ -446,10 +436,7 @@ interface Headers { entries(): IterableIterator<[string, string]>; keys(): IterableIterator<string>; values(): IterableIterator<string>; - forEach( - callbackfn: (value: string, key: string, parent: Headers) => void, - thisArg?: any, - ): void; + forEach(callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: any): void; /** * Convert {@link Headers} to a plain JavaScript object. @@ -493,13 +480,7 @@ declare var Headers: { }; type HeadersInit = Array<[string, string]> | Record<string, string> | Headers; -type ResponseType = - | "basic" - | "cors" - | "default" - | "error" - | "opaque" - | "opaqueredirect"; +type ResponseType = "basic" | "cors" | "default" | "error" | "opaque" | "opaqueredirect"; declare class Blob implements BlobInterface { /** @@ -575,10 +556,7 @@ interface ResponseInit { * ``` */ declare class Response implements BlobInterface { - constructor( - body?: ReadableStream | BlobPart | BlobPart[] | null, - options?: ResponseInit, - ); + constructor(body?: ReadableStream | BlobPart | BlobPart[] | null, options?: ResponseInit); /** * Create a new {@link Response} with a JSON body @@ -711,13 +689,7 @@ declare class Response implements BlobInterface { clone(): Response; } -type RequestCache = - | "default" - | "force-cache" - | "no-cache" - | "no-store" - | "only-if-cached" - | "reload"; +type RequestCache = "default" | "force-cache" | "no-cache" | "no-store" | "only-if-cached" | "reload"; type RequestCredentials = "include" | "omit" | "same-origin"; type RequestDestination = | "" @@ -757,9 +729,7 @@ type RequestInfo = Request | string; type BodyInit = ReadableStream | XMLHttpRequestBodyInit; type XMLHttpRequestBodyInit = Blob | BufferSource | string; type ReadableStreamController<T> = ReadableStreamDefaultController<T>; -type ReadableStreamDefaultReadResult<T> = - | ReadableStreamDefaultReadValueResult<T> - | ReadableStreamDefaultReadDoneResult; +type ReadableStreamDefaultReadResult<T> = ReadableStreamDefaultReadValueResult<T> | ReadableStreamDefaultReadDoneResult; type ReadableStreamReader<T> = ReadableStreamDefaultReader<T>; interface RequestInit { @@ -1091,10 +1061,7 @@ declare class TextDecoder { */ readonly ignoreBOM: boolean; - constructor( - encoding?: Encoding, - options?: { fatal?: boolean; ignoreBOM?: boolean }, - ); + constructor(encoding?: Encoding, options?: { fatal?: boolean; ignoreBOM?: boolean }); /** * Decodes the `input` and returns a string. If `options.stream` is `true`, any @@ -1252,10 +1219,9 @@ declare function clearTimeout(id?: number): void; * * */ -declare function fetch( - url: string | URL, - init?: FetchRequestInit, -): Promise<Response>; + +declare function fetch(url: string | URL, init?: FetchRequestInit): Promise<Response>; + /** * Send a HTTP(s) request @@ -1280,30 +1246,19 @@ declare function reportError(error: any): void; * Run a function immediately after main event loop is vacant * @param handler function to call */ -declare function setImmediate( - handler: TimerHandler, - ...arguments: any[] -): number; +declare function setImmediate(handler: TimerHandler, ...arguments: any[]): number; /** * Run a function every `interval` milliseconds * @param handler function to call * @param interval milliseconds to wait between calls */ -declare function setInterval( - handler: TimerHandler, - interval?: number, - ...arguments: any[] -): number; +declare function setInterval(handler: TimerHandler, interval?: number, ...arguments: any[]): number; /** * Run a function after `timeout` (milliseconds) * @param handler function to call * @param timeout milliseconds to wait between calls */ -declare function setTimeout( - handler: TimerHandler, - timeout?: number, - ...arguments: any[] -): number; +declare function setTimeout(handler: TimerHandler, timeout?: number, ...arguments: any[]): number; declare function addEventListener<K extends keyof EventMap>( type: K, listener: (this: object, ev: EventMap[K]) => any, @@ -1751,19 +1706,14 @@ interface URLSearchParams { keys(): IterableIterator<string>; /** Returns an iterator allowing to go through all values of the key/value pairs of this search parameter. */ values(): IterableIterator<string>; - forEach( - callbackfn: (value: string, key: string, parent: URLSearchParams) => void, - thisArg?: any, - ): void; + forEach(callbackfn: (value: string, key: string, parent: URLSearchParams) => void, thisArg?: any): void; /** Returns a string containing a query string suitable for use in a URL. Does not include the question mark. */ toString(): string; } declare var URLSearchParams: { prototype: URLSearchParams; - new ( - init?: string[][] | Record<string, string> | string | URLSearchParams, - ): URLSearchParams; + new (init?: string[][] | Record<string, string> | string | URLSearchParams): URLSearchParams; toString(): string; }; @@ -1964,19 +1914,10 @@ interface ReadableStream<R = any> { readonly locked: boolean; cancel(reason?: any): Promise<void>; getReader(): ReadableStreamDefaultReader<R>; - pipeThrough<T>( - transform: ReadableWritablePair<T, R>, - options?: StreamPipeOptions, - ): ReadableStream<T>; - pipeTo( - destination: WritableStream<R>, - options?: StreamPipeOptions, - ): Promise<void>; + pipeThrough<T>(transform: ReadableWritablePair<T, R>, options?: StreamPipeOptions): ReadableStream<T>; + pipeTo(destination: WritableStream<R>, options?: StreamPipeOptions): Promise<void>; tee(): [ReadableStream<R>, ReadableStream<R>]; - forEach( - callbackfn: (value: any, key: number, parent: ReadableStream<R>) => void, - thisArg?: any, - ): void; + forEach(callbackfn: (value: any, key: number, parent: ReadableStream<R>) => void, thisArg?: any): void; [Symbol.asyncIterator](): AsyncIterableIterator<R>; values(options?: { preventCancel: boolean }): AsyncIterableIterator<R>; } @@ -2034,8 +1975,7 @@ declare var ReadableStreamDefaultController: { new (): ReadableStreamDefaultController; }; -interface ReadableStreamDefaultReader<R = any> - extends ReadableStreamGenericReader { +interface ReadableStreamDefaultReader<R = any> extends ReadableStreamGenericReader { read(): Promise<ReadableStreamDefaultReadResult<R>>; releaseLock(): void; } @@ -2080,10 +2020,7 @@ interface WritableStream<W = any> { declare var WritableStream: { prototype: WritableStream; - new <W = any>( - underlyingSink?: UnderlyingSink<W>, - strategy?: QueuingStrategy<W>, - ): WritableStream<W>; + new <W = any>(underlyingSink?: UnderlyingSink<W>, strategy?: QueuingStrategy<W>): WritableStream<W>; }; /** This Streams API interface represents a controller allowing control of a WritableStream's state. When constructing a WritableStream, the underlying sink is given a corresponding WritableStreamDefaultController instance to manipulate. */ @@ -2123,10 +2060,7 @@ interface TransformerStartCallback<O> { } interface TransformerTransformCallback<I, O> { - ( - chunk: I, - controller: TransformStreamDefaultController<O>, - ): void | PromiseLike<void>; + (chunk: I, controller: TransformStreamDefaultController<O>): void | PromiseLike<void>; } interface UnderlyingSinkAbortCallback { @@ -2142,10 +2076,7 @@ interface UnderlyingSinkStartCallback { } interface UnderlyingSinkWriteCallback<W> { - ( - chunk: W, - controller: WritableStreamDefaultController, - ): void | PromiseLike<void>; + (chunk: W, controller: WritableStreamDefaultController): void | PromiseLike<void>; } interface UnderlyingSourceCancelCallback { @@ -2170,9 +2101,7 @@ interface UnderlyingSource<R = any> { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface DirectUnderlyingSource<R = any> { cancel?: UnderlyingSourceCancelCallback; - pull: ( - controller: ReadableStreamDirectController, - ) => void | PromiseLike<void>; + pull: (controller: ReadableStreamDirectController) => void | PromiseLike<void>; type: "direct"; } @@ -2289,15 +2218,7 @@ declare function prompt(message?: string, _default?: string): string | null; type KeyFormat = "jwk" | "pkcs8" | "raw" | "spki"; type KeyType = "private" | "public" | "secret"; -type KeyUsage = - | "decrypt" - | "deriveBits" - | "deriveKey" - | "encrypt" - | "sign" - | "unwrapKey" - | "verify" - | "wrapKey"; +type KeyUsage = "decrypt" | "deriveBits" | "deriveKey" | "encrypt" | "sign" | "unwrapKey" | "verify" | "wrapKey"; type HashAlgorithmIdentifier = AlgorithmIdentifier; type NamedCurve = string; @@ -2450,59 +2371,30 @@ type AlgorithmIdentifier = Algorithm | string; */ interface SubtleCrypto { decrypt( - algorithm: - | AlgorithmIdentifier - | RsaOaepParams - | AesCtrParams - | AesCbcParams - | AesGcmParams, + algorithm: AlgorithmIdentifier | RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams, key: CryptoKey, data: BufferSource, ): Promise<ArrayBuffer>; deriveBits( - algorithm: - | AlgorithmIdentifier - | EcdhKeyDeriveParams - | HkdfParams - | Pbkdf2Params, + algorithm: AlgorithmIdentifier | EcdhKeyDeriveParams | HkdfParams | Pbkdf2Params, baseKey: CryptoKey, length: number, ): Promise<ArrayBuffer>; deriveKey( - algorithm: - | AlgorithmIdentifier - | EcdhKeyDeriveParams - | HkdfParams - | Pbkdf2Params, + algorithm: AlgorithmIdentifier | EcdhKeyDeriveParams | HkdfParams | Pbkdf2Params, baseKey: CryptoKey, - derivedKeyType: - | AlgorithmIdentifier - | AesDerivedKeyParams - | HmacImportParams - | HkdfParams - | Pbkdf2Params, + derivedKeyType: AlgorithmIdentifier | AesDerivedKeyParams | HmacImportParams | HkdfParams | Pbkdf2Params, extractable: boolean, keyUsages: KeyUsage[], ): Promise<CryptoKey>; - digest( - algorithm: AlgorithmIdentifier, - data: BufferSource, - ): Promise<ArrayBuffer>; + digest(algorithm: AlgorithmIdentifier, data: BufferSource): Promise<ArrayBuffer>; encrypt( - algorithm: - | AlgorithmIdentifier - | RsaOaepParams - | AesCtrParams - | AesCbcParams - | AesGcmParams, + algorithm: AlgorithmIdentifier | RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams, key: CryptoKey, data: BufferSource, ): Promise<ArrayBuffer>; exportKey(format: "jwk", key: CryptoKey): Promise<JsonWebKey>; - exportKey( - format: Exclude<KeyFormat, "jwk">, - key: CryptoKey, - ): Promise<ArrayBuffer>; + exportKey(format: Exclude<KeyFormat, "jwk">, key: CryptoKey): Promise<ArrayBuffer>; generateKey( algorithm: RsaHashedKeyGenParams | EcKeyGenParams, extractable: boolean, @@ -2521,24 +2413,14 @@ interface SubtleCrypto { importKey( format: "jwk", keyData: JsonWebKey, - algorithm: - | AlgorithmIdentifier - | RsaHashedImportParams - | EcKeyImportParams - | HmacImportParams - | AesKeyAlgorithm, + algorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm, extractable: boolean, keyUsages: ReadonlyArray<KeyUsage>, ): Promise<CryptoKey>; importKey( format: Exclude<KeyFormat, "jwk">, keyData: BufferSource, - algorithm: - | AlgorithmIdentifier - | RsaHashedImportParams - | EcKeyImportParams - | HmacImportParams - | AesKeyAlgorithm, + algorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm, extractable: boolean, keyUsages: KeyUsage[], ): Promise<CryptoKey>; @@ -2551,12 +2433,7 @@ interface SubtleCrypto { format: KeyFormat, wrappedKey: BufferSource, unwrappingKey: CryptoKey, - unwrapAlgorithm: - | AlgorithmIdentifier - | RsaOaepParams - | AesCtrParams - | AesCbcParams - | AesGcmParams, + unwrapAlgorithm: AlgorithmIdentifier | RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams, unwrappedKeyAlgorithm: | AlgorithmIdentifier | RsaHashedImportParams @@ -2576,12 +2453,7 @@ interface SubtleCrypto { format: KeyFormat, key: CryptoKey, wrappingKey: CryptoKey, - wrapAlgorithm: - | AlgorithmIdentifier - | RsaOaepParams - | AesCtrParams - | AesCbcParams - | AesGcmParams, + wrapAlgorithm: AlgorithmIdentifier | RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams, ): Promise<ArrayBuffer>; } @@ -2665,9 +2537,7 @@ interface ErrorConstructor { * * @see https://v8.dev/docs/stack-trace-api#customizing-stack-traces */ - prepareStackTrace?: - | ((err: Error, stackTraces: CallSite[]) => any) - | undefined; + prepareStackTrace?: ((err: Error, stackTraces: CallSite[]) => any) | undefined; stackTraceLimit: number; } diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index 0b47bea27..da42e5feb 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -2873,19 +2873,25 @@ pub const Timer = struct { const callback = this.callback.get() orelse @panic("Expected CallbackJob to have a callback function"); if (this.arguments.trySwap()) |arguments| { - const count = arguments.getLengthOfArray(globalThis); - if (count > 0) { - if (count > args_buf.len) { - args = bun.default_allocator.alloc(JSC.JSValue, count) catch unreachable; - args_needs_deinit = true; - } else { - args = args_buf[0..count]; - } - var arg = args.ptr; - var i: u32 = 0; - while (i < count) : (i += 1) { - arg[0] = JSC.JSObject.getIndex(arguments, globalThis, @truncate(u32, i)); - arg += 1; + // Bun.sleep passes a Promise + if (arguments.jsType() == .JSPromise) { + args_buf[0] = arguments; + args = args_buf[0..1]; + } else { + const count = arguments.getLengthOfArray(globalThis); + if (count > 0) { + if (count > args_buf.len) { + args = bun.default_allocator.alloc(JSC.JSValue, count) catch unreachable; + args_needs_deinit = true; + } else { + args = args_buf[0..count]; + } + var arg = args.ptr; + var i: u32 = 0; + while (i < count) : (i += 1) { + arg[0] = JSC.JSObject.getIndex(arguments, globalThis, @truncate(u32, i)); + arg += 1; + } } } } @@ -2916,7 +2922,7 @@ pub const Timer = struct { this.deinit(); // get the value out of the promise - _ = promise.result(this.globalThis.vm()); + _ = promise.result(globalThis.vm()); }, .Pending => { result.then(globalThis, this, CallbackJob__onResolve, CallbackJob__onReject); diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index 10002b664..2ce07617a 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -580,14 +580,14 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_compareBody(JSC::J } auto castedThisValue = callFrame->uncheckedArgument(0); - JSC::JSUint8Array* castedThis = JSC::jsDynamicCast<JSC::JSUint8Array*>(castedThisValue); + JSC::JSArrayBufferView* castedThis = JSC::jsDynamicCast<JSC::JSArrayBufferView*>(castedThisValue); if (UNLIKELY(!castedThis)) { throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Buffer (first argument)"_s); return JSValue::encode(jsUndefined()); } auto buffer = callFrame->uncheckedArgument(1); - JSC::JSUint8Array* view = JSC::jsDynamicCast<JSC::JSUint8Array*>(buffer); + JSC::JSArrayBufferView* view = JSC::jsDynamicCast<JSC::JSArrayBufferView*>(buffer); if (UNLIKELY(!view)) { throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Buffer (2nd argument)"_s); return JSValue::encode(jsUndefined()); @@ -657,8 +657,8 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_compareBody(JSC::J auto targetLength = targetEnd - targetStart; auto actualLength = std::min(sourceLength, targetLength); - auto sourceStartPtr = castedThis->typedVector() + sourceStart; - auto targetStartPtr = view->typedVector() + targetStart; + auto sourceStartPtr = reinterpret_cast<unsigned char*>(castedThis->vector()) + sourceStart; + auto targetStartPtr = reinterpret_cast<unsigned char*>(view->vector()) + targetStart; auto result = actualLength > 0 ? memcmp(sourceStartPtr, targetStartPtr, actualLength) : 0; @@ -729,7 +729,10 @@ static inline JSC::EncodedJSValue jsBufferConstructorFunction_concatBody(JSC::JS static inline JSC::EncodedJSValue jsBufferConstructorFunction_isEncodingBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) { auto& vm = JSC::getVM(lexicalGlobalObject); - auto encoding_ = callFrame->argument(0).toString(lexicalGlobalObject); + auto* encoding_ = callFrame->argument(0).toStringOrNull(lexicalGlobalObject); + if (!encoding_) + return JSValue::encode(jsBoolean(false)); + std::optional<BufferEncodingType> encoded = parseEnumeration<BufferEncodingType>(*lexicalGlobalObject, encoding_); return JSValue::encode(jsBoolean(!!encoded)); } @@ -955,7 +958,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_equalsBody(JSC::JSGl } auto buffer = callFrame->uncheckedArgument(0); - JSC::JSUint8Array* view = JSC::jsDynamicCast<JSC::JSUint8Array*>(buffer); + JSC::JSArrayBufferView* view = JSC::jsDynamicCast<JSC::JSArrayBufferView*>(buffer); if (UNLIKELY(!view)) { throwVMTypeError(lexicalGlobalObject, throwScope, "Expected Buffer"_s); return JSValue::encode(jsUndefined()); @@ -969,7 +972,7 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_equalsBody(JSC::JSGl size_t a_length = castedThis->byteLength(); size_t b_length = view->byteLength(); auto sourceStartPtr = castedThis->typedVector(); - auto targetStartPtr = view->typedVector(); + auto targetStartPtr = reinterpret_cast<unsigned char*>(view->vector()); // same pointer, same length, same contents if (sourceStartPtr == targetStartPtr && a_length == b_length) @@ -1347,6 +1350,73 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_swap64Body(JSC::JSGl return JSC::JSValue::encode(castedThis); } +static inline JSC::EncodedJSValue jsBufferToString(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSUint8Array* castedThis, size_t offset, size_t length, WebCore::BufferEncodingType encoding) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + if (UNLIKELY(length == 0)) { + RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::jsEmptyString(vm))); + } + + JSC::EncodedJSValue ret = 0; + + switch (encoding) { + case WebCore::BufferEncodingType::latin1: { + LChar* data = nullptr; + auto str = String::createUninitialized(length, data); + memcpy(data, reinterpret_cast<const char*>(castedThis->typedVector() + offset), length); + return JSC::JSValue::encode(JSC::jsString(vm, WTFMove(str))); + } + + case WebCore::BufferEncodingType::ucs2: + case WebCore::BufferEncodingType::utf16le: { + UChar* data = nullptr; + size_t u16length = length / 2; + if (u16length == 0) { + return JSC::JSValue::encode(JSC::jsEmptyString(vm)); + } else { + auto str = String::createUninitialized(u16length, data); + // always zero out the last byte of the string incase the buffer is not a multiple of 2 + data[u16length - 1] = 0; + memcpy(data, reinterpret_cast<const char*>(castedThis->typedVector() + offset), length); + return JSC::JSValue::encode(JSC::jsString(vm, WTFMove(str))); + } + + break; + } + + case WebCore::BufferEncodingType::ascii: { + // ascii: we always know the length + // so we might as well allocate upfront + LChar* data = nullptr; + auto str = String::createUninitialized(length, data); + Bun__encoding__writeLatin1(castedThis->typedVector() + offset, length, data, length, static_cast<uint8_t>(encoding)); + return JSC::JSValue::encode(JSC::jsString(vm, WTFMove(str))); + } + + case WebCore::BufferEncodingType::buffer: + case WebCore::BufferEncodingType::utf8: + case WebCore::BufferEncodingType::base64: + case WebCore::BufferEncodingType::base64url: + case WebCore::BufferEncodingType::hex: { + ret = Bun__encoding__toString(castedThis->typedVector() + offset, length, lexicalGlobalObject, static_cast<uint8_t>(encoding)); + break; + } + default: { + throwTypeError(lexicalGlobalObject, scope, "Unsupported encoding? This shouldn't happen"_s); + break; + } + } + + JSC::JSValue retValue = JSC::JSValue::decode(ret); + if (UNLIKELY(!retValue.isString())) { + scope.throwException(lexicalGlobalObject, retValue); + return JSC::JSValue::encode(jsUndefined()); + } + + RELEASE_AND_RETURN(scope, JSC::JSValue::encode(retValue)); +} + static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis) { auto& vm = JSC::getVM(lexicalGlobalObject); @@ -1358,8 +1428,6 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JS if (length == 0) return JSC::JSValue::encode(JSC::jsEmptyString(vm)); - auto scope = DECLARE_THROW_SCOPE(vm); - switch (callFrame->argumentCount()) { case 0: { break; @@ -1371,6 +1439,8 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JS if (arg1.value().isString()) { std::optional<BufferEncodingType> encoded = parseEnumeration<BufferEncodingType>(*lexicalGlobalObject, arg1.value()); if (!encoded) { + auto scope = DECLARE_THROW_SCOPE(vm); + throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s); return JSC::JSValue::encode(jsUndefined()); } @@ -1386,6 +1456,8 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JS JSC::JSValue arg2 = callFrame->uncheckedArgument(1); int32_t ioffset = arg2.toInt32(lexicalGlobalObject); if (ioffset < 0) { + auto scope = DECLARE_THROW_SCOPE(vm); + throwTypeError(lexicalGlobalObject, scope, "Offset must be a positive integer"_s); return JSC::JSValue::encode(jsUndefined()); } @@ -1403,61 +1475,33 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JS length -= std::min(offset, length); - if (UNLIKELY(length == 0)) { - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(JSC::jsEmptyString(vm))); - } + return jsBufferToString(vm, lexicalGlobalObject, castedThis, offset, length, encoding); +} - JSC::EncodedJSValue ret = 0; +// DOMJIT makes it slower! TODO: investigate why +// JSC_DECLARE_JIT_OPERATION_WITHOUT_WTF_INTERNAL(jsBufferPrototypeToStringWithoutTypeChecks, JSValue, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::JSUint8Array* thisValue, JSC::JSString* encodingValue)); - switch (encoding) { - case WebCore::BufferEncodingType::latin1: { - LChar* data = nullptr; - auto str = String::createUninitialized(length, data); - memcpy(data, reinterpret_cast<const char*>(castedThis->typedVector() + offset), length); - ret = JSC::JSValue::encode(JSC::jsString(vm, WTFMove(str))); - break; - } +// JSC_DEFINE_JIT_OPERATION(jsBufferPrototypeToStringWithoutTypeChecks, JSValue, (JSC::JSGlobalObject * lexicalGlobalObject, JSUint8Array* thisValue, JSString* encodingValue)) +// { +// VM& vm = JSC::getVM(lexicalGlobalObject); +// IGNORE_WARNINGS_BEGIN("frame-address") +// CallFrame* callFrame = DECLARE_CALL_FRAME(vm); +// IGNORE_WARNINGS_END +// JSC::JITOperationPrologueCallFrameTracer tracer(vm, callFrame); - case WebCore::BufferEncodingType::ucs2: - case WebCore::BufferEncodingType::utf16le: { - UChar* data = nullptr; - size_t u16length = length / 2; - if (u16length == 0) { - ret = JSC::JSValue::encode(JSC::jsEmptyString(vm)); - } else { - auto str = String::createUninitialized(u16length, data); - // always zero out the last byte of the string incase the buffer is not a multiple of 2 - data[u16length - 1] = 0; - memcpy(data, reinterpret_cast<const char*>(castedThis->typedVector() + offset), length); - ret = JSC::JSValue::encode(JSC::jsString(vm, WTFMove(str))); - } +// std::optional<BufferEncodingType> encoded = parseEnumeration<BufferEncodingType>(*lexicalGlobalObject, encodingValue); +// if (!encoded) { +// auto scope = DECLARE_THROW_SCOPE(vm); - break; - } +// throwTypeError(lexicalGlobalObject, scope, "Invalid encoding"_s); +// return {}; +// } - case WebCore::BufferEncodingType::buffer: - case WebCore::BufferEncodingType::utf8: - case WebCore::BufferEncodingType::ascii: - case WebCore::BufferEncodingType::base64: - case WebCore::BufferEncodingType::base64url: - case WebCore::BufferEncodingType::hex: { - ret = Bun__encoding__toString(castedThis->typedVector() + offset, length, lexicalGlobalObject, static_cast<uint8_t>(encoding)); - break; - } - default: { - throwTypeError(lexicalGlobalObject, scope, "Unsupported encoding? This shouldn't happen"_s); - break; - } - } +// auto encoding = encoded.value(); - JSC::JSValue retValue = JSC::JSValue::decode(ret); - if (UNLIKELY(!retValue.isString())) { - scope.throwException(lexicalGlobalObject, retValue); - return JSC::JSValue::encode(jsUndefined()); - } +// return JSValue::decode(jsBufferToString(vm, lexicalGlobalObject, thisValue, 0, thisValue->byteLength(), encoding)); +// } - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(retValue)); -} static inline JSC::EncodedJSValue jsBufferPrototypeFunction_writeBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis) { auto& vm = JSC::getVM(lexicalGlobalObject); diff --git a/src/bun.js/bindings/JSBufferEncodingType.cpp b/src/bun.js/bindings/JSBufferEncodingType.cpp index d65e90d2b..8d99dd4db 100644 --- a/src/bun.js/bindings/JSBufferEncodingType.cpp +++ b/src/bun.js/bindings/JSBufferEncodingType.cpp @@ -30,19 +30,21 @@ namespace WebCore { using namespace JSC; +static const NeverDestroyed<String> values[] = { + MAKE_STATIC_STRING_IMPL("utf8"), + MAKE_STATIC_STRING_IMPL("ucs2"), + MAKE_STATIC_STRING_IMPL("utf16le"), + MAKE_STATIC_STRING_IMPL("latin1"), + MAKE_STATIC_STRING_IMPL("ascii"), + MAKE_STATIC_STRING_IMPL("base64"), + MAKE_STATIC_STRING_IMPL("base64url"), + MAKE_STATIC_STRING_IMPL("hex"), + MAKE_STATIC_STRING_IMPL("buffer"), +}; + String convertEnumerationToString(BufferEncodingType enumerationValue) { - static const NeverDestroyed<String> values[] = { - MAKE_STATIC_STRING_IMPL("utf8"), - MAKE_STATIC_STRING_IMPL("ucs2"), - MAKE_STATIC_STRING_IMPL("utf16le"), - MAKE_STATIC_STRING_IMPL("latin1"), - MAKE_STATIC_STRING_IMPL("ascii"), - MAKE_STATIC_STRING_IMPL("base64"), - MAKE_STATIC_STRING_IMPL("base64url"), - MAKE_STATIC_STRING_IMPL("hex"), - MAKE_STATIC_STRING_IMPL("buffer"), - }; + ASSERT(static_cast<size_t>(enumerationValue) < std::size(values)); return values[static_cast<size_t>(enumerationValue)]; } @@ -55,9 +57,9 @@ template<> JSString* convertEnumerationToJS(JSGlobalObject& lexicalGlobalObject, // this function is mostly copied from node template<> std::optional<BufferEncodingType> parseEnumeration<BufferEncodingType>(JSGlobalObject& lexicalGlobalObject, JSValue value) { - - JSC::JSString* str = value.toStringOrNull(&lexicalGlobalObject); - if (!str) + // caller must check if value is a string + JSC::JSString* str = asString(value); + if (UNLIKELY(!str)) return std::nullopt; auto encoding = str->value(&lexicalGlobalObject); @@ -75,21 +77,7 @@ template<> std::optional<BufferEncodingType> parseEnumeration<BufferEncodingType switch (encoding[0]) { case 'u': - case 'U': - // utf8, utf16le - if (encoding[1] == 't' && encoding[2] == 'f') { - // Skip `-` - const size_t skip = encoding[3] == '-' ? 4 : 3; - if (encoding[skip] == '8' && encoding[skip + 1] == '\0') - return BufferEncodingType::utf8; - if (WTF::equalIgnoringASCIICase(encoding.substringSharingImpl(skip, 5), "16le"_s)) - return BufferEncodingType::ucs2; - // ucs2 - } else if (encoding[1] == 'c' && encoding[2] == 's') { - const size_t skip = encoding[3] == '-' ? 4 : 3; - if (encoding[skip] == '2' && encoding[skip + 1] == '\0') - return BufferEncodingType::ucs2; - } + case 'U': { if (WTF::equalIgnoringASCIICase(encoding, "utf8"_s)) return BufferEncodingType::utf8; if (WTF::equalIgnoringASCIICase(encoding, "utf-8"_s)) @@ -103,35 +91,17 @@ template<> std::optional<BufferEncodingType> parseEnumeration<BufferEncodingType if (WTF::equalIgnoringASCIICase(encoding, "utf-16le"_s)) return BufferEncodingType::ucs2; break; + } case 'l': - case 'L': - // latin1 - if (encoding[1] == 'a') { - if (WTF::equalIgnoringASCIICase(encoding.substringSharingImpl(2, 4), "tin1"_s)) - return BufferEncodingType::latin1; - } + case 'L': { if (WTF::equalIgnoringASCIICase(encoding, "latin1"_s)) return BufferEncodingType::latin1; break; + } case 'b': - case 'B': - // binary is a deprecated alias of latin1 - if (encoding[1] == 'i') { - if (WTF::equalIgnoringASCIICase(encoding.substringSharingImpl(2, 5), "nary"_s)) - return BufferEncodingType::latin1; - // buffer - } else if (encoding[1] == 'u') { - if (WTF::equalIgnoringASCIICase(encoding.substringSharingImpl(2, 5), "ffer"_s)) - return BufferEncodingType::buffer; - // base64 - } else if (encoding[1] == 'a') { - if (WTF::equalIgnoringASCIICase(encoding.substringSharingImpl(2, 5), "se64"_s)) - return BufferEncodingType::base64; - if (WTF::equalIgnoringASCIICase(encoding.substringSharingImpl(2, 8), "se64url"_s)) - return BufferEncodingType::base64url; - } + case 'B': { if (WTF::equalIgnoringASCIICase(encoding, "binary"_s)) return BufferEncodingType::latin1; // BINARY is a deprecated alias of LATIN1. if (WTF::equalIgnoringASCIICase(encoding, "buffer"_s)) @@ -141,15 +111,12 @@ template<> std::optional<BufferEncodingType> parseEnumeration<BufferEncodingType if (WTF::equalIgnoringASCIICase(encoding, "base64url"_s)) return BufferEncodingType::base64url; break; + } case 'a': case 'A': // ascii - if (encoding[1] == 's') { - if (WTF::equalIgnoringASCIICase(encoding.substringSharingImpl(2, 3), "cii"_s)) - return BufferEncodingType::ascii; - } - if (WTF::equalIgnoringASCIICase(encoding, "ascii"_s)) + if (WTF::equalLettersIgnoringASCIICase(encoding, "ascii"_s)) return BufferEncodingType::ascii; break; diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index bfd775cde..54ccba343 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -108,6 +108,7 @@ #include "ModuleLoader.h" #include "ZigGeneratedClasses.h" +#include "JavaScriptCore/DateInstance.h" #include "BunPlugin.h" @@ -713,7 +714,7 @@ JSC_DEFINE_CUSTOM_GETTER(lazyProcessEnvGetter, globalObject->processEnvObject()); } -static JSC_DEFINE_HOST_FUNCTION(functionQueueMicrotask, +JSC_DEFINE_HOST_FUNCTION(functionQueueMicrotask, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); @@ -741,7 +742,7 @@ static JSC_DEFINE_HOST_FUNCTION(functionQueueMicrotask, return JSC::JSValue::encode(JSC::jsUndefined()); } -static JSC_DEFINE_HOST_FUNCTION(functionSetTimeout, +JSC_DEFINE_HOST_FUNCTION(functionSetTimeout, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); @@ -790,6 +791,45 @@ static JSC_DEFINE_HOST_FUNCTION(functionSetTimeout, return Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(job), JSC::JSValue::encode(num), JSValue::encode(arguments)); } +JSC_DEFINE_HOST_FUNCTION(functionBunSleepThenCallback, + (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + + RELEASE_ASSERT(callFrame->argumentCount() == 1); + JSPromise* promise = jsCast<JSC::JSPromise*>(callFrame->argument(0)); + RELEASE_ASSERT(promise); + + promise->resolve(globalObject, JSC::jsUndefined()); + + return JSC::JSValue::encode(promise); +} + +JSC_DEFINE_HOST_FUNCTION(functionBunSleep, + (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + JSC::VM& vm = globalObject->vm(); + + JSC::JSValue millisecondsValue = callFrame->argument(0); + + if (millisecondsValue.inherits<JSC::DateInstance>()) { + auto now = MonotonicTime::now(); + auto milliseconds = jsCast<JSC::DateInstance*>(millisecondsValue)->internalNumber() - now.approximateWallTime().secondsSinceEpoch().milliseconds(); + millisecondsValue = JSC::jsNumber(milliseconds > 0 ? milliseconds : 0); + } + + if (!millisecondsValue.isNumber()) { + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + JSC::throwTypeError(globalObject, scope, "sleep expects a number (milliseconds)"_s); + return JSC::JSValue::encode(JSC::JSValue {}); + } + + Zig::GlobalObject* global = JSC::jsCast<Zig::GlobalObject*>(globalObject); + JSC::JSPromise* promise = JSC::JSPromise::create(vm, globalObject->promiseStructure()); + Bun__Timer__setTimeout(globalObject, JSC::JSValue::encode(global->bunSleepThenCallback()), JSC::JSValue::encode(millisecondsValue), JSValue::encode(promise)); + return JSC::JSValue::encode(promise); +} + static JSC_DEFINE_HOST_FUNCTION(functionSetInterval, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { @@ -2490,6 +2530,11 @@ void GlobalObject::finishCreation(VM& vm) init.set(JSFunction::create(init.vm, init.owner, 4, "emitReadable"_s, WebCore::jsReadable_emitReadable_, ImplementationVisibility::Public)); }); + m_bunSleepThenCallback.initLater( + [](const Initializer<JSFunction>& init) { + init.set(JSFunction::create(init.vm, init.owner, 1, "onSleep"_s, functionBunSleepThenCallback, ImplementationVisibility::Public)); + }); + m_performMicrotaskVariadicFunction.initLater( [](const Initializer<JSFunction>& init) { init.set(JSFunction::create(init.vm, init.owner, 4, "performMicrotaskVariadic"_s, jsFunctionPerformMicrotaskVariadic, ImplementationVisibility::Public)); @@ -3344,6 +3389,13 @@ void GlobalObject::installAPIGlobals(JSClassRef* globals, int count, JSC::VM& vm { + JSC::Identifier identifier = JSC::Identifier::fromString(vm, "sleep"_s); + object->putDirectNativeFunction(vm, this, identifier, 1, functionBunSleep, ImplementationVisibility::Public, NoIntrinsic, + JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0); + } + + { + JSC::Identifier identifier = JSC::Identifier::fromString(vm, "env"_s); object->putDirectCustomAccessor(vm, identifier, JSC::CustomGetterSetter::create(vm, lazyProcessEnvGetter, lazyProcessEnvSetter), @@ -3529,6 +3581,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor) thisObject->m_requireResolveFunctionStructure.visit(visitor); thisObject->m_resolveFunctionPrototype.visit(visitor); thisObject->m_dnsObject.visit(visitor); + thisObject->m_bunSleepThenCallback.visit(visitor); for (auto& barrier : thisObject->m_thenables) { visitor.append(barrier); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index f2364fd61..2b688f09d 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -247,6 +247,8 @@ public: Structure* requireResolveFunctionStructure() { return m_requireResolveFunctionStructure.getInitializedOnMainThread(this); } JSObject* requireResolveFunctionPrototype() { return m_resolveFunctionPrototype.getInitializedOnMainThread(this); } + JSFunction* bunSleepThenCallback() { return m_bunSleepThenCallback.getInitializedOnMainThread(this); } + JSObject* dnsObject() { return m_dnsObject.getInitializedOnMainThread(this); } JSC::JSObject* processObject() @@ -453,6 +455,7 @@ private: LazyProperty<JSGlobalObject, JSC::Structure> m_requireResolveFunctionStructure; LazyProperty<JSGlobalObject, JSObject> m_resolveFunctionPrototype; LazyProperty<JSGlobalObject, JSObject> m_dnsObject; + LazyProperty<JSGlobalObject, JSFunction> m_bunSleepThenCallback; DOMGuardedObjectSet m_guardedObjects WTF_GUARDED_BY_LOCK(m_gcLock); void* m_bunVM; diff --git a/src/bun.js/crypto.exports.js b/src/bun.js/crypto.exports.js index fb38a9352..6f9e82b5d 100644 --- a/src/bun.js/crypto.exports.js +++ b/src/bun.js/crypto.exports.js @@ -6,6 +6,8 @@ var __getProtoOf = Object.getPrototypeOf, __hasOwnProp = Object.prototype.hasOwnProperty; var __require = id => import.meta.require(id); +const crypto = globalThis.crypto; +const globalCrypto = crypto; var __esm = (fn, res) => function () { @@ -89,7 +91,7 @@ var require_browser = __commonJS({ Use Chrome, Firefox or Internet Explorer 11`); } var Buffer2 = require_safe_buffer().Buffer, - crypto2 = global.crypto || global.msCrypto; + crypto2 = globalCrypto; crypto2 && crypto2.getRandomValues ? (module.exports = randomBytes) : (module.exports = oldBrowser); function randomBytes(size, cb) { if (size > MAX_UINT32) throw new RangeError("requested too many random bytes"); @@ -1588,7 +1590,7 @@ var require_async = __commonJS({ sync = require_sync_browser(), toBuffer = require_to_buffer(), ZERO_BUF, - subtle = global.crypto && global.crypto.subtle, + subtle = globalCrypto.subtle, toBrowser = { sha: "SHA-1", "sha-1": "SHA-1", @@ -23553,15 +23555,10 @@ var require_browser10 = __commonJS({ var require_browser11 = __commonJS({ "node_modules/randomfill/browser.js"(exports) { "use strict"; - function oldBrowser() { - throw new Error(`secure random number generation not supported by this browser -use chrome, FireFox or Internet Explorer 11`); - } var safeBuffer = require_safe_buffer(), randombytes = require_browser(), Buffer2 = safeBuffer.Buffer, kBufferMaxLength = safeBuffer.kMaxLength, - crypto2 = global.crypto || global.msCrypto, kMaxUint32 = Math.pow(2, 32) - 1; function assertOffset(offset, length) { if (typeof offset != "number" || offset !== offset) throw new TypeError("offset must be a number"); @@ -23654,6 +23651,7 @@ var require_crypto_browserify2 = __commonJS({ exports.privateEncrypt = publicEncrypt.privateEncrypt; exports.publicDecrypt = publicEncrypt.publicDecrypt; exports.privateDecrypt = publicEncrypt.privateDecrypt; + exports.getRandomValues = values => crypto.getRandomValues(values); var rf = require_browser11(); exports.randomFill = rf.randomFill; exports.randomFillSync = rf.randomFillSync; @@ -23692,15 +23690,6 @@ var crypto_exports = { ...require_crypto_browserify2(), [Symbol.for("CommonJS")]: 0, }; -__export(crypto_exports, { - DEFAULT_ENCODING: () => DEFAULT_ENCODING, - getRandomValues: () => getRandomValues, - randomUUID: () => randomUUID, - scrypt: () => scrypt, - scryptSync: () => scryptSync, - timingSafeEqual: () => timingSafeEqual, - webcrypto: () => webcrypto, -}); var DEFAULT_ENCODING = "buffer", getRandomValues = array => crypto.getRandomValues(array), randomUUID = () => crypto.randomUUID(), @@ -23754,6 +23743,17 @@ timingSafeEqual && value: "::bunternal::", })); var webcrypto = crypto; +__export(crypto_exports, { + DEFAULT_ENCODING: () => DEFAULT_ENCODING, + getRandomValues: () => getRandomValues, + randomUUID: () => randomUUID, + scrypt: () => scrypt, + scryptSync: () => scryptSync, + timingSafeEqual: () => timingSafeEqual, + webcrypto: () => webcrypto, + subtle: () => webcrypto.subtle, +}); + export const { randomBytes, rng, diff --git a/src/bun.js/node/node_os.zig b/src/bun.js/node/node_os.zig index 525ee991b..f7a91393e 100644 --- a/src/bun.js/node/node_os.zig +++ b/src/bun.js/node/node_os.zig @@ -54,11 +54,147 @@ pub const Os = struct { return JSC.ZigString.init(Global.arch_name).withEncoding().toValue(globalThis); } + const CPU = struct { + model: JSC.ZigString = JSC.ZigString.init("unknown"), + speed: u64 = 0, + times: struct { + user: u64 = 0, + nice: u64 = 0, + sys: u64 = 0, + idle: u64 = 0, + irq: u64 = 0, + } = .{} + }; + pub fn cpus(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { JSC.markBinding(@src()); - // TODO: - return JSC.JSArray.from(globalThis, &.{}); + var cpu_buffer: [8192]CPU = undefined; + const cpus_or_error = if (comptime Environment.isLinux) + cpusImplLinux(&cpu_buffer) + else + @as(anyerror![]CPU, cpu_buffer[0..0]); // unsupported platform -> empty array + + if (cpus_or_error) |list| { + // Convert the CPU list to a JS Array + const values = JSC.JSValue.createEmptyArray(globalThis, list.len); + for (list) |cpu, cpu_index| { + const obj = JSC.JSValue.createEmptyObject(globalThis, 3); + obj.put(globalThis, JSC.ZigString.static("model"), cpu.model.withEncoding().toValueGC(globalThis)); + obj.put(globalThis, JSC.ZigString.static("speed"), JSC.JSValue.jsNumberFromUint64(cpu.speed)); + + const timesFields = comptime std.meta.fieldNames(@TypeOf(cpu.times)); + const times = JSC.JSValue.createEmptyObject(globalThis, 5); + inline for (timesFields) |fieldName| { + times.put(globalThis, JSC.ZigString.static(fieldName), + JSC.JSValue.jsNumberFromUint64(@field(cpu.times, fieldName))); + } + obj.put(globalThis, JSC.ZigString.static("times"), times); + values.putIndex(globalThis, @intCast(u32, cpu_index), obj); + } + return values; + + } else |zig_err| { + const msg = switch (zig_err) { + error.too_many_cpus => "Too many CPUs or malformed /proc/cpuinfo file", + error.eol => "Malformed /proc/stat file", + else => "An error occurred while fetching cpu information", + }; + //TODO more suitable error type? + const err = JSC.SystemError{ + .message = JSC.ZigString.init(msg), + }; + globalThis.vm().throwError(globalThis, err.toErrorInstance(globalThis)); + return JSC.JSValue.jsUndefined(); + } + } + + fn cpusImplLinux(cpu_buffer: []CPU) ![]CPU { + // Use a large line buffer because the /proc/stat file can have a very long list of interrupts + var line_buffer: [1024*8]u8 = undefined; + var num_cpus: usize = 0; + + // Read /proc/stat to get number of CPUs and times + if (std.fs.openFileAbsolute("/proc/stat", .{})) |file| { + defer file.close(); + var reader = file.reader(); + + // Skip the first line (aggregate of all CPUs) + try reader.skipUntilDelimiterOrEof('\n'); + + // Read each CPU line + while (try reader.readUntilDelimiterOrEof(&line_buffer, '\n')) |line| { + + if (num_cpus >= cpu_buffer.len) return error.too_many_cpus; + + // CPU lines are formatted as `cpu0 user nice sys idle iowait irq softirq` + var toks = std.mem.tokenize(u8, line, " \t"); + const cpu_name = toks.next(); + if (cpu_name == null or !std.mem.startsWith(u8, cpu_name.?, "cpu")) break; // done with CPUs + + // Default initialize the CPU to ensure that we never return uninitialized fields + cpu_buffer[num_cpus] = CPU{}; + + //NOTE: libuv assumes this is fixed on Linux, not sure that's actually the case + const scale = 10; + cpu_buffer[num_cpus].times.user = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10); + cpu_buffer[num_cpus].times.nice = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10); + cpu_buffer[num_cpus].times.sys = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10); + cpu_buffer[num_cpus].times.idle = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10); + _ = try (toks.next() orelse error.eol); // skip iowait + cpu_buffer[num_cpus].times.irq = scale * try std.fmt.parseInt(u64, toks.next() orelse return error.eol, 10); + + num_cpus += 1; + } + } else |_| { + return error.cannot_open_proc_stat; + } + + const slice = cpu_buffer[0..num_cpus]; + + // Read /proc/cpuinfo to get model information (optional) + if (std.fs.openFileAbsolute("/proc/cpuinfo", .{})) |file| { + defer file.close(); + var reader = file.reader(); + const key_processor = "processor\t: "; + const key_model_name = "model name\t: "; + + var cpu_index: usize = 0; + while (try reader.readUntilDelimiterOrEof(&line_buffer, '\n')) |line| { + + if (std.mem.startsWith(u8, line, key_processor)) { + // If this line starts a new processor, parse the index from the line + const digits = std.mem.trim(u8, line[key_processor.len..], " \t\n"); + cpu_index = try std.fmt.parseInt(usize, digits, 10); + if (cpu_index >= slice.len) return error.too_may_cpus; + + } else if (std.mem.startsWith(u8, line, key_model_name)) { + // If this is the model name, extract it and store on the current cpu + const model_name = line[key_model_name.len..]; + slice[cpu_index].model = JSC.ZigString.init(model_name); + } + //TODO: special handling for ARM64 (no model name)? + } + } else |_| { + // Do nothing: CPU default initializer has set model name to "unknown" + } + + // Read /sys/devices/system/cpu/cpu{}/cpufreq/scaling_cur_freq to get current frequency (optional) + for (slice) |*cpu, cpu_index| { + var path_buf: [128]u8 = undefined; + const path = try std.fmt.bufPrint(&path_buf, "/sys/devices/system/cpu/cpu{}/cpufreq/scaling_cur_freq", .{cpu_index}); + if (std.fs.openFileAbsolute(path, .{})) |file| { + defer file.close(); + + const bytes_read = try file.readAll(&line_buffer); + const digits = std.mem.trim(u8, line_buffer[0..bytes_read], " \n"); + cpu.speed = try std.fmt.parseInt(u64, digits, 10) / 1000; + } else |_| { + // Do nothing: CPU default initializer has set speed to 0 + } + } + + return slice; } pub fn endianness(globalThis: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue { diff --git a/src/bun.js/perf_hooks.exports.js b/src/bun.js/perf_hooks.exports.js index 8a1a9a915..c461abafe 100644 --- a/src/bun.js/perf_hooks.exports.js +++ b/src/bun.js/perf_hooks.exports.js @@ -22,4 +22,5 @@ export default { PerformanceEntry, PerformanceEntry, PerformanceNodeTiming, + [Symbol.for("CommonJS")]: 0, }; diff --git a/src/bun.js/util.exports.js b/src/bun.js/util.exports.js index 1bba01977..c0adc0344 100644 --- a/src/bun.js/util.exports.js +++ b/src/bun.js/util.exports.js @@ -27,8 +27,11 @@ var require_inherits_browser = __commonJS({ }; }, }); - -const exports = {}; +const deepEquals = Bun.deepEquals; +const isDeepStrictEqual = (a, b) => deepEquals(a, b, true); +const exports = { + isDeepStrictEqual, +}; var getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors; var formatRegExp = /%[sdj%]/g; function format(f) { @@ -576,4 +579,5 @@ export { inherits, promisify, callbackify, + isDeepStrictEqual, }; diff --git a/src/bun.js/webcore/encoding.zig b/src/bun.js/webcore/encoding.zig index 6729cc4de..8a6e3224b 100644 --- a/src/bun.js/webcore/encoding.zig +++ b/src/bun.js/webcore/encoding.zig @@ -795,15 +795,19 @@ pub const Encoder = struct { switch (comptime encoding) { .ascii => { - var to = allocator.alloc(u8, len) catch return ZigString.init("Out of memory").toErrorInstance(global); - - @memcpy(to.ptr, input_ptr, to.len); + if (bun.simdutf.validate.ascii(input)) { + return ZigString.init(input).toValueGC(global); + } - // Hoping this gets auto vectorized - for (to[0..to.len]) |c, i| { - to[i] = @as(u8, @truncate(u7, c)); + if (input.len < 512) { + var buf: [512]u8 = undefined; + var to = buf[0..input.len]; + strings.copyLatin1IntoASCII(to, input); + return ZigString.init(to).toValueGC(global); } + var to = allocator.alloc(u8, len) catch return ZigString.init("Out of memory").toErrorInstance(global); + strings.copyLatin1IntoASCII(to, input); return ZigString.init(to).toExternalValue(global); }, .latin1 => { @@ -857,7 +861,7 @@ pub const Encoder = struct { } } - pub fn writeU8(input: [*]const u8, len: usize, to: [*]u8, to_len: usize, comptime encoding: JSC.Node.Encoding) i64 { + pub fn writeU8(input: [*]const u8, len: usize, to_ptr: [*]u8, to_len: usize, comptime encoding: JSC.Node.Encoding) i64 { if (len == 0 or to_len == 0) return 0; @@ -871,39 +875,42 @@ pub const Encoder = struct { switch (comptime encoding) { JSC.Node.Encoding.buffer => { const written = @min(len, to_len); - @memcpy(to, input, written); + @memcpy(to_ptr, input, written); return @intCast(i64, written); }, .latin1, .ascii => { const written = @min(len, to_len); - @memcpy(to, input, written); - // Hoping this gets auto vectorized - for (to[0..written]) |c, i| { - to[i] = @as(u8, @truncate(u7, c)); + var to = to_ptr[0..written]; + var remain = input[0..written]; + + if (bun.simdutf.validate.ascii(remain)) { + @memcpy(to.ptr, remain.ptr, written); + } else { + strings.copyLatin1IntoASCII(to, remain); } return @intCast(i64, written); }, .utf8 => { // need to encode - return @intCast(i64, strings.copyLatin1IntoUTF8(to[0..to_len], []const u8, input[0..len]).written); + return @intCast(i64, strings.copyLatin1IntoUTF8(to_ptr[0..to_len], []const u8, input[0..len]).written); }, // encode latin1 into UTF16 JSC.Node.Encoding.ucs2, JSC.Node.Encoding.utf16le => { if (to_len < 2) return 0; - if (std.mem.isAligned(@ptrToInt(to), @alignOf([*]u16))) { + if (std.mem.isAligned(@ptrToInt(to_ptr), @alignOf([*]u16))) { var buf = input[0..len]; - var output = @ptrCast([*]u16, @alignCast(@alignOf(u16), to))[0 .. to_len / 2]; + var output = @ptrCast([*]u16, @alignCast(@alignOf(u16), to_ptr))[0 .. to_len / 2]; var written = strings.copyLatin1IntoUTF16([]u16, output, []const u8, buf).written; return written * 2; } else { var buf = input[0..len]; - var output = @ptrCast([*]align(1) u16, to)[0 .. to_len / 2]; + var output = @ptrCast([*]align(1) u16, to_ptr)[0 .. to_len / 2]; var written = strings.copyLatin1IntoUTF16([]align(1) u16, output, []const u8, buf).written; return written * 2; @@ -911,7 +918,7 @@ pub const Encoder = struct { }, JSC.Node.Encoding.hex => { - return @intCast(i64, strings.decodeHexToBytes(to[0..to_len], u8, input[0..len])); + return @intCast(i64, strings.decodeHexToBytes(to_ptr[0..to_len], u8, input[0..len])); }, JSC.Node.Encoding.base64url => { @@ -919,18 +926,18 @@ pub const Encoder = struct { if (slice.len == 0) return 0; - if (strings.eqlComptime(slice[slice.len - 2 ..][0..2], "==")) { + if (strings.endsWithComptime(slice, "==")) { slice = slice[0 .. slice.len - 2]; } else if (slice[slice.len - 1] == '=') { slice = slice[0 .. slice.len - 1]; } - const wrote = bun.base64.decodeURLSafe(to[0..to_len], slice).written; + const wrote = bun.base64.decodeURLSafe(to_ptr[0..to_len], slice).written; return @intCast(i64, wrote); }, JSC.Node.Encoding.base64 => { - return @intCast(i64, bun.base64.decode(to[0..to_len], input[0..len]).written); + return @intCast(i64, bun.base64.decode(to_ptr[0..to_len], input[0..len]).written); }, // else => return 0, } @@ -1094,7 +1101,7 @@ pub const Encoder = struct { if (slice.len == 0) return &[_]u8{}; - if (strings.eqlComptime(slice[slice.len - 2 ..][0..2], "==")) { + if (strings.endsWithComptime(slice, "==")) { slice = slice[0 .. slice.len - 2]; } else if (slice[slice.len - 1] == '=') { slice = slice[0 .. slice.len - 1]; diff --git a/src/node-fallbacks/package-lock.json b/src/node-fallbacks/package-lock.json index 18307a703..1a5b9a13a 100644 --- a/src/node-fallbacks/package-lock.json +++ b/src/node-fallbacks/package-lock.json @@ -23,7 +23,7 @@ "path-browserify": "^1.0.1", "process": "^0.11.10", "punycode": "^2.1.1", - "querystring-es3": "^0.2.1", + "querystring-es3": "^1.0.0-0", "readable-stream": "^4.1.0", "stream-http": "^3.2.0", "string_decoder": "^1.3.0", @@ -1188,11 +1188,24 @@ } }, "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "version": "1.0.0-0", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-1.0.0-0.tgz", + "integrity": "sha512-0etE6Uj4NYut0/y/6pFgVRDAaFtf6x/n4xnBRL82c8n6yigRBepcgPaBAn2o8EhA9QQR1l8b1Je5s+LsJ+zS+g==", + "dependencies": { + "buffer": "5.0.5" + }, "engines": { - "node": ">=0.4.x" + "node": ">=4" + } + }, + "node_modules/querystring-es3/node_modules/buffer": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.0.5.tgz", + "integrity": "sha512-ye69HVxM0Htf+E5YhFLOfHX8dXOkammTDtqCU5xLFMlPYTQkNJ7Kv6I90TjZiPH1yXBHQdRvSAK28EZSwSm3Dg==", + "deprecated": "This version of 'buffer' is out-of-date. You must update to v5.0.8 or newer", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" } }, "node_modules/randombytes": { @@ -2275,9 +2288,23 @@ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" + "version": "1.0.0-0", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-1.0.0-0.tgz", + "integrity": "sha512-0etE6Uj4NYut0/y/6pFgVRDAaFtf6x/n4xnBRL82c8n6yigRBepcgPaBAn2o8EhA9QQR1l8b1Je5s+LsJ+zS+g==", + "requires": { + "buffer": "5.0.5" + }, + "dependencies": { + "buffer": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.0.5.tgz", + "integrity": "sha512-ye69HVxM0Htf+E5YhFLOfHX8dXOkammTDtqCU5xLFMlPYTQkNJ7Kv6I90TjZiPH1yXBHQdRvSAK28EZSwSm3Dg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + } + } }, "randombytes": { "version": "2.1.0", diff --git a/src/node-fallbacks/package.json b/src/node-fallbacks/package.json index 66ff79c5b..144f553c0 100644 --- a/src/node-fallbacks/package.json +++ b/src/node-fallbacks/package.json @@ -25,7 +25,7 @@ "path-browserify": "^1.0.1", "process": "^0.11.10", "punycode": "^2.1.1", - "querystring-es3": "^0.2.1", + "querystring-es3": "^1.0.0-0", "readable-stream": "^4.1.0", "stream-http": "^3.2.0", "string_decoder": "^1.3.0", diff --git a/src/string_immutable.zig b/src/string_immutable.zig index d24edb99a..775de74d4 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -1085,6 +1085,53 @@ pub inline fn copyU16IntoU8(output_: []u8, comptime InputType: type, input_: Inp const strings = @This(); +pub fn copyLatin1IntoASCII(dest: []u8, src: []const u8) void { + var remain = src; + var to = dest; + + const non_ascii_offset = strings.firstNonASCII(remain) orelse @truncate(u32, remain.len); + if (non_ascii_offset > 0) { + @memcpy(to.ptr, remain.ptr, non_ascii_offset); + remain = remain[non_ascii_offset..]; + to = to[non_ascii_offset..]; + + // ascii fast path + if (remain.len == 0) { + return; + } + } + + if (to.len >= 16 and bun.Environment.enableSIMD) { + const vector_size = 16; + // https://zig.godbolt.org/z/qezsY8T3W + var remain_in_u64 = remain[0 .. remain.len - (remain.len % vector_size)]; + var to_in_u64 = to[0 .. to.len - (to.len % vector_size)]; + var remain_as_u64 = std.mem.bytesAsSlice(u64, remain_in_u64); + var to_as_u64 = std.mem.bytesAsSlice(u64, to_in_u64); + const end_vector_len = @min(remain_as_u64.len, to_as_u64.len); + remain_as_u64 = remain_as_u64[0..end_vector_len]; + to_as_u64 = to_as_u64[0..end_vector_len]; + const end_ptr = remain_as_u64.ptr + remain_as_u64.len; + // using the pointer instead of the length is super important for the codegen + while (end_ptr != remain_as_u64.ptr) { + const buf = remain_as_u64[0]; + // this gets auto-vectorized + const mask = @as(u64, 0x7f7f7f7f7f7f7f7f); + to_as_u64[0] = buf & mask; + + remain_as_u64 = remain_as_u64[1..]; + to_as_u64 = to_as_u64[1..]; + } + remain = remain[remain_in_u64.len..]; + to = to[to_in_u64.len..]; + } + + for (to) |*to_byte| { + to_byte.* = @as(u8, @truncate(u7, remain[0])); + remain = remain[1..]; + } +} + /// Convert a UTF-8 string to a UTF-16 string IF there are any non-ascii characters /// If there are no non-ascii characters, this returns null /// This is intended to be used for strings that go to JavaScript @@ -1096,28 +1143,23 @@ pub fn toUTF16Alloc(allocator: std.mem.Allocator, bytes: []const u8, comptime fa if (bytes.len == 0) return &[_]u16{}; use_simdutf: { - const validated = bun.simdutf.validate.with_errors.ascii(bytes); - if (validated.status == .success) + if (bun.simdutf.validate.ascii(bytes)) return null; - const offset = @truncate(u32, validated.count); - - const trimmed = bun.simdutf.trim.utf8(bytes[offset..]); + const trimmed = bun.simdutf.trim.utf8(bytes); if (trimmed.len == 0) break :use_simdutf; const out_length = bun.simdutf.length.utf16.from.utf8.le(trimmed); - if (out_length != trimmed.len) + if (out_length == 0) break :use_simdutf; - var out = try allocator.alloc(u16, out_length + offset); + var out = try allocator.alloc(u16, out_length); log("toUTF16 {d} UTF8 -> {d} UTF16", .{ bytes.len, out_length }); - if (offset > 0) - strings.copyU8IntoU16(out[0..offset], bytes[0..offset]); - const result = bun.simdutf.convert.utf8.to.utf16.with_errors.le(trimmed, out[offset..]); + const result = bun.simdutf.convert.utf8.to.utf16.with_errors.le(trimmed, out); switch (result.status) { .success => { return out; @@ -1128,7 +1170,7 @@ pub fn toUTF16Alloc(allocator: std.mem.Allocator, bytes: []const u8, comptime fa return error.InvalidByteSequence; } - first_non_ascii = @truncate(u32, result.count) + offset; + first_non_ascii = @truncate(u32, result.count); output_ = std.ArrayList(u16){ .items = out[0..first_non_ascii.?], .capacity = out.len, diff --git a/test/bun.js/node-crypto.test.js b/test/bun.js/node-crypto.test.js index 5d9d6a77d..f148f4fe9 100644 --- a/test/bun.js/node-crypto.test.js +++ b/test/bun.js/node-crypto.test.js @@ -21,3 +21,9 @@ it("crypto.createHmac", () => { expect(result).toBe("bp7ym3X//Ft6uuUn1Y/a2y/kLnIZARl2kXNDBl9Y7Uo="); }); + +it("web crypto", async () => { + let bytes = new Uint8Array(32); + crypto.getRandomValues(bytes); + await crypto.subtle.digest("SHA-256", bytes); +}); diff --git a/test/bun.js/node-dns.test.js b/test/bun.js/node-dns.test.js index a2780374f..6f4cac22f 100644 --- a/test/bun.js/node-dns.test.js +++ b/test/bun.js/node-dns.test.js @@ -53,7 +53,7 @@ test("dns.resolveTxt (txt.socketify.dev)", done => { test("dns.resolveSoa (bun.sh)", done => { dns.resolveSoa("bun.sh", (err, result) => { expect(err).toBeNull(); - expect(result.serial).toBe(2295878541); + expect(typeof result.serial).toBe("number"); expect(result.refresh).toBe(10000); expect(result.retry).toBe(2400); expect(result.expire).toBe(604800); diff --git a/test/bun.js/setTimeout.test.js b/test/bun.js/setTimeout.test.js index 9cd16ece2..393a32bbe 100644 --- a/test/bun.js/setTimeout.test.js +++ b/test/bun.js/setTimeout.test.js @@ -89,3 +89,49 @@ it("setTimeout(() => {}, 0)", async () => { }); expect(ranFirst).toBe(-1); }); + +it("Bun.sleep", async () => { + var sleeps = 0; + await Bun.sleep(0); + const start = performance.now(); + sleeps++; + await Bun.sleep(1); + sleeps++; + await Bun.sleep(2); + sleeps++; + const end = performance.now(); + expect((end - start) * 1000).toBeGreaterThanOrEqual(3); + + expect(sleeps).toBe(3); +}); + +it("Bun.sleep propagates exceptions", async () => { + try { + await Bun.sleep(1).then(a => { + throw new Error("TestPassed"); + }); + throw "Should not reach here"; + } catch (err) { + expect(err.message).toBe("TestPassed"); + } +}); + +it("Bun.sleep works with a Date object", async () => { + var ten_ms = new Date(); + ten_ms.setMilliseconds(ten_ms.getMilliseconds() + 10); + const now = performance.now(); + await Bun.sleep(ten_ms); + expect(performance.now() - now).toBeGreaterThanOrEqual(10); +}); + +it("node.js timers/promises setTimeout propagates exceptions", async () => { + const { setTimeout } = require("timers/promises"); + try { + await setTimeout(1).then(a => { + throw new Error("TestPassed"); + }); + throw "Should not reach here"; + } catch (err) { + expect(err.message).toBe("TestPassed"); + } +}); diff --git a/test/bun.js/text-decoder.test.js b/test/bun.js/text-decoder.test.js index be3f8421b..da0497464 100644 --- a/test/bun.js/text-decoder.test.js +++ b/test/bun.js/text-decoder.test.js @@ -42,7 +42,118 @@ describe("TextDecoder", () => { gcTrace(true); expect(decoder.decode(new Uint8Array([0x41, 0x42, 0x43]))).toBe("ABC"); gcTrace(true); - const result = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]; + + // hit the SIMD code path + const result = [ + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, + ]; gcTrace(true); expect(decoder.decode(Uint8Array.from(result))).toBe(String.fromCharCode(...result)); gcTrace(true); @@ -51,19 +162,16 @@ describe("TextDecoder", () => { it("should decode unicode text", () => { const decoder = new TextDecoder(); gcTrace(true); - var text = `❤️ Red Heart`; - - const bytes = [226, 157, 164, 239, 184, 143, 32, 82, 101, 100, 32, 72, 101, 97, 114, 116]; - const decoded = decoder.decode(Uint8Array.from(bytes)); - expect(decoder.encoding).toBe("utf-8"); + const inputBytes = [226, 157, 164, 239, 184, 143, 32, 82, 101, 100, 32, 72, 101, 97, 114, 116]; + for (var repeat = 1; repeat < 100; repeat++) { + var text = `❤️ Red Heart`.repeat(repeat); - gcTrace(true); - - for (let i = 0; i < text.length; i++) { - expect(decoded.charCodeAt(i)).toBe(text.charCodeAt(i)); + var bytes = Array.from({ length: repeat }, () => inputBytes).flat(); + var decoded = decoder.decode(Uint8Array.from(bytes)); + expect(decoder.encoding).toBe("utf-8"); + expect(decoded).toBe(text); + gcTrace(true); } - expect(decoded).toHaveLength(text.length); - gcTrace(true); }); describe("typedArrays", () => { |