Files
bun.sh/src/bun.js/api/bun.zig
Dylan Conway f7f6233ea8 fix semver edge cases and buffer used for prerelease comparisons (#6981)
* use different buf for group and manifest versions

* tests

* tests for installs that should fail

* allow `<=<prerelease-version`

* `Bun.semver.satisfies`

* one

* stringify

* symbol tests

* deinit

* arena

* more tests

* fix test

* version always on the left

* `Bun.semver.order`

* handle more edge cases, more tests

* whitespace

* more
2023-11-08 03:36:17 -08:00

4661 lines
180 KiB
Zig

/// How to add a new function or property to the Bun global
///
/// - Add a callback or property to the below struct
/// - @export it in the appropriate place
/// - Update "@begin bunObjectTable" in BunObject.cpp
/// - Getters use a generated wrapper function `BunObject_getter_wrap_<name>`
/// - Update "BunObject+exports.h"
/// - Run "make dev"
pub const BunObject = struct {
// --- Callbacks ---
pub const DO_NOT_USE_OR_YOU_WILL_BE_FIRED_mimalloc_dump = dump_mimalloc;
pub const _Os = Bun._Os;
pub const _Path = Bun._Path;
pub const allocUnsafe = Bun.allocUnsafe;
pub const build = Bun.JSBundler.buildFn;
pub const connect = JSC.wrapStaticMethod(JSC.API.Listener, "connect", false);
pub const deflateSync = JSC.wrapStaticMethod(JSZlib, "deflateSync", true);
pub const file = WebCore.Blob.constructBunFile;
pub const fs = Bun.fs;
pub const gc = Bun.runGC;
pub const generateHeapSnapshot = Bun.generateHeapSnapshot;
pub const getImportedStyles = Bun.getImportedStyles;
pub const getPublicPath = Bun.getPublicPathJS;
pub const gunzipSync = JSC.wrapStaticMethod(JSZlib, "gunzipSync", true);
pub const gzipSync = JSC.wrapStaticMethod(JSZlib, "gzipSync", true);
pub const indexOfLine = Bun.indexOfLine;
pub const inflateSync = JSC.wrapStaticMethod(JSZlib, "inflateSync", true);
pub const jest = @import("../test/jest.zig").Jest.call;
pub const listen = JSC.wrapStaticMethod(JSC.API.Listener, "listen", false);
pub const mmap = Bun.mmapFile;
pub const nanoseconds = Bun.nanoseconds;
pub const openInEditor = Bun.openInEditor;
pub const registerMacro = Bun.registerMacro;
pub const resolve = Bun.resolve;
pub const resolveSync = Bun.resolveSync;
pub const serve = Bun.serve;
pub const sha = JSC.wrapStaticMethod(Crypto.SHA512_256, "hash_", true);
pub const shrink = Bun.shrink;
pub const sleepSync = Bun.sleepSync;
pub const spawn = JSC.wrapStaticMethod(JSC.Subprocess, "spawn", false);
pub const spawnSync = JSC.wrapStaticMethod(JSC.Subprocess, "spawnSync", false);
pub const which = Bun.which;
pub const write = JSC.WebCore.Blob.writeFile;
// --- Callbacks ---
// --- Getters ---
pub const CryptoHasher = Crypto.CryptoHasher.getter;
pub const FFI = Bun.FFIObject.getter;
pub const FileSystemRouter = Bun.getFileSystemRouter;
pub const MD4 = Crypto.MD4.getter;
pub const MD5 = Crypto.MD5.getter;
pub const SHA1 = Crypto.SHA1.getter;
pub const SHA224 = Crypto.SHA224.getter;
pub const SHA256 = Crypto.SHA256.getter;
pub const SHA384 = Crypto.SHA384.getter;
pub const SHA512 = Crypto.SHA512.getter;
pub const SHA512_256 = Crypto.SHA512_256.getter;
pub const TOML = Bun.getTOMLObject;
pub const Transpiler = Bun.getTranspilerConstructor;
pub const argv = Bun.getArgv;
pub const assetPrefix = Bun.getAssetPrefix;
pub const cwd = Bun.getCWD;
pub const enableANSIColors = Bun.enableANSIColors;
pub const hash = Bun.getHashObject;
pub const inspect = Bun.getInspect;
pub const main = Bun.getMain;
pub const origin = Bun.getOrigin;
pub const stderr = Bun.getStderr;
pub const stdin = Bun.getStdin;
pub const stdout = Bun.getStdout;
pub const unsafe = Bun.getUnsafe;
pub const semver = Bun.getSemver;
// --- Getters ---
fn getterName(comptime baseName: anytype) [:0]const u8 {
return "BunObject_getter_" ++ baseName;
}
fn callbackName(comptime baseName: anytype) [:0]const u8 {
return "BunObject_callback_" ++ baseName;
}
pub fn exportAll() void {
if (!@inComptime()) {
@compileError("Must be comptime");
}
if (JSC.is_bindgen) {
return;
}
// --- Getters ---
@export(BunObject.CryptoHasher, .{ .name = getterName("CryptoHasher") });
@export(BunObject.FFI, .{ .name = getterName("FFI") });
@export(BunObject.FileSystemRouter, .{ .name = getterName("FileSystemRouter") });
@export(BunObject.MD4, .{ .name = getterName("MD4") });
@export(BunObject.MD5, .{ .name = getterName("MD5") });
@export(BunObject.SHA1, .{ .name = getterName("SHA1") });
@export(BunObject.SHA224, .{ .name = getterName("SHA224") });
@export(BunObject.SHA256, .{ .name = getterName("SHA256") });
@export(BunObject.SHA384, .{ .name = getterName("SHA384") });
@export(BunObject.SHA512, .{ .name = getterName("SHA512") });
@export(BunObject.SHA512_256, .{ .name = getterName("SHA512_256") });
@export(BunObject.TOML, .{ .name = getterName("TOML") });
@export(BunObject.Transpiler, .{ .name = getterName("Transpiler") });
@export(BunObject.argv, .{ .name = getterName("argv") });
@export(BunObject.assetPrefix, .{ .name = getterName("assetPrefix") });
@export(BunObject.cwd, .{ .name = getterName("cwd") });
@export(BunObject.enableANSIColors, .{ .name = getterName("enableANSIColors") });
@export(BunObject.hash, .{ .name = getterName("hash") });
@export(BunObject.inspect, .{ .name = getterName("inspect") });
@export(BunObject.main, .{ .name = getterName("main") });
@export(BunObject.origin, .{ .name = getterName("origin") });
@export(BunObject.stderr, .{ .name = getterName("stderr") });
@export(BunObject.stdin, .{ .name = getterName("stdin") });
@export(BunObject.stdout, .{ .name = getterName("stdout") });
@export(BunObject.unsafe, .{ .name = getterName("unsafe") });
@export(BunObject.semver, .{ .name = getterName("semver") });
// --- Getters --
// -- Callbacks --
@export(BunObject.DO_NOT_USE_OR_YOU_WILL_BE_FIRED_mimalloc_dump, .{ .name = callbackName("DO_NOT_USE_OR_YOU_WILL_BE_FIRED_mimalloc_dump") });
@export(BunObject._Os, .{ .name = callbackName("_Os") });
@export(BunObject._Path, .{ .name = callbackName("_Path") });
@export(BunObject.allocUnsafe, .{ .name = callbackName("allocUnsafe") });
@export(BunObject.build, .{ .name = callbackName("build") });
@export(BunObject.connect, .{ .name = callbackName("connect") });
@export(BunObject.deflateSync, .{ .name = callbackName("deflateSync") });
@export(BunObject.file, .{ .name = callbackName("file") });
@export(BunObject.fs, .{ .name = callbackName("fs") });
@export(BunObject.gc, .{ .name = callbackName("gc") });
@export(BunObject.generateHeapSnapshot, .{ .name = callbackName("generateHeapSnapshot") });
@export(BunObject.getImportedStyles, .{ .name = callbackName("getImportedStyles") });
@export(BunObject.gunzipSync, .{ .name = callbackName("gunzipSync") });
@export(BunObject.gzipSync, .{ .name = callbackName("gzipSync") });
@export(BunObject.indexOfLine, .{ .name = callbackName("indexOfLine") });
@export(BunObject.inflateSync, .{ .name = callbackName("inflateSync") });
@export(BunObject.jest, .{ .name = callbackName("jest") });
@export(BunObject.listen, .{ .name = callbackName("listen") });
@export(BunObject.mmap, .{ .name = callbackName("mmap") });
@export(BunObject.nanoseconds, .{ .name = callbackName("nanoseconds") });
@export(BunObject.openInEditor, .{ .name = callbackName("openInEditor") });
@export(BunObject.registerMacro, .{ .name = callbackName("registerMacro") });
@export(BunObject.resolve, .{ .name = callbackName("resolve") });
@export(BunObject.resolveSync, .{ .name = callbackName("resolveSync") });
@export(BunObject.serve, .{ .name = callbackName("serve") });
@export(BunObject.sha, .{ .name = callbackName("sha") });
@export(BunObject.shrink, .{ .name = callbackName("shrink") });
@export(BunObject.sleepSync, .{ .name = callbackName("sleepSync") });
@export(BunObject.spawn, .{ .name = callbackName("spawn") });
@export(BunObject.spawnSync, .{ .name = callbackName("spawnSync") });
@export(BunObject.which, .{ .name = callbackName("which") });
@export(BunObject.write, .{ .name = callbackName("write") });
// -- Callbacks --
}
};
const Bun = @This();
const default_allocator = @import("root").bun.default_allocator;
const bun = @import("root").bun;
const Environment = bun.Environment;
const NetworkThread = @import("root").bun.HTTP.NetworkThread;
const Global = bun.Global;
const strings = bun.strings;
const string = bun.string;
const Output = @import("root").bun.Output;
const MutableString = @import("root").bun.MutableString;
const std = @import("std");
const Allocator = std.mem.Allocator;
const IdentityContext = @import("../../identity_context.zig").IdentityContext;
const Fs = @import("../../fs.zig");
const Resolver = @import("../../resolver/resolver.zig");
const ast = @import("../../import_record.zig");
const MacroEntryPoint = bun.bundler.MacroEntryPoint;
const logger = @import("root").bun.logger;
const Api = @import("../../api/schema.zig").Api;
const options = @import("../../options.zig");
const Bundler = bun.Bundler;
const ServerEntryPoint = bun.bundler.ServerEntryPoint;
const js_printer = bun.js_printer;
const js_parser = bun.js_parser;
const js_ast = bun.JSAst;
const http = @import("../../bun_dev_http_server.zig");
const NodeFallbackModules = @import("../../node_fallbacks.zig");
const ImportKind = ast.ImportKind;
const Analytics = @import("../../analytics/analytics_thread.zig");
const ZigString = @import("root").bun.JSC.ZigString;
const Runtime = @import("../../runtime.zig");
const Router = @import("./filesystem_router.zig");
const ImportRecord = ast.ImportRecord;
const DotEnv = @import("../../env_loader.zig");
const ParseResult = bun.bundler.ParseResult;
const PackageJSON = @import("../../resolver/package_json.zig").PackageJSON;
const MacroRemap = @import("../../resolver/package_json.zig").MacroMap;
const WebCore = @import("root").bun.JSC.WebCore;
const Request = WebCore.Request;
const Response = WebCore.Response;
const Headers = WebCore.Headers;
const Fetch = WebCore.Fetch;
const js = @import("root").bun.JSC.C;
const JSC = @import("root").bun.JSC;
const JSError = @import("../base.zig").JSError;
const MarkedArrayBuffer = @import("../base.zig").MarkedArrayBuffer;
const getAllocator = @import("../base.zig").getAllocator;
const JSValue = @import("root").bun.JSC.JSValue;
const JSGlobalObject = @import("root").bun.JSC.JSGlobalObject;
const ExceptionValueRef = @import("root").bun.JSC.ExceptionValueRef;
const JSPrivateDataPtr = @import("root").bun.JSC.JSPrivateDataPtr;
const ZigConsoleClient = @import("root").bun.JSC.ZigConsoleClient;
const Node = @import("root").bun.JSC.Node;
const ZigException = @import("root").bun.JSC.ZigException;
const ZigStackTrace = @import("root").bun.JSC.ZigStackTrace;
const ErrorableResolvedSource = @import("root").bun.JSC.ErrorableResolvedSource;
const ResolvedSource = @import("root").bun.JSC.ResolvedSource;
const JSPromise = @import("root").bun.JSC.JSPromise;
const JSInternalPromise = @import("root").bun.JSC.JSInternalPromise;
const JSModuleLoader = @import("root").bun.JSC.JSModuleLoader;
const JSPromiseRejectionOperation = @import("root").bun.JSC.JSPromiseRejectionOperation;
const Exception = @import("root").bun.JSC.Exception;
const ErrorableZigString = @import("root").bun.JSC.ErrorableZigString;
const ZigGlobalObject = @import("root").bun.JSC.ZigGlobalObject;
const VM = @import("root").bun.JSC.VM;
const JSFunction = @import("root").bun.JSC.JSFunction;
const Config = @import("../config.zig");
const URL = @import("../../url.zig").URL;
const Transpiler = bun.JSC.API.JSTranspiler;
const JSBundler = bun.JSC.API.JSBundler;
const VirtualMachine = JSC.VirtualMachine;
const IOTask = JSC.IOTask;
const zlib = @import("../../zlib.zig");
const Which = @import("../../which.zig");
const ErrorableString = JSC.ErrorableString;
const is_bindgen = JSC.is_bindgen;
const max_addressible_memory = std.math.maxInt(u56);
const Async = bun.Async;
const SemverObject = @import("../../install/semver.zig").SemverObject;
threadlocal var css_imports_list_strings: [512]ZigString = undefined;
threadlocal var css_imports_list: [512]Api.StringPointer = undefined;
threadlocal var css_imports_list_tail: u16 = 0;
threadlocal var css_imports_buf: std.ArrayList(u8) = undefined;
threadlocal var css_imports_buf_loaded: bool = false;
threadlocal var routes_list_strings: [1024]ZigString = undefined;
pub fn onImportCSS(
resolve_result: *const Resolver.Result,
import_record: *ImportRecord,
origin: URL,
) void {
if (!css_imports_buf_loaded) {
css_imports_buf = std.ArrayList(u8).initCapacity(
VirtualMachine.get().allocator,
import_record.path.text.len,
) catch unreachable;
css_imports_buf_loaded = true;
}
var writer = css_imports_buf.writer();
const offset = css_imports_buf.items.len;
css_imports_list[css_imports_list_tail] = .{
.offset = @as(u32, @truncate(offset)),
.length = 0,
};
getPublicPath(resolve_result.path_pair.primary.text, origin, @TypeOf(writer), writer);
const length = css_imports_buf.items.len - offset;
css_imports_list[css_imports_list_tail].length = @as(u32, @truncate(length));
css_imports_list_tail += 1;
}
pub fn flushCSSImports() void {
if (css_imports_buf_loaded) {
css_imports_buf.clearRetainingCapacity();
css_imports_list_tail = 0;
}
}
pub fn getCSSImports() []ZigString {
var i: u16 = 0;
const tail = css_imports_list_tail;
while (i < tail) : (i += 1) {
ZigString.fromStringPointer(css_imports_list[i], css_imports_buf.items, &css_imports_list_strings[i]);
}
return css_imports_list_strings[0..tail];
}
pub fn which(
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const arguments_ = callframe.arguments(2);
var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
var arguments = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments_.slice());
defer arguments.deinit();
const path_arg = arguments.nextEat() orelse {
globalThis.throw("which: expected 1 argument, got 0", .{});
return JSC.JSValue.jsUndefined();
};
var path_str: ZigString.Slice = ZigString.Slice.empty;
var bin_str: ZigString.Slice = ZigString.Slice.empty;
var cwd_str: ZigString.Slice = ZigString.Slice.empty;
defer {
path_str.deinit();
bin_str.deinit();
cwd_str.deinit();
}
if (path_arg.isEmptyOrUndefinedOrNull()) {
return JSC.JSValue.jsNull();
}
bin_str = path_arg.toSlice(globalThis, globalThis.bunVM().allocator);
if (bin_str.len >= bun.MAX_PATH_BYTES) {
globalThis.throw("bin path is too long", .{});
return JSC.JSValue.jsUndefined();
}
if (bin_str.len == 0) {
return JSC.JSValue.jsNull();
}
path_str = ZigString.Slice.fromUTF8NeverFree(
globalThis.bunVM().bundler.env.map.get("PATH") orelse "",
);
cwd_str = ZigString.Slice.fromUTF8NeverFree(
globalThis.bunVM().bundler.fs.top_level_dir,
);
if (arguments.nextEat()) |arg| {
if (!arg.isEmptyOrUndefinedOrNull() and arg.isObject()) {
if (arg.get(globalThis, "PATH")) |str_| {
path_str = str_.toSlice(globalThis, globalThis.bunVM().allocator);
}
if (arg.get(globalThis, "cwd")) |str_| {
cwd_str = str_.toSlice(globalThis, globalThis.bunVM().allocator);
}
}
}
if (Which.which(
&path_buf,
path_str.slice(),
cwd_str.slice(),
bin_str.slice(),
)) |bin_path| {
return ZigString.init(bin_path).withEncoding().toValueGC(globalThis);
}
return JSC.JSValue.jsNull();
}
pub fn inspect(
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const arguments = callframe.arguments(4).slice();
if (arguments.len == 0)
return bun.String.empty.toJSConst(globalThis);
for (arguments) |arg| {
arg.protect();
}
defer {
for (arguments) |arg| {
arg.unprotect();
}
}
var formatOptions = ZigConsoleClient.FormatOptions{
.enable_colors = false,
.add_newline = false,
.flush = false,
.max_depth = 8,
.quote_strings = true,
.ordered_properties = false,
};
const value = arguments[0];
if (arguments.len > 1) {
const arg1 = arguments[1];
if (arg1.isObject()) {
if (arg1.getTruthy(globalThis, "depth")) |opt| {
if (opt.isInt32()) {
const arg = opt.toInt32();
if (arg < 0) {
globalThis.throwInvalidArguments("expected depth to be greater than or equal to 0, got {d}", .{arg});
return .zero;
}
formatOptions.max_depth = @as(u16, @truncate(@as(u32, @intCast(@min(arg, std.math.maxInt(u16))))));
} else if (opt.isNumber()) {
const v = opt.asDouble();
if (std.math.isInf(v)) {
formatOptions.max_depth = std.math.maxInt(u16);
} else {
globalThis.throwInvalidArguments("expected depth to be an integer, got {d}", .{v});
return .zero;
}
}
}
if (arg1.getOptional(globalThis, "colors", bool) catch return .zero) |opt| {
formatOptions.enable_colors = opt;
}
if (arg1.getOptional(globalThis, "sorted", bool) catch return .zero) |opt| {
formatOptions.ordered_properties = opt;
}
} else {
// formatOptions.show_hidden = arg1.toBoolean();
if (arguments.len > 2) {
var depthArg = arguments[1];
if (depthArg.isInt32()) {
const arg = depthArg.toInt32();
if (arg < 0) {
globalThis.throwInvalidArguments("expected depth to be greater than or equal to 0, got {d}", .{arg});
return .zero;
}
formatOptions.max_depth = @as(u16, @truncate(@as(u32, @intCast(@min(arg, std.math.maxInt(u16))))));
} else if (depthArg.isNumber()) {
const v = depthArg.asDouble();
if (std.math.isInf(v)) {
formatOptions.max_depth = std.math.maxInt(u16);
} else {
globalThis.throwInvalidArguments("expected depth to be an integer, got {d}", .{v});
return .zero;
}
}
if (arguments.len > 3) {
formatOptions.enable_colors = arguments[2].toBoolean();
}
}
}
}
// very stable memory address
var array = MutableString.init(getAllocator(globalThis), 0) catch unreachable;
var buffered_writer_ = MutableString.BufferedWriter{ .context = &array };
var buffered_writer = &buffered_writer_;
var writer = buffered_writer.writer();
const Writer = @TypeOf(writer);
// we buffer this because it'll almost always be < 4096
// when it's under 4096, we want to avoid the dynamic allocation
ZigConsoleClient.format(
.Debug,
globalThis,
@as([*]const JSValue, @ptrCast(&value)),
1,
Writer,
Writer,
writer,
formatOptions,
);
buffered_writer.flush() catch {
return .undefined;
};
// we are going to always clone to keep things simple for now
// the common case here will be stack-allocated, so it should be fine
var out = ZigString.init(array.toOwnedSliceLeaky()).withEncoding();
const ret = out.toValueGC(globalThis);
array.deinit();
return ret;
}
pub fn getInspect(globalObject: *JSC.JSGlobalObject, _: *JSC.JSObject) callconv(.C) JSC.JSValue {
const fun = JSC.createCallback(globalObject, ZigString.static("inspect"), 2, &inspect);
var str = ZigString.init("nodejs.util.inspect.custom");
fun.put(globalObject, ZigString.static("custom"), JSC.JSValue.symbolFor(globalObject, &str));
return fun;
}
pub fn registerMacro(
globalObject: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const arguments_ = callframe.arguments(2);
const arguments = arguments_.slice();
if (arguments.len != 2 or !arguments[0].isNumber()) {
globalObject.throwInvalidArguments("Internal error registering macros: invalid args", .{});
return .undefined;
}
const id = arguments[0].toInt32();
if (id == -1 or id == 0) {
globalObject.throwInvalidArguments("Internal error registering macros: invalid id", .{});
return .undefined;
}
if (!arguments[1].isCell() or !arguments[1].isCallable(globalObject.vm())) {
// TODO: add "toTypeOf" helper
globalObject.throw("Macro must be a function", .{});
return .undefined;
}
var get_or_put_result = VirtualMachine.get().macros.getOrPut(id) catch unreachable;
if (get_or_put_result.found_existing) {
get_or_put_result.value_ptr.*.?.value().unprotect();
}
arguments[1].protect();
get_or_put_result.value_ptr.* = arguments[1].asObjectRef();
return .undefined;
}
pub fn getCWD(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
return ZigString.init(VirtualMachine.get().bundler.fs.top_level_dir).toValueGC(globalThis);
}
pub fn getOrigin(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
return ZigString.init(VirtualMachine.get().origin.origin).toValueGC(globalThis);
}
pub fn getStdin(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
var rare_data = globalThis.bunVM().rareData();
var store = rare_data.stdin();
store.ref();
var blob = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable;
blob.* = JSC.WebCore.Blob.initWithStore(store, globalThis);
return blob.toJS(globalThis);
}
pub fn getStderr(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
var rare_data = globalThis.bunVM().rareData();
var store = rare_data.stderr();
store.ref();
var blob = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable;
blob.* = JSC.WebCore.Blob.initWithStore(store, globalThis);
return blob.toJS(globalThis);
}
pub fn getStdout(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
var rare_data = globalThis.bunVM().rareData();
var store = rare_data.stdout();
store.ref();
var blob = bun.default_allocator.create(JSC.WebCore.Blob) catch unreachable;
blob.* = JSC.WebCore.Blob.initWithStore(store, globalThis);
return blob.toJS(globalThis);
}
pub fn enableANSIColors(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
_ = globalThis;
return JSValue.jsBoolean(Output.enable_ansi_colors);
}
pub fn getMain(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
return ZigString.init(globalThis.bunVM().main).toValueGC(globalThis);
}
pub fn getAssetPrefix(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
return ZigString.init(VirtualMachine.get().bundler.options.routes.asset_prefix_path).toValueGC(globalThis);
}
pub fn getArgv(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
return JSC.Node.Process.getArgv(globalThis);
}
const Editor = @import("../../open.zig").Editor;
pub fn openInEditor(
globalThis: js.JSContextRef,
callframe: *JSC.CallFrame,
) callconv(.C) JSValue {
var edit = &VirtualMachine.get().rareData().editor_context;
const args = callframe.arguments(4);
var arguments = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), args.slice());
defer arguments.deinit();
var path: string = "";
var editor_choice: ?Editor = null;
var line: ?string = null;
var column: ?string = null;
if (arguments.nextEat()) |file_path_| {
path = file_path_.toSlice(globalThis, arguments.arena.allocator()).slice();
}
if (arguments.nextEat()) |opts| {
if (!opts.isUndefinedOrNull()) {
if (opts.getTruthy(globalThis, "editor")) |editor_val| {
var sliced = editor_val.toSlice(globalThis, arguments.arena.allocator());
var prev_name = edit.name;
if (!strings.eqlLong(prev_name, sliced.slice(), true)) {
var prev = edit.*;
edit.name = sliced.slice();
edit.detectEditor(VirtualMachine.get().bundler.env);
editor_choice = edit.editor;
if (editor_choice == null) {
edit.* = prev;
globalThis.throw("Could not find editor \"{s}\"", .{sliced.slice()});
return .undefined;
} else if (edit.name.ptr == edit.path.ptr) {
edit.name = arguments.arena.allocator().dupe(u8, edit.path) catch unreachable;
edit.path = edit.path;
}
}
}
if (opts.getTruthy(globalThis, "line")) |line_| {
line = line_.toSlice(globalThis, arguments.arena.allocator()).slice();
}
if (opts.getTruthy(globalThis, "column")) |column_| {
column = column_.toSlice(globalThis, arguments.arena.allocator()).slice();
}
}
}
const editor = editor_choice orelse edit.editor orelse brk: {
edit.autoDetectEditor(VirtualMachine.get().bundler.env);
if (edit.editor == null) {
globalThis.throw("Failed to auto-detect editor", .{});
return .zero;
}
break :brk edit.editor.?;
};
if (path.len == 0) {
globalThis.throw("No file path specified", .{});
return .zero;
}
editor.open(edit.path, path, line, column, arguments.arena.allocator()) catch |err| {
globalThis.throw("Opening editor failed {s}", .{@errorName(err)});
return .zero;
};
return JSC.JSValue.jsUndefined();
}
pub fn getPublicPath(to: string, origin: URL, comptime Writer: type, writer: Writer) void {
return getPublicPathWithAssetPrefix(to, VirtualMachine.get().bundler.fs.top_level_dir, origin, VirtualMachine.get().bundler.options.routes.asset_prefix_path, comptime Writer, writer);
}
pub fn getPublicPathWithAssetPrefix(to: string, dir: string, origin: URL, asset_prefix: string, comptime Writer: type, writer: Writer) void {
const relative_path = if (strings.hasPrefix(to, dir))
strings.withoutTrailingSlash(to[dir.len..])
else
VirtualMachine.get().bundler.fs.relative(dir, to);
if (origin.isAbsolute()) {
if (strings.hasPrefix(relative_path, "..") or strings.hasPrefix(relative_path, "./")) {
writer.writeAll(origin.origin) catch return;
writer.writeAll("/abs:") catch return;
if (std.fs.path.isAbsolute(to)) {
writer.writeAll(to) catch return;
} else {
writer.writeAll(VirtualMachine.get().bundler.fs.abs(&[_]string{to})) catch return;
}
} else {
origin.joinWrite(
Writer,
writer,
asset_prefix,
"",
relative_path,
"",
) catch return;
}
} else {
writer.writeAll(std.mem.trimLeft(u8, relative_path, "/")) catch unreachable;
}
}
pub fn sleepSync(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
const arguments = callframe.arguments(1);
// Expect at least one argument. We allow more than one but ignore them; this
// is useful for supporting things like `[1, 2].map(sleepSync)`
if (arguments.len < 1) {
globalObject.throwNotEnoughArguments("sleepSync", 1, 0);
return .undefined;
}
const arg = arguments.slice()[0];
// The argument must be a number
if (!arg.isNumber()) {
globalObject.throwInvalidArgumentType("sleepSync", "milliseconds", "number");
return .undefined;
}
//NOTE: if argument is > max(i32) then it will be truncated
const milliseconds = arg.coerce(i32, globalObject);
if (milliseconds < 0) {
globalObject.throwInvalidArguments("argument to sleepSync must not be negative, got {d}", .{milliseconds});
return .undefined;
}
std.time.sleep(@as(u64, @intCast(milliseconds)) * std.time.ns_per_ms);
return .undefined;
}
pub fn generateHeapSnapshot(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
return globalObject.generateHeapSnapshot();
}
pub fn runGC(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
const arguments_ = callframe.arguments(1);
const arguments = arguments_.slice();
return globalObject.bunVM().garbageCollect(arguments.len > 0 and arguments[0].isBoolean() and arguments[0].toBoolean());
}
pub fn shrink(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
globalObject.vm().shrinkFootprint();
return .undefined;
}
fn doResolve(
globalThis: *JSC.JSGlobalObject,
arguments: []const JSValue,
exception: js.ExceptionRef,
) ?JSC.JSValue {
var args = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments);
defer args.deinit();
const specifier = args.protectEatNext() orelse {
JSC.throwInvalidArguments("Expected a specifier and a from path", .{}, globalThis, exception);
return null;
};
if (specifier.isUndefinedOrNull()) {
JSC.throwInvalidArguments("specifier must be a string", .{}, globalThis, exception);
return null;
}
const from = args.protectEatNext() orelse {
JSC.throwInvalidArguments("Expected a from path", .{}, globalThis, exception);
return null;
};
if (from.isUndefinedOrNull()) {
JSC.throwInvalidArguments("from must be a string", .{}, globalThis, exception);
return null;
}
var is_esm = true;
if (args.nextEat()) |next| {
if (next.isBoolean()) {
is_esm = next.toBoolean();
} else {
JSC.throwInvalidArguments("esm must be a boolean", .{}, globalThis, exception);
return null;
}
}
return doResolveWithArgs(globalThis, specifier.toBunString(globalThis), from.toBunString(globalThis), exception, is_esm, false);
}
fn doResolveWithArgs(
ctx: js.JSContextRef,
specifier: bun.String,
from: bun.String,
exception: js.ExceptionRef,
is_esm: bool,
comptime is_file_path: bool,
) ?JSC.JSValue {
var errorable: ErrorableString = undefined;
var query_string = ZigString.Empty;
const specifier_decoded = if (specifier.hasPrefixComptime("file://"))
bun.JSC.URL.pathFromFileURL(specifier)
else
specifier.dupeRef();
defer specifier_decoded.deref();
if (comptime is_file_path) {
VirtualMachine.resolveFilePathForAPI(
&errorable,
ctx.ptr(),
specifier_decoded,
from,
&query_string,
is_esm,
);
} else {
VirtualMachine.resolveForAPI(
&errorable,
ctx.ptr(),
specifier_decoded,
from,
&query_string,
is_esm,
);
}
if (!errorable.success) {
exception.* = bun.cast(JSC.JSValueRef, errorable.result.err.ptr.?);
return null;
}
if (query_string.len > 0) {
var stack = std.heap.stackFallback(1024, ctx.allocator());
const allocator = stack.get();
var arraylist = std.ArrayList(u8).initCapacity(allocator, 1024) catch unreachable;
defer arraylist.deinit();
arraylist.writer().print("{any}{any}", .{
errorable.result.value,
query_string,
}) catch {
JSC.JSError(allocator, "Failed to allocate memory", .{}, ctx, exception);
return null;
};
return ZigString.initUTF8(arraylist.items).toValueGC(ctx);
}
return errorable.result.value.toJS(ctx);
}
pub fn resolveSync(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
var exception_ = [1]JSC.JSValueRef{null};
var exception = &exception_;
const arguments = callframe.arguments(3);
const result = doResolve(globalObject, arguments.slice(), exception);
if (exception_[0] != null) {
globalObject.throwValue(exception_[0].?.value());
}
return result orelse .zero;
}
pub fn resolve(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
var exception_ = [1]JSC.JSValueRef{null};
var exception = &exception_;
const arguments = callframe.arguments(3);
const value = doResolve(globalObject, arguments.slice(), exception) orelse {
return JSC.JSPromise.rejectedPromiseValue(globalObject, exception_[0].?.value());
};
return JSC.JSPromise.resolvedPromiseValue(globalObject, value);
}
export fn Bun__resolve(
global: *JSGlobalObject,
specifier: JSValue,
source: JSValue,
is_esm: bool,
) JSC.JSValue {
var exception_ = [1]JSC.JSValueRef{null};
var exception = &exception_;
const value = doResolveWithArgs(global, specifier.toBunString(global), source.toBunString(global), exception, is_esm, true) orelse {
return JSC.JSPromise.rejectedPromiseValue(global, exception_[0].?.value());
};
return JSC.JSPromise.resolvedPromiseValue(global, value);
}
export fn Bun__resolveSync(
global: *JSGlobalObject,
specifier: JSValue,
source: JSValue,
is_esm: bool,
) JSC.JSValue {
var exception_ = [1]JSC.JSValueRef{null};
var exception = &exception_;
return doResolveWithArgs(global, specifier.toBunString(global), source.toBunString(global), exception, is_esm, true) orelse {
return JSC.JSValue.fromRef(exception[0]);
};
}
export fn Bun__resolveSyncWithSource(
global: *JSGlobalObject,
specifier: JSValue,
source: *bun.String,
is_esm: bool,
) JSC.JSValue {
var exception_ = [1]JSC.JSValueRef{null};
var exception = &exception_;
return doResolveWithArgs(global, specifier.toBunString(global), source.*, exception, is_esm, true) orelse {
return JSC.JSValue.fromRef(exception[0]);
};
}
comptime {
if (!is_bindgen) {
_ = Bun__resolve;
_ = Bun__resolveSync;
_ = Bun__resolveSyncWithSource;
}
}
pub fn getPublicPathJS(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
const arguments = callframe.arguments(1).slice();
if (arguments.len < 1) {
return bun.String.empty.toJSConst(globalObject);
}
var public_path_temp_str: [bun.MAX_PATH_BYTES]u8 = undefined;
const to = arguments[0].toSlice(globalObject, bun.default_allocator);
defer to.deinit();
var stream = std.io.fixedBufferStream(&public_path_temp_str);
var writer = stream.writer();
getPublicPath(to.slice(), VirtualMachine.get().origin, @TypeOf(&writer), &writer);
return ZigString.init(stream.buffer[0..stream.pos]).toValueGC(globalObject);
}
fn fs(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
var module = globalObject.allocator().create(JSC.Node.NodeJSFS) catch unreachable;
module.* = .{};
var vm = globalObject.bunVM();
if (vm.standalone_module_graph != null)
module.node_fs.vm = vm;
return module.toJS(globalObject);
}
fn _Os(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
return Node.Os.create(globalObject);
}
fn _Path(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
const arguments = callframe.arguments(1);
const args = arguments.slice();
const is_windows = args.len == 1 and args[0].toBoolean();
return Node.Path.create(globalObject, is_windows);
}
/// @deprecated
fn getImportedStyles(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
defer flushCSSImports();
const styles = getCSSImports();
if (styles.len == 0) {
return JSC.JSValue.createEmptyArray(globalObject, 0);
}
return JSValue.createStringArray(globalObject, styles.ptr, styles.len, true);
}
pub fn dump_mimalloc(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSC.JSValue {
globalObject.bunVM().arena.dumpStats();
return .undefined;
}
pub fn indexOfLine(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
const arguments_ = callframe.arguments(2);
const arguments = arguments_.slice();
if (arguments.len == 0) {
return JSC.JSValue.jsNumberFromInt32(-1);
}
var buffer = arguments[0].asArrayBuffer(globalThis) orelse {
return JSC.JSValue.jsNumberFromInt32(-1);
};
var offset: usize = 0;
if (arguments.len > 1) {
offset = @as(
usize,
@intCast(@max(
arguments[1].to(u32),
0,
)),
);
}
const bytes = buffer.byteSlice();
var current_offset = offset;
const end = @as(u32, @truncate(bytes.len));
while (current_offset < end) {
if (strings.indexOfNewlineOrNonASCII(bytes, @as(u32, @truncate(current_offset)))) |i| {
const byte = bytes[i];
if (byte > 0x7F) {
current_offset += @max(strings.wtf8ByteSequenceLength(byte), 1);
continue;
}
if (byte == '\r') {
if (i + 1 < bytes.len and bytes[i + 1] == '\n') {
return JSC.JSValue.jsNumber(i + 1);
}
} else if (byte == '\n') {
return JSC.JSValue.jsNumber(i);
}
current_offset = i + 1;
} else {
break;
}
}
return JSC.JSValue.jsNumberFromInt32(-1);
}
pub const Crypto = struct {
const Hashers = @import("../../sha.zig");
const BoringSSL = bun.BoringSSL;
const EVP = struct {
ctx: BoringSSL.EVP_MD_CTX = undefined,
md: *const BoringSSL.EVP_MD = undefined,
algorithm: Algorithm,
// we do this to avoid asking BoringSSL what the digest name is
// because that API is confusing
pub const Algorithm = enum {
// @"DSA-SHA",
// @"DSA-SHA1",
// @"MD5-SHA1",
// @"RSA-MD5",
// @"RSA-RIPEMD160",
// @"RSA-SHA1",
// @"RSA-SHA1-2",
// @"RSA-SHA224",
// @"RSA-SHA256",
// @"RSA-SHA384",
// @"RSA-SHA512",
// @"ecdsa-with-SHA1",
blake2b256,
md4,
md5,
ripemd160,
sha1,
sha224,
sha256,
sha384,
sha512,
@"sha512-256",
pub const names: std.EnumArray(Algorithm, ZigString) = brk: {
var all = std.EnumArray(Algorithm, ZigString).initUndefined();
var iter = all.iterator();
while (iter.next()) |entry| {
entry.value.* = ZigString.init(@tagName(entry.key));
}
break :brk all;
};
pub const map = bun.ComptimeStringMap(Algorithm, .{
.{ "blake2b256", .blake2b256 },
.{ "ripemd160", .ripemd160 },
.{ "rmd160", .ripemd160 },
.{ "md4", .md4 },
.{ "md5", .md5 },
.{ "sha1", .sha1 },
.{ "sha128", .sha1 },
.{ "sha224", .sha224 },
.{ "sha256", .sha256 },
.{ "sha384", .sha384 },
.{ "sha512", .sha512 },
.{ "sha-1", .sha1 },
.{ "sha-224", .sha224 },
.{ "sha-256", .sha256 },
.{ "sha-384", .sha384 },
.{ "sha-512", .sha512 },
.{ "sha-512/256", .@"sha512-256" },
.{ "sha-512_256", .@"sha512-256" },
.{ "sha-512256", .@"sha512-256" },
.{ "sha512-256", .@"sha512-256" },
.{ "sha384", .sha384 },
// .{ "md5-sha1", .@"MD5-SHA1" },
// .{ "dsa-sha", .@"DSA-SHA" },
// .{ "dsa-sha1", .@"DSA-SHA1" },
// .{ "ecdsa-with-sha1", .@"ecdsa-with-SHA1" },
// .{ "rsa-md5", .@"RSA-MD5" },
// .{ "rsa-sha1", .@"RSA-SHA1" },
// .{ "rsa-sha1-2", .@"RSA-SHA1-2" },
// .{ "rsa-sha224", .@"RSA-SHA224" },
// .{ "rsa-sha256", .@"RSA-SHA256" },
// .{ "rsa-sha384", .@"RSA-SHA384" },
// .{ "rsa-sha512", .@"RSA-SHA512" },
// .{ "rsa-ripemd160", .@"RSA-RIPEMD160" },
});
};
pub const Digest = [BoringSSL.EVP_MAX_MD_SIZE]u8;
pub fn init(algorithm: Algorithm, md: *const BoringSSL.EVP_MD, engine: *BoringSSL.ENGINE) EVP {
BoringSSL.load();
var ctx: BoringSSL.EVP_MD_CTX = undefined;
BoringSSL.EVP_MD_CTX_init(&ctx);
_ = BoringSSL.EVP_DigestInit_ex(&ctx, md, engine);
return .{
.ctx = ctx,
.md = md,
.algorithm = algorithm,
};
}
pub fn reset(this: *EVP, engine: *BoringSSL.ENGINE) void {
BoringSSL.ERR_clear_error();
_ = BoringSSL.EVP_DigestInit_ex(&this.ctx, this.md, engine);
}
pub fn hash(this: *EVP, engine: *BoringSSL.ENGINE, input: []const u8, output: []u8) ?u32 {
BoringSSL.ERR_clear_error();
var outsize: c_uint = @min(@as(u16, @truncate(output.len)), this.size());
if (BoringSSL.EVP_Digest(input.ptr, input.len, output.ptr, &outsize, this.md, engine) != 1) {
return null;
}
return outsize;
}
pub fn final(this: *EVP, engine: *BoringSSL.ENGINE, output: []u8) []const u8 {
BoringSSL.ERR_clear_error();
var outsize: u32 = @min(@as(u16, @truncate(output.len)), this.size());
if (BoringSSL.EVP_DigestFinal_ex(
&this.ctx,
output.ptr,
&outsize,
) != 1) {
return "";
}
this.reset(engine);
return output[0..outsize];
}
pub fn update(this: *EVP, input: []const u8) void {
BoringSSL.ERR_clear_error();
_ = BoringSSL.EVP_DigestUpdate(&this.ctx, input.ptr, input.len);
}
pub fn size(this: *EVP) u16 {
return @as(u16, @truncate(BoringSSL.EVP_MD_CTX_size(&this.ctx)));
}
pub fn copy(this: *const EVP, engine: *BoringSSL.ENGINE) error{OutOfMemory}!EVP {
BoringSSL.ERR_clear_error();
var new = init(this.algorithm, this.md, engine);
if (BoringSSL.EVP_MD_CTX_copy_ex(&new.ctx, &this.ctx) == 0) {
return error.OutOfMemory;
}
return new;
}
pub fn byNameAndEngine(engine: *BoringSSL.ENGINE, name: []const u8) ?EVP {
if (Algorithm.map.getWithEql(name, strings.eqlCaseInsensitiveASCIIIgnoreLength)) |algorithm| {
if (algorithm == .blake2b256) {
return EVP.init(algorithm, BoringSSL.EVP_blake2b256(), engine);
}
switch (algorithm) {
.md4 => return EVP.init(algorithm, BoringSSL.EVP_md4(), engine),
.md5 => return EVP.init(algorithm, BoringSSL.EVP_md5(), engine),
.sha1 => return EVP.init(algorithm, BoringSSL.EVP_sha1(), engine),
.sha224 => return EVP.init(algorithm, BoringSSL.EVP_sha224(), engine),
.sha256 => return EVP.init(algorithm, BoringSSL.EVP_sha256(), engine),
.sha384 => return EVP.init(algorithm, BoringSSL.EVP_sha384(), engine),
.sha512 => return EVP.init(algorithm, BoringSSL.EVP_sha512(), engine),
.@"sha512-256" => return EVP.init(algorithm, BoringSSL.EVP_sha512_256(), engine),
else => {
if (BoringSSL.EVP_get_digestbyname(@tagName(algorithm))) |md|
return EVP.init(algorithm, md, engine);
},
}
}
return null;
}
pub fn byName(name: ZigString, global: *JSC.JSGlobalObject) ?EVP {
var name_str = name.toSlice(global.allocator());
defer name_str.deinit();
return byNameAndEngine(global.bunVM().rareData().boringEngine(), name_str.slice());
}
pub fn deinit(this: *EVP) void {
// https://github.com/oven-sh/bun/issues/3250
_ = BoringSSL.EVP_MD_CTX_cleanup(&this.ctx);
}
};
fn createCryptoError(globalThis: *JSC.JSGlobalObject, err_code: u32) JSValue {
var outbuf: [128 + 1 + "BoringSSL error: ".len]u8 = undefined;
@memset(&outbuf, 0);
outbuf[0.."BoringSSL error: ".len].* = "BoringSSL error: ".*;
var message_buf = outbuf["BoringSSL error: ".len..];
_ = BoringSSL.ERR_error_string_n(err_code, message_buf, message_buf.len);
const error_message: []const u8 = bun.sliceTo(outbuf[0..], 0);
if (error_message.len == "BoringSSL error: ".len) {
return ZigString.static("Unknown BoringSSL error").toErrorInstance(globalThis);
}
return ZigString.fromUTF8(error_message).toErrorInstance(globalThis);
}
const unknown_password_algorithm_message = "unknown algorithm, expected one of: \"bcrypt\", \"argon2id\", \"argon2d\", \"argon2i\" (default is \"argon2id\")";
pub const PasswordObject = struct {
pub const pwhash = std.crypto.pwhash;
pub const Algorithm = enum {
argon2i,
argon2d,
argon2id,
bcrypt,
pub const Value = union(Algorithm) {
argon2i: Argon2Params,
argon2d: Argon2Params,
argon2id: Argon2Params,
// bcrypt only accepts "cost"
bcrypt: u6,
pub const bcrpyt_default = 10;
pub const default = Algorithm.Value{
.argon2id = .{},
};
pub fn fromJS(globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) ?Value {
if (value.isObject()) {
if (value.getTruthy(globalObject, "algorithm")) |algorithm_value| {
if (!algorithm_value.isString()) {
globalObject.throwInvalidArgumentType("hash", "algorithm", "string");
return null;
}
const algorithm_string = algorithm_value.getZigString(globalObject);
switch (PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
globalObject.throwInvalidArgumentType("hash", "algorithm", unknown_password_algorithm_message);
return null;
}) {
.bcrypt => {
var algorithm = PasswordObject.Algorithm.Value{
.bcrypt = PasswordObject.Algorithm.Value.bcrpyt_default,
};
if (value.getTruthy(globalObject, "cost")) |rounds_value| {
if (!rounds_value.isNumber()) {
globalObject.throwInvalidArgumentType("hash", "cost", "number");
return null;
}
const rounds = rounds_value.coerce(i32, globalObject);
if (rounds < 4 or rounds > 31) {
globalObject.throwInvalidArguments("Rounds must be between 4 and 31", .{});
return null;
}
algorithm.bcrypt = @as(u6, @intCast(rounds));
}
return algorithm;
},
inline .argon2id, .argon2d, .argon2i => |tag| {
var argon = Algorithm.Argon2Params{};
if (value.getTruthy(globalObject, "timeCost")) |time_value| {
if (!time_value.isNumber()) {
globalObject.throwInvalidArgumentType("hash", "timeCost", "number");
return null;
}
const time_cost = time_value.coerce(i32, globalObject);
if (time_cost < 1) {
globalObject.throwInvalidArguments("Time cost must be greater than 0", .{});
return null;
}
argon.time_cost = @as(u32, @intCast(time_cost));
}
if (value.getTruthy(globalObject, "memoryCost")) |memory_value| {
if (!memory_value.isNumber()) {
globalObject.throwInvalidArgumentType("hash", "memoryCost", "number");
return null;
}
const memory_cost = memory_value.coerce(i32, globalObject);
if (memory_cost < 1) {
globalObject.throwInvalidArguments("Memory cost must be greater than 0", .{});
return null;
}
argon.memory_cost = @as(u32, @intCast(memory_cost));
}
return @unionInit(Algorithm.Value, @tagName(tag), argon);
},
}
unreachable;
} else {
globalObject.throwInvalidArgumentType("hash", "options.algorithm", "string");
return null;
}
} else if (value.isString()) {
const algorithm_string = value.getZigString(globalObject);
switch (PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
globalObject.throwInvalidArgumentType("hash", "algorithm", unknown_password_algorithm_message);
return null;
}) {
.bcrypt => {
return PasswordObject.Algorithm.Value{
.bcrypt = PasswordObject.Algorithm.Value.bcrpyt_default,
};
},
.argon2id => {
return PasswordObject.Algorithm.Value{
.argon2id = .{},
};
},
.argon2d => {
return PasswordObject.Algorithm.Value{
.argon2d = .{},
};
},
.argon2i => {
return PasswordObject.Algorithm.Value{
.argon2i = .{},
};
},
}
} else {
globalObject.throwInvalidArgumentType("hash", "algorithm", "string");
return null;
}
unreachable;
}
};
pub const Argon2Params = struct {
// we don't support the other options right now, but can add them later if someone asks
memory_cost: u32 = pwhash.argon2.Params.interactive_2id.m,
time_cost: u32 = pwhash.argon2.Params.interactive_2id.t,
pub fn toParams(this: Argon2Params) pwhash.argon2.Params {
return pwhash.argon2.Params{
.t = this.time_cost,
.m = this.memory_cost,
.p = 1,
};
}
};
pub const argon2 = Algorithm.argon2id;
pub const label = bun.ComptimeStringMap(
Algorithm,
.{
.{ "argon2i", .argon2i },
.{ "argon2d", .argon2d },
.{ "argon2id", .argon2id },
.{ "bcrypt", .bcrypt },
},
);
pub const default = Algorithm.argon2;
pub fn get(pw: []const u8) ?Algorithm {
if (pw[0] != '$') {
return null;
}
// PHC format looks like $<algorithm>$<params>$<salt>$<hash><optional stuff>
if (strings.hasPrefixComptime(pw[1..], "argon2d$")) {
return .argon2d;
}
if (strings.hasPrefixComptime(pw[1..], "argon2i$")) {
return .argon2i;
}
if (strings.hasPrefixComptime(pw[1..], "argon2id$")) {
return .argon2id;
}
if (strings.hasPrefixComptime(pw[1..], "bcrypt")) {
return .bcrypt;
}
// https://en.wikipedia.org/wiki/Crypt_(C)
if (strings.hasPrefixComptime(pw[1..], "2")) {
return .bcrypt;
}
return null;
}
};
pub const HashError = pwhash.Error || error{UnsupportedAlgorithm};
// This is purposely simple because nobody asked to make it more complicated
pub fn hash(
allocator: std.mem.Allocator,
password: []const u8,
algorithm: Algorithm.Value,
) HashError![]const u8 {
switch (algorithm) {
inline .argon2i, .argon2d, .argon2id => |argon| {
var outbuf: [4096]u8 = undefined;
const hash_options = pwhash.argon2.HashOptions{
.params = argon.toParams(),
.allocator = allocator,
.mode = switch (algorithm) {
.argon2i => .argon2i,
.argon2d => .argon2d,
.argon2id => .argon2id,
else => unreachable,
},
.encoding = .phc,
};
// warning: argon2's code may spin up threads if paralellism is set to > 0
// we don't expose this option
// but since it parses from phc format, it's possible that it will be set
// eventually we should do something that about that.
const out_bytes = try pwhash.argon2.strHash(password, hash_options, &outbuf);
return try allocator.dupe(u8, out_bytes);
},
.bcrypt => |cost| {
var outbuf: [4096]u8 = undefined;
var outbuf_slice: []u8 = outbuf[0..];
var password_to_use = password;
// bcrypt silently truncates passwords longer than 72 bytes
// we use SHA512 to hash the password if it's longer than 72 bytes
if (password.len > 72) {
var sha_256 = bun.sha.SHA512.init();
defer sha_256.deinit();
sha_256.update(password);
sha_256.final(outbuf[0..bun.sha.SHA512.digest]);
password_to_use = outbuf[0..bun.sha.SHA512.digest];
outbuf_slice = outbuf[bun.sha.SHA512.digest..];
}
const hash_options = pwhash.bcrypt.HashOptions{
.params = pwhash.bcrypt.Params{ .rounds_log = cost },
.allocator = allocator,
.encoding = .crypt,
};
const out_bytes = try pwhash.bcrypt.strHash(password_to_use, hash_options, outbuf_slice);
return try allocator.dupe(u8, out_bytes);
},
}
}
pub fn verify(
allocator: std.mem.Allocator,
password: []const u8,
previous_hash: []const u8,
algorithm: ?Algorithm,
) HashError!bool {
if (previous_hash.len == 0) {
return false;
}
return verifyWithAlgorithm(
allocator,
password,
previous_hash,
algorithm orelse Algorithm.get(previous_hash) orelse return error.UnsupportedAlgorithm,
);
}
pub fn verifyWithAlgorithm(
allocator: std.mem.Allocator,
password: []const u8,
previous_hash: []const u8,
algorithm: Algorithm,
) HashError!bool {
switch (algorithm) {
.argon2id, .argon2d, .argon2i => {
pwhash.argon2.strVerify(previous_hash, password, .{ .allocator = allocator }) catch |err| {
if (err == error.PasswordVerificationFailed) {
return false;
}
return err;
};
return true;
},
.bcrypt => {
pwhash.bcrypt.strVerify(previous_hash, password, .{ .allocator = allocator }) catch |err| {
if (err == error.PasswordVerificationFailed) {
return false;
}
return err;
};
return true;
},
}
}
};
pub const JSPasswordObject = struct {
const PascalToUpperUnderscoreCaseFormatter = struct {
input: []const u8,
pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
for (self.input) |c| {
if (std.ascii.isUpper(c)) {
try writer.writeByte('_');
try writer.writeByte(c);
} else if (std.ascii.isLower(c)) {
try writer.writeByte(std.ascii.toUpper(c));
} else {
try writer.writeByte(c);
}
}
}
};
pub export fn JSPasswordObject__create(globalObject: *JSC.JSGlobalObject) JSC.JSValue {
var object = JSValue.createEmptyObject(globalObject, 4);
object.put(
globalObject,
ZigString.static("hash"),
JSC.createCallback(globalObject, ZigString.static("hash"), 2, JSPasswordObject__hash),
);
object.put(
globalObject,
ZigString.static("hashSync"),
JSC.createCallback(globalObject, ZigString.static("hashSync"), 2, JSPasswordObject__hashSync),
);
object.put(
globalObject,
ZigString.static("verify"),
JSC.createCallback(globalObject, ZigString.static("verify"), 2, JSPasswordObject__verify),
);
object.put(
globalObject,
ZigString.static("verifySync"),
JSC.createCallback(globalObject, ZigString.static("verifySync"), 2, JSPasswordObject__verifySync),
);
return object;
}
const HashJob = struct {
algorithm: PasswordObject.Algorithm.Value,
password: []const u8,
promise: JSC.JSPromise.Strong,
event_loop: *JSC.EventLoop,
global: *JSC.JSGlobalObject,
ref: Async.KeepAlive = .{},
task: JSC.WorkPoolTask = .{ .callback = &run },
pub const Result = struct {
value: Value,
ref: Async.KeepAlive = .{},
task: JSC.AnyTask = undefined,
promise: JSC.JSPromise.Strong,
global: *JSC.JSGlobalObject,
pub const Value = union(enum) {
err: PasswordObject.HashError,
hash: []const u8,
pub fn toErrorInstance(this: Value, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
var error_code = std.fmt.allocPrint(bun.default_allocator, "PASSWORD_{}", .{PascalToUpperUnderscoreCaseFormatter{ .input = @errorName(this.err) }}) catch @panic("out of memory");
defer bun.default_allocator.free(error_code);
const instance = globalObject.createErrorInstance("Password hashing failed with error \"{s}\"", .{@errorName(this.err)});
instance.put(globalObject, ZigString.static("code"), JSC.ZigString.init(error_code).toValueGC(globalObject));
return instance;
}
};
pub fn runFromJS(this: *Result) void {
var promise = this.promise;
this.promise = .{};
this.ref.unref(this.global.bunVM());
var global = this.global;
switch (this.value) {
.err => {
const error_instance = this.value.toErrorInstance(global);
bun.default_allocator.destroy(this);
promise.reject(global, error_instance);
},
.hash => |value| {
const js_string = JSC.ZigString.init(value).toValueGC(global);
bun.default_allocator.destroy(this);
promise.resolve(global, js_string);
},
}
}
};
pub fn deinit(this: *HashJob) void {
this.ref = .{};
this.promise.strong.deinit();
bun.default_allocator.free(this.password);
bun.default_allocator.destroy(this);
}
pub fn getValue(password: []const u8, algorithm: PasswordObject.Algorithm.Value) Result.Value {
const value = PasswordObject.hash(bun.default_allocator, password, algorithm) catch |err| {
return Result.Value{ .err = err };
};
return Result.Value{ .hash = value };
}
pub fn run(task: *bun.ThreadPool.Task) void {
var this = @fieldParentPtr(HashJob, "task", task);
var result = bun.default_allocator.create(Result) catch @panic("out of memory");
result.* = Result{
.value = getValue(this.password, this.algorithm),
.task = JSC.AnyTask.New(Result, Result.runFromJS).init(result),
.promise = this.promise,
.global = this.global,
.ref = this.ref,
};
this.ref = .{};
this.promise.strong = .{};
var concurrent_task = bun.default_allocator.create(JSC.ConcurrentTask) catch @panic("out of memory");
concurrent_task.* = JSC.ConcurrentTask{
.task = JSC.Task.init(&result.task),
.auto_delete = true,
};
this.event_loop.enqueueTaskConcurrent(concurrent_task);
this.deinit();
}
};
pub fn hash(
globalObject: *JSC.JSGlobalObject,
password: []const u8,
algorithm: PasswordObject.Algorithm.Value,
comptime sync: bool,
) JSC.JSValue {
std.debug.assert(password.len > 0); // caller must check
if (comptime sync) {
const value = HashJob.getValue(password, algorithm);
switch (value) {
.err => {
const error_instance = value.toErrorInstance(globalObject);
globalObject.throwValue(error_instance);
},
.hash => |h| {
return JSC.ZigString.init(h).toValueGC(globalObject);
},
}
unreachable;
}
var job = bun.default_allocator.create(HashJob) catch @panic("out of memory");
var promise = JSC.JSPromise.Strong.init(globalObject);
job.* = HashJob{
.algorithm = algorithm,
.password = password,
.promise = promise,
.event_loop = globalObject.bunVM().eventLoop(),
.global = globalObject,
};
job.ref.ref(globalObject.bunVM());
JSC.WorkPool.schedule(&job.task);
return promise.value();
}
pub fn verify(
globalObject: *JSC.JSGlobalObject,
password: []const u8,
prev_hash: []const u8,
algorithm: ?PasswordObject.Algorithm,
comptime sync: bool,
) JSC.JSValue {
std.debug.assert(password.len > 0); // caller must check
if (comptime sync) {
const value = VerifyJob.getValue(password, prev_hash, algorithm);
switch (value) {
.err => {
const error_instance = value.toErrorInstance(globalObject);
globalObject.throwValue(error_instance);
return JSC.JSValue.undefined;
},
.pass => |pass| {
return JSC.JSValue.jsBoolean(pass);
},
}
unreachable;
}
var job = bun.default_allocator.create(VerifyJob) catch @panic("out of memory");
var promise = JSC.JSPromise.Strong.init(globalObject);
job.* = VerifyJob{
.algorithm = algorithm,
.password = password,
.prev_hash = prev_hash,
.promise = promise,
.event_loop = globalObject.bunVM().eventLoop(),
.global = globalObject,
};
job.ref.ref(globalObject.bunVM());
JSC.WorkPool.schedule(&job.task);
return promise.value();
}
// Once we have bindings generator, this should be replaced with a generated function
pub export fn JSPasswordObject__hash(
globalObject: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const arguments_ = callframe.arguments(2);
const arguments = arguments_.ptr[0..arguments_.len];
if (arguments.len < 1) {
globalObject.throwNotEnoughArguments("hash", 1, 0);
return JSC.JSValue.undefined;
}
var algorithm = PasswordObject.Algorithm.Value.default;
if (arguments.len > 1 and !arguments[1].isEmptyOrUndefinedOrNull()) {
algorithm = PasswordObject.Algorithm.Value.fromJS(globalObject, arguments[1]) orelse
return JSC.JSValue.undefined;
}
var string_or_buffer = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray");
return JSC.JSValue.undefined;
};
if (string_or_buffer.slice().len == 0) {
globalObject.throwInvalidArguments("password must not be empty", .{});
string_or_buffer.deinit();
return JSC.JSValue.undefined;
}
string_or_buffer.ensureCloned(bun.default_allocator) catch {
globalObject.throwOutOfMemory();
return JSC.JSValue.undefined;
};
return hash(globalObject, string_or_buffer.slice(), algorithm, false);
}
// Once we have bindings generator, this should be replaced with a generated function
pub export fn JSPasswordObject__hashSync(
globalObject: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const arguments_ = callframe.arguments(2);
const arguments = arguments_.ptr[0..arguments_.len];
if (arguments.len < 1) {
globalObject.throwNotEnoughArguments("hash", 1, 0);
return JSC.JSValue.undefined;
}
var algorithm = PasswordObject.Algorithm.Value.default;
if (arguments.len > 1 and !arguments[1].isEmptyOrUndefinedOrNull()) {
algorithm = PasswordObject.Algorithm.Value.fromJS(globalObject, arguments[1]) orelse
return JSC.JSValue.undefined;
}
var string_or_buffer = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray");
return JSC.JSValue.undefined;
};
if (string_or_buffer.slice().len == 0) {
globalObject.throwInvalidArguments("password must not be empty", .{});
string_or_buffer.deinit();
return JSC.JSValue.undefined;
}
string_or_buffer.ensureCloned(bun.default_allocator) catch {
globalObject.throwOutOfMemory();
return JSC.JSValue.undefined;
};
defer string_or_buffer.deinit();
return hash(globalObject, string_or_buffer.slice(), algorithm, true);
}
const VerifyJob = struct {
algorithm: ?PasswordObject.Algorithm = null,
password: []const u8,
prev_hash: []const u8,
promise: JSC.JSPromise.Strong,
event_loop: *JSC.EventLoop,
global: *JSC.JSGlobalObject,
ref: Async.KeepAlive = .{},
task: JSC.WorkPoolTask = .{ .callback = &run },
pub const Result = struct {
value: Value,
ref: Async.KeepAlive = .{},
task: JSC.AnyTask = undefined,
promise: JSC.JSPromise.Strong,
global: *JSC.JSGlobalObject,
pub const Value = union(enum) {
err: PasswordObject.HashError,
pass: bool,
pub fn toErrorInstance(this: Value, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
var error_code = std.fmt.allocPrint(bun.default_allocator, "PASSWORD{}", .{PascalToUpperUnderscoreCaseFormatter{ .input = @errorName(this.err) }}) catch @panic("out of memory");
defer bun.default_allocator.free(error_code);
const instance = globalObject.createErrorInstance("Password verification failed with error \"{s}\"", .{@errorName(this.err)});
instance.put(globalObject, ZigString.static("code"), JSC.ZigString.init(error_code).toValueGC(globalObject));
return instance;
}
};
pub fn runFromJS(this: *Result) void {
var promise = this.promise;
this.promise = .{};
this.ref.unref(this.global.bunVM());
var global = this.global;
switch (this.value) {
.err => {
const error_instance = this.value.toErrorInstance(global);
bun.default_allocator.destroy(this);
promise.reject(global, error_instance);
},
.pass => |pass| {
bun.default_allocator.destroy(this);
promise.resolve(global, JSC.JSValue.jsBoolean(pass));
},
}
}
};
pub fn deinit(this: *VerifyJob) void {
this.ref = .{};
this.promise.strong.deinit();
bun.default_allocator.free(this.password);
bun.default_allocator.free(this.prev_hash);
bun.default_allocator.destroy(this);
}
pub fn getValue(password: []const u8, prev_hash: []const u8, algorithm: ?PasswordObject.Algorithm) Result.Value {
const pass = PasswordObject.verify(bun.default_allocator, password, prev_hash, algorithm) catch |err| {
return Result.Value{ .err = err };
};
return Result.Value{ .pass = pass };
}
pub fn run(task: *bun.ThreadPool.Task) void {
var this = @fieldParentPtr(VerifyJob, "task", task);
var result = bun.default_allocator.create(Result) catch @panic("out of memory");
result.* = Result{
.value = getValue(this.password, this.prev_hash, this.algorithm),
.task = JSC.AnyTask.New(Result, Result.runFromJS).init(result),
.promise = this.promise,
.global = this.global,
.ref = this.ref,
};
this.ref = .{};
this.promise.strong = .{};
var concurrent_task = bun.default_allocator.create(JSC.ConcurrentTask) catch @panic("out of memory");
concurrent_task.* = JSC.ConcurrentTask{
.task = JSC.Task.init(&result.task),
.auto_delete = true,
};
this.event_loop.enqueueTaskConcurrent(concurrent_task);
this.deinit();
}
};
// Once we have bindings generator, this should be replaced with a generated function
pub export fn JSPasswordObject__verify(
globalObject: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const arguments_ = callframe.arguments(3);
const arguments = arguments_.ptr[0..arguments_.len];
if (arguments.len < 2) {
globalObject.throwNotEnoughArguments("verify", 2, 0);
return JSC.JSValue.undefined;
}
var algorithm: ?PasswordObject.Algorithm = null;
if (arguments.len > 2 and !arguments[2].isEmptyOrUndefinedOrNull()) {
if (!arguments[2].isString()) {
globalObject.throwInvalidArgumentType("verify", "algorithm", "string");
return JSC.JSValue.undefined;
}
const algorithm_string = arguments[2].getZigString(globalObject);
algorithm = PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
globalObject.throwInvalidArgumentType("verify", "algorithm", unknown_password_algorithm_message);
return JSC.JSValue.undefined;
};
}
var password = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
globalObject.throwInvalidArgumentType("verify", "password", "string or TypedArray");
return JSC.JSValue.undefined;
};
var hash_ = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[1]) orelse {
password.deinit();
globalObject.throwInvalidArgumentType("verify", "hash", "string or TypedArray");
return JSC.JSValue.undefined;
};
if (hash_.slice().len == 0) {
password.deinit();
return JSC.JSPromise.resolvedPromiseValue(globalObject, JSC.JSValue.jsBoolean(false));
}
if (password.slice().len == 0) {
hash_.deinit();
return JSC.JSPromise.resolvedPromiseValue(globalObject, JSC.JSValue.jsBoolean(false));
}
password.ensureCloned(bun.default_allocator) catch {
hash_.deinit();
globalObject.throwOutOfMemory();
return JSC.JSValue.undefined;
};
hash_.ensureCloned(bun.default_allocator) catch {
password.deinit();
globalObject.throwOutOfMemory();
return JSC.JSValue.undefined;
};
return verify(globalObject, password.slice(), hash_.slice(), algorithm, false);
}
// Once we have bindings generator, this should be replaced with a generated function
pub export fn JSPasswordObject__verifySync(
globalObject: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const arguments_ = callframe.arguments(3);
const arguments = arguments_.ptr[0..arguments_.len];
if (arguments.len < 2) {
globalObject.throwNotEnoughArguments("verify", 2, 0);
return JSC.JSValue.undefined;
}
var algorithm: ?PasswordObject.Algorithm = null;
if (arguments.len > 2 and !arguments[2].isEmptyOrUndefinedOrNull()) {
if (!arguments[2].isString()) {
globalObject.throwInvalidArgumentType("verify", "algorithm", "string");
return JSC.JSValue.undefined;
}
const algorithm_string = arguments[2].getZigString(globalObject);
algorithm = PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
globalObject.throwInvalidArgumentType("verify", "algorithm", unknown_password_algorithm_message);
return JSC.JSValue.undefined;
};
}
var password = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
globalObject.throwInvalidArgumentType("verify", "password", "string or TypedArray");
return JSC.JSValue.undefined;
};
var hash_ = JSC.Node.SliceOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[1]) orelse {
password.deinit();
globalObject.throwInvalidArgumentType("verify", "hash", "string or TypedArray");
return JSC.JSValue.undefined;
};
defer password.deinit();
defer hash_.deinit();
if (hash_.slice().len == 0) {
return JSC.JSValue.jsBoolean(false);
}
if (password.slice().len == 0) {
return JSC.JSValue.jsBoolean(false);
}
return verify(globalObject, password.slice(), hash_.slice(), algorithm, true);
}
};
pub const CryptoHasher = struct {
evp: EVP = undefined,
const Digest = EVP.Digest;
pub usingnamespace JSC.Codegen.JSCryptoHasher;
pub const digest = JSC.wrapInstanceMethod(CryptoHasher, "digest_", false);
pub const hash = JSC.wrapStaticMethod(CryptoHasher, "hash_", false);
pub fn getByteLength(
this: *CryptoHasher,
_: *JSC.JSGlobalObject,
) callconv(.C) JSC.JSValue {
return JSC.JSValue.jsNumber(@as(u16, @truncate(this.evp.size())));
}
pub fn getAlgorithm(
this: *CryptoHasher,
globalObject: *JSC.JSGlobalObject,
) callconv(.C) JSC.JSValue {
return ZigString.fromUTF8(bun.asByteSlice(@tagName(this.evp.algorithm))).toValueGC(globalObject);
}
pub fn getAlgorithms(
globalThis_: *JSC.JSGlobalObject,
_: JSValue,
_: JSValue,
) callconv(.C) JSC.JSValue {
var values = EVP.Algorithm.names.values;
return JSC.JSValue.createStringArray(globalThis_, &values, values.len, true);
}
fn hashToEncoding(
globalThis: *JSGlobalObject,
evp: *EVP,
input: JSC.Node.SliceOrBuffer,
encoding: JSC.Node.Encoding,
) JSC.JSValue {
var output_digest_buf: Digest = undefined;
defer input.deinit();
const len = evp.hash(globalThis.bunVM().rareData().boringEngine(), input.slice(), &output_digest_buf) orelse {
const err = BoringSSL.ERR_get_error();
const instance = createCryptoError(globalThis, err);
BoringSSL.ERR_clear_error();
globalThis.throwValue(instance);
return .zero;
};
return encoding.encodeWithMaxSize(globalThis, len, BoringSSL.EVP_MAX_MD_SIZE, &output_digest_buf);
}
fn hashToBytes(
globalThis: *JSGlobalObject,
evp: *EVP,
input: JSC.Node.SliceOrBuffer,
output: ?JSC.ArrayBuffer,
) JSC.JSValue {
var output_digest_buf: Digest = undefined;
var output_digest_slice: []u8 = &output_digest_buf;
defer input.deinit();
if (output) |output_buf| {
const size = evp.size();
var bytes = output_buf.byteSlice();
if (bytes.len < size) {
globalThis.throwInvalidArguments("TypedArray must be at least {d} bytes", .{size});
return JSC.JSValue.zero;
}
output_digest_slice = bytes[0..size];
}
const len = evp.hash(globalThis.bunVM().rareData().boringEngine(), input.slice(), output_digest_slice) orelse {
const err = BoringSSL.ERR_get_error();
const instance = createCryptoError(globalThis, err);
BoringSSL.ERR_clear_error();
globalThis.throwValue(instance);
return .zero;
};
if (output) |output_buf| {
return output_buf.value;
} else {
// Clone to GC-managed memory
return JSC.ArrayBuffer.create(globalThis, output_digest_slice[0..len], .Buffer);
}
}
pub fn hash_(
globalThis: *JSGlobalObject,
algorithm: ZigString,
input: JSC.Node.SliceOrBuffer,
output: ?JSC.Node.StringOrBuffer,
) JSC.JSValue {
var evp = EVP.byName(algorithm, globalThis) orelse {
globalThis.throwInvalidArguments("Unsupported algorithm \"{any}\"", .{algorithm});
return .zero;
};
defer evp.deinit();
if (output) |string_or_buffer| {
switch (string_or_buffer) {
.string => |str| {
const encoding = JSC.Node.Encoding.from(str) orelse {
globalThis.throwInvalidArguments("Unknown encoding: {s}", .{str});
return JSC.JSValue.zero;
};
return hashToEncoding(globalThis, &evp, input, encoding);
},
.buffer => |buffer| {
return hashToBytes(globalThis, &evp, input, buffer.buffer);
},
}
} else {
return hashToBytes(globalThis, &evp, input, null);
}
}
pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) ?*CryptoHasher {
var arguments = callframe.arguments(2);
if (arguments.len == 0) {
globalThis.throwInvalidArguments("Expected an algorithm name as an argument", .{});
return null;
}
const algorithm_name = arguments.ptr[0];
if (algorithm_name.isEmptyOrUndefinedOrNull() or !algorithm_name.isString()) {
globalThis.throwInvalidArguments("algorithm must be a string", .{});
return null;
}
const algorithm = algorithm_name.getZigString(globalThis);
if (algorithm.len == 0) {
globalThis.throwInvalidArguments("Invalid algorithm name", .{});
return null;
}
const evp = EVP.byName(algorithm, globalThis) orelse {
globalThis.throwInvalidArguments("Unsupported algorithm {any}", .{algorithm});
return null;
};
var this = bun.default_allocator.create(CryptoHasher) catch return null;
this.evp = evp;
return this;
}
pub fn getter(
globalObject: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
return CryptoHasher.getConstructor(globalObject);
}
pub fn update(this: *CryptoHasher, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
const thisValue = callframe.this();
const arguments = callframe.arguments(2);
const input = arguments.ptr[0];
const encoding = arguments.ptr[1];
const buffer = JSC.Node.SliceOrBuffer.fromJSWithEncoding(globalThis, globalThis.bunVM().allocator, input, encoding) orelse {
globalThis.throwInvalidArguments("expected string or buffer", .{});
return JSC.JSValue.zero;
};
defer buffer.deinit();
this.evp.update(buffer.slice());
const err = BoringSSL.ERR_get_error();
if (err != 0) {
const instance = createCryptoError(globalThis, err);
BoringSSL.ERR_clear_error();
globalThis.throwValue(instance);
return .zero;
}
return thisValue;
}
pub fn copy(
this: *CryptoHasher,
globalObject: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const new = bun.default_allocator.create(CryptoHasher) catch @panic("Out of memory");
new.evp = this.evp.copy(globalObject.bunVM().rareData().boringEngine()) catch @panic("Out of memory");
return new.toJS(globalObject);
}
pub fn digest_(
this: *@This(),
globalThis: *JSGlobalObject,
output: ?JSC.Node.SliceOrBuffer,
) JSC.JSValue {
if (output) |string_or_buffer| {
switch (string_or_buffer) {
.string => |str| {
defer str.deinit();
const encoding = JSC.Node.Encoding.from(str.slice()) orelse {
globalThis.throwInvalidArguments("Unknown encoding: {}", .{str});
return JSC.JSValue.zero;
};
return this.digestToEncoding(globalThis, encoding);
},
.buffer => |buffer| {
return this.digestToBytes(
globalThis,
buffer.buffer,
);
},
}
} else {
return this.digestToBytes(globalThis, null);
}
}
fn digestToBytes(this: *CryptoHasher, globalThis: *JSGlobalObject, output: ?JSC.ArrayBuffer) JSC.JSValue {
var output_digest_buf: EVP.Digest = undefined;
var output_digest_slice: []u8 = &output_digest_buf;
if (output) |output_buf| {
var bytes = output_buf.byteSlice();
if (bytes.len < output_digest_buf.len) {
globalThis.throwInvalidArguments(comptime std.fmt.comptimePrint("TypedArray must be at least {d} bytes", .{output_digest_buf.len}), .{});
return JSC.JSValue.zero;
}
output_digest_slice = bytes[0..bytes.len];
} else {
output_digest_buf = std.mem.zeroes(EVP.Digest);
}
const result = this.evp.final(globalThis.bunVM().rareData().boringEngine(), output_digest_slice);
if (output) |output_buf| {
return output_buf.value;
} else {
// Clone to GC-managed memory
return JSC.ArrayBuffer.create(globalThis, result, .Buffer);
}
}
fn digestToEncoding(this: *CryptoHasher, globalThis: *JSGlobalObject, encoding: JSC.Node.Encoding) JSC.JSValue {
var output_digest_buf: EVP.Digest = std.mem.zeroes(EVP.Digest);
var output_digest_slice: []u8 = &output_digest_buf;
const out = this.evp.final(globalThis.bunVM().rareData().boringEngine(), output_digest_slice);
return encoding.encodeWithMaxSize(globalThis, out.len, BoringSSL.EVP_MAX_MD_SIZE, out);
}
pub fn finalize(this: *CryptoHasher) callconv(.C) void {
// https://github.com/oven-sh/bun/issues/3250
this.evp.deinit();
bun.default_allocator.destroy(this);
}
};
fn StaticCryptoHasher(comptime Hasher: type, comptime name: [:0]const u8) type {
return struct {
hashing: Hasher = Hasher{},
const ThisHasher = @This();
pub usingnamespace @field(JSC.Codegen, "JS" ++ name);
pub const digest = JSC.wrapInstanceMethod(ThisHasher, "digest_", false);
pub const hash = JSC.wrapStaticMethod(ThisHasher, "hash_", false);
pub fn getByteLength(
_: *@This(),
_: *JSC.JSGlobalObject,
) callconv(.C) JSC.JSValue {
return JSC.JSValue.jsNumber(@as(u16, Hasher.digest));
}
pub fn getByteLengthStatic(
_: *JSC.JSGlobalObject,
_: JSValue,
_: JSValue,
) callconv(.C) JSC.JSValue {
return JSC.JSValue.jsNumber(@as(u16, Hasher.digest));
}
fn hashToEncoding(
globalThis: *JSGlobalObject,
input: JSC.Node.StringOrBuffer,
encoding: JSC.Node.Encoding,
) JSC.JSValue {
var output_digest_buf: Hasher.Digest = undefined;
if (comptime @typeInfo(@TypeOf(Hasher.hash)).Fn.params.len == 3) {
Hasher.hash(input.slice(), &output_digest_buf, JSC.VirtualMachine.get().rareData().boringEngine());
} else {
Hasher.hash(input.slice(), &output_digest_buf);
}
return encoding.encodeWithSize(globalThis, Hasher.digest, &output_digest_buf);
}
fn hashToBytes(
globalThis: *JSGlobalObject,
input: JSC.Node.StringOrBuffer,
output: ?JSC.ArrayBuffer,
) JSC.JSValue {
var output_digest_buf: Hasher.Digest = undefined;
var output_digest_slice: *Hasher.Digest = &output_digest_buf;
if (output) |output_buf| {
var bytes = output_buf.byteSlice();
if (bytes.len < Hasher.digest) {
globalThis.throwInvalidArguments(comptime std.fmt.comptimePrint("TypedArray must be at least {d} bytes", .{Hasher.digest}), .{});
return JSC.JSValue.zero;
}
output_digest_slice = bytes[0..Hasher.digest];
}
if (comptime @typeInfo(@TypeOf(Hasher.hash)).Fn.params.len == 3) {
Hasher.hash(input.slice(), output_digest_slice, JSC.VirtualMachine.get().rareData().boringEngine());
} else {
Hasher.hash(input.slice(), output_digest_slice);
}
if (output) |output_buf| {
return output_buf.value;
} else {
var array_buffer_out = JSC.ArrayBuffer.fromBytes(bun.default_allocator.dupe(u8, output_digest_slice) catch unreachable, .Uint8Array);
return array_buffer_out.toJSUnchecked(globalThis, null);
}
}
pub fn hash_(
globalThis: *JSGlobalObject,
input: JSC.Node.StringOrBuffer,
output: ?JSC.Node.StringOrBuffer,
) JSC.JSValue {
if (output) |string_or_buffer| {
switch (string_or_buffer) {
.string => |str| {
const encoding = JSC.Node.Encoding.from(str) orelse {
globalThis.throwInvalidArguments("Unknown encoding: {s}", .{str});
return JSC.JSValue.zero;
};
return hashToEncoding(globalThis, input, encoding);
},
.buffer => |buffer| {
return hashToBytes(globalThis, input, buffer.buffer);
},
}
} else {
return hashToBytes(globalThis, input, null);
}
}
pub fn constructor(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) ?*@This() {
var this = bun.default_allocator.create(@This()) catch return null;
this.* = .{ .hashing = Hasher.init() };
return this;
}
pub fn getter(
globalObject: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
return ThisHasher.getConstructor(globalObject);
}
pub fn update(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSC.JSValue {
const thisValue = callframe.this();
const input = callframe.argument(0);
const buffer = JSC.Node.SliceOrBuffer.fromJS(globalThis, globalThis.bunVM().allocator, input) orelse {
globalThis.throwInvalidArguments("expected string or buffer", .{});
return JSC.JSValue.zero;
};
defer buffer.deinit();
this.hashing.update(buffer.slice());
return thisValue;
}
pub fn digest_(
this: *@This(),
globalThis: *JSGlobalObject,
output: ?JSC.Node.SliceOrBuffer,
) JSC.JSValue {
if (output) |string_or_buffer| {
switch (string_or_buffer) {
.string => |str| {
const encoding = JSC.Node.Encoding.from(str.slice()) orelse {
globalThis.throwInvalidArguments("Unknown encoding: \"{s}\"", .{str.slice()});
return JSC.JSValue.zero;
};
return this.digestToEncoding(globalThis, encoding);
},
.buffer => |buffer| {
return this.digestToBytes(
globalThis,
buffer.buffer,
);
},
}
} else {
return this.digestToBytes(globalThis, null);
}
}
fn digestToBytes(this: *@This(), globalThis: *JSGlobalObject, output: ?JSC.ArrayBuffer) JSC.JSValue {
var output_digest_buf: Hasher.Digest = undefined;
var output_digest_slice: *Hasher.Digest = &output_digest_buf;
if (output) |output_buf| {
var bytes = output_buf.byteSlice();
if (bytes.len < Hasher.digest) {
globalThis.throwInvalidArguments(comptime std.fmt.comptimePrint("TypedArray must be at least {d} bytes", .{Hasher.digest}), .{});
return JSC.JSValue.zero;
}
output_digest_slice = bytes[0..Hasher.digest];
} else {
output_digest_buf = comptime brk: {
var bytes: Hasher.Digest = undefined;
var i: usize = 0;
while (i < Hasher.digest) {
bytes[i] = 0;
i += 1;
}
break :brk bytes;
};
}
this.hashing.final(output_digest_slice);
if (output) |output_buf| {
return output_buf.value;
} else {
var array_buffer_out = JSC.ArrayBuffer.fromBytes(bun.default_allocator.dupe(u8, &output_digest_buf) catch unreachable, .Uint8Array);
return array_buffer_out.toJSUnchecked(globalThis, null);
}
}
fn digestToEncoding(this: *@This(), globalThis: *JSGlobalObject, encoding: JSC.Node.Encoding) JSC.JSValue {
var output_digest_buf: Hasher.Digest = comptime brk: {
var bytes: Hasher.Digest = undefined;
var i: usize = 0;
while (i < Hasher.digest) {
bytes[i] = 0;
i += 1;
}
break :brk bytes;
};
var output_digest_slice: *Hasher.Digest = &output_digest_buf;
this.hashing.final(output_digest_slice);
return encoding.encodeWithSize(globalThis, Hasher.digest, output_digest_slice);
}
pub fn finalize(this: *@This()) callconv(.C) void {
VirtualMachine.get().allocator.destroy(this);
}
};
}
pub const SHA1 = StaticCryptoHasher(Hashers.SHA1, "SHA1");
pub const MD5 = StaticCryptoHasher(Hashers.MD5, "MD5");
pub const MD4 = StaticCryptoHasher(Hashers.MD4, "MD4");
pub const SHA224 = StaticCryptoHasher(Hashers.SHA224, "SHA224");
pub const SHA512 = StaticCryptoHasher(Hashers.SHA512, "SHA512");
pub const SHA384 = StaticCryptoHasher(Hashers.SHA384, "SHA384");
pub const SHA256 = StaticCryptoHasher(Hashers.SHA256, "SHA256");
pub const SHA512_256 = StaticCryptoHasher(Hashers.SHA512_256, "SHA512_256");
};
pub fn nanoseconds(
globalThis: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const ns = globalThis.bunVM().origin_timer.read();
return JSC.JSValue.jsNumberFromUint64(ns);
}
pub fn serve(
globalObject: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const arguments = callframe.arguments(2).slice();
var config: JSC.API.ServerConfig = brk: {
var exception_ = [1]JSC.JSValueRef{null};
var exception = &exception_;
var args = JSC.Node.ArgumentsSlice.init(globalObject.bunVM(), arguments);
const config_ = JSC.API.ServerConfig.fromJS(globalObject.ptr(), &args, exception);
if (exception[0] != null) {
globalObject.throwValue(exception_[0].?.value());
return .undefined;
}
break :brk config_;
};
var exception_value: *JSC.JSValue = undefined;
if (config.allow_hot) {
if (globalObject.bunVM().hotMap()) |hot| {
if (config.id.len == 0) {
config.id = config.computeID(globalObject.allocator());
}
if (hot.getEntry(config.id)) |entry| {
switch (entry.tag()) {
@field(@TypeOf(entry.tag()), @typeName(JSC.API.HTTPServer)) => {
var server: *JSC.API.HTTPServer = entry.as(JSC.API.HTTPServer);
server.onReloadFromZig(&config, globalObject);
return server.thisObject;
},
@field(@TypeOf(entry.tag()), @typeName(JSC.API.DebugHTTPServer)) => {
var server: *JSC.API.DebugHTTPServer = entry.as(JSC.API.DebugHTTPServer);
server.onReloadFromZig(&config, globalObject);
return server.thisObject;
},
@field(@TypeOf(entry.tag()), @typeName(JSC.API.DebugHTTPSServer)) => {
var server: *JSC.API.DebugHTTPSServer = entry.as(JSC.API.DebugHTTPSServer);
server.onReloadFromZig(&config, globalObject);
return server.thisObject;
},
@field(@TypeOf(entry.tag()), @typeName(JSC.API.HTTPSServer)) => {
var server: *JSC.API.HTTPSServer = entry.as(JSC.API.HTTPSServer);
server.onReloadFromZig(&config, globalObject);
return server.thisObject;
},
else => {},
}
}
}
}
// Listen happens on the next tick!
// This is so we can return a Server object
if (config.ssl_config != null) {
if (config.development) {
var server = JSC.API.DebugHTTPSServer.init(config, globalObject.ptr());
exception_value = &server.thisObject;
server.listen();
if (!server.thisObject.isEmpty()) {
exception_value.unprotect();
globalObject.throwValue(server.thisObject);
server.thisObject = JSC.JSValue.zero;
server.deinit();
return .zero;
}
const obj = server.toJS(globalObject);
obj.protect();
server.thisObject = obj;
if (config.allow_hot) {
if (globalObject.bunVM().hotMap()) |hot| {
hot.insert(config.id, server);
}
}
return obj;
} else {
var server = JSC.API.HTTPSServer.init(config, globalObject.ptr());
exception_value = &server.thisObject;
server.listen();
if (!exception_value.isEmpty()) {
exception_value.unprotect();
globalObject.throwValue(exception_value.*);
server.thisObject = JSC.JSValue.zero;
server.deinit();
return .zero;
}
const obj = server.toJS(globalObject);
obj.protect();
server.thisObject = obj;
if (config.allow_hot) {
if (globalObject.bunVM().hotMap()) |hot| {
hot.insert(config.id, server);
}
}
return obj;
}
} else {
if (config.development) {
var server = JSC.API.DebugHTTPServer.init(config, globalObject.ptr());
exception_value = &server.thisObject;
server.listen();
if (!exception_value.isEmpty()) {
exception_value.unprotect();
globalObject.throwValue(exception_value.*);
server.thisObject = JSC.JSValue.zero;
server.deinit();
return .zero;
}
const obj = server.toJS(globalObject);
obj.protect();
server.thisObject = obj;
if (config.allow_hot) {
if (globalObject.bunVM().hotMap()) |hot| {
hot.insert(config.id, server);
}
}
return obj;
} else {
var server = JSC.API.HTTPServer.init(config, globalObject.ptr());
exception_value = &server.thisObject;
server.listen();
if (!exception_value.isEmpty()) {
exception_value.unprotect();
globalObject.throwValue(exception_value.*);
server.thisObject = JSC.JSValue.zero;
server.deinit();
return .zero;
}
const obj = server.toJS(globalObject);
obj.protect();
server.thisObject = obj;
if (config.allow_hot) {
if (globalObject.bunVM().hotMap()) |hot| {
hot.insert(config.id, server);
}
}
return obj;
}
}
unreachable;
}
pub export fn Bun__escapeHTML16(globalObject: *JSC.JSGlobalObject, input_value: JSValue, ptr: [*]const u16, len: usize) JSValue {
std.debug.assert(len > 0);
const input_slice = ptr[0..len];
const escaped = strings.escapeHTMLForUTF16Input(globalObject.bunVM().allocator, input_slice) catch {
globalObject.vm().throwError(globalObject, ZigString.init("Out of memory").toValue(globalObject));
return JSC.JSValue.jsUndefined();
};
switch (escaped) {
.static => |val| {
return ZigString.init(val).toValue(globalObject);
},
.original => return input_value,
.allocated => |escaped_html| {
if (comptime Environment.allow_assert) {
// assert that re-encoding the string produces the same result
std.debug.assert(
std.mem.eql(
u16,
(strings.toUTF16Alloc(bun.default_allocator, strings.toUTF8Alloc(bun.default_allocator, escaped_html) catch unreachable, false) catch unreachable).?,
escaped_html,
),
);
// assert we do not allocate a new string unnecessarily
std.debug.assert(
!std.mem.eql(
u16,
input_slice,
escaped_html,
),
);
// the output should always be longer than the input
std.debug.assert(escaped_html.len > input_slice.len);
}
return ZigString.from16(escaped_html.ptr, escaped_html.len).toExternalValue(globalObject);
},
}
}
pub export fn Bun__escapeHTML8(globalObject: *JSC.JSGlobalObject, input_value: JSValue, ptr: [*]const u8, len: usize) JSValue {
std.debug.assert(len > 0);
const input_slice = ptr[0..len];
var stack_allocator = std.heap.stackFallback(256, globalObject.bunVM().allocator);
const allocator = if (input_slice.len <= 32) stack_allocator.get() else stack_allocator.fallback_allocator;
const escaped = strings.escapeHTMLForLatin1Input(allocator, input_slice) catch {
globalObject.vm().throwError(globalObject, ZigString.init("Out of memory").toValue(globalObject));
return JSC.JSValue.jsUndefined();
};
switch (escaped) {
.static => |val| {
return ZigString.init(val).toValue(globalObject);
},
.original => return input_value,
.allocated => |escaped_html| {
if (comptime Environment.allow_assert) {
// the output should always be longer than the input
std.debug.assert(escaped_html.len > input_slice.len);
// assert we do not allocate a new string unnecessarily
std.debug.assert(
!std.mem.eql(
u8,
input_slice,
escaped_html,
),
);
}
if (input_slice.len <= 32) {
const zig_str = ZigString.init(escaped_html);
const out = zig_str.toAtomicValue(globalObject);
return out;
}
return ZigString.init(escaped_html).toExternalValue(globalObject);
},
}
}
comptime {
if (!JSC.is_bindgen) {
_ = Bun__escapeHTML8;
_ = Bun__escapeHTML16;
}
}
pub fn allocUnsafe(
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const arguments = callframe.arguments(1);
const size = arguments.ptr[0];
if (!size.isUInt32AsAnyInt()) {
globalThis.throwInvalidArguments("Expected a positive number", .{});
return JSC.JSValue.zero;
}
return JSC.JSValue.createUninitializedUint8Array(globalThis, size.toUInt64NoTruncate());
}
pub fn mmapFile(
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
if (comptime Environment.isWindows) {
globalThis.throwTODO("mmapFile is not supported on Windows");
return JSC.JSValue.zero;
}
const arguments_ = callframe.arguments(2);
var args = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments_.slice());
defer args.deinit();
var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
const path = brk: {
if (args.nextEat()) |path| {
if (path.isString()) {
const path_str = path.toSlice(globalThis, args.arena.allocator());
if (path_str.len > bun.MAX_PATH_BYTES) {
globalThis.throwInvalidArguments("Path too long", .{});
return JSC.JSValue.zero;
}
const paths = &[_]string{path_str.slice()};
break :brk bun.path.joinAbsStringBuf(bun.fs.FileSystem.instance.top_level_dir, &buf, paths, .auto);
}
}
globalThis.throwInvalidArguments("Expected a path", .{});
return JSC.JSValue.zero;
};
buf[path.len] = 0;
const buf_z: [:0]const u8 = buf[0..path.len :0];
const sync_flags: u32 = if (@hasDecl(std.os.MAP, "SYNC")) std.os.MAP.SYNC | std.os.MAP.SHARED_VALIDATE else 0;
const file_flags: u32 = if (@hasDecl(std.os.MAP, "FILE")) std.os.MAP.FILE else 0;
// Conforming applications must specify either MAP_PRIVATE or MAP_SHARED.
var offset: usize = 0;
var flags = file_flags;
var map_size: ?usize = null;
if (args.nextEat()) |opts| {
const sync = opts.get(globalThis, "sync") orelse JSC.JSValue.jsBoolean(false);
const shared = opts.get(globalThis, "shared") orelse JSC.JSValue.jsBoolean(true);
flags |= @as(u32, if (sync.toBoolean()) sync_flags else 0);
flags |= @as(u32, if (shared.toBoolean()) std.os.MAP.SHARED else std.os.MAP.PRIVATE);
if (opts.get(globalThis, "size")) |value| {
map_size = @as(usize, @intCast(value.toInt64()));
}
if (opts.get(globalThis, "offset")) |value| {
offset = @as(usize, @intCast(value.toInt64()));
offset = std.mem.alignBackwardAnyAlign(offset, std.mem.page_size);
}
} else {
flags |= std.os.MAP.SHARED;
}
const map = switch (bun.sys.mmapFile(buf_z, flags, map_size, offset)) {
.result => |map| map,
.err => |err| {
globalThis.throwValue(err.toJSC(globalThis));
return .zero;
},
};
return JSC.C.JSObjectMakeTypedArrayWithBytesNoCopy(globalThis, JSC.C.JSTypedArrayType.kJSTypedArrayTypeUint8Array, @as(?*anyopaque, @ptrCast(map.ptr)), map.len, struct {
pub fn x(ptr: ?*anyopaque, size: ?*anyopaque) callconv(.C) void {
_ = bun.sys.munmap(@as([*]align(std.mem.page_size) u8, @ptrCast(@alignCast(ptr)))[0..@intFromPtr(size)]);
}
}.x, @as(?*anyopaque, @ptrFromInt(map.len)), null).?.value();
}
pub fn getTranspilerConstructor(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
return JSC.API.JSTranspiler.getConstructor(globalThis);
}
pub fn getFileSystemRouter(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
return JSC.API.FileSystemRouter.getConstructor(globalThis);
}
pub fn getHashObject(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
return HashObject.create(globalThis);
}
const HashObject = struct {
pub const wyhash = hashWrap(std.hash.Wyhash).hash;
pub const adler32 = hashWrap(std.hash.Adler32).hash;
pub const crc32 = hashWrap(std.hash.Crc32).hash;
pub const cityHash32 = hashWrap(std.hash.CityHash32).hash;
pub const cityHash64 = hashWrap(std.hash.CityHash64).hash;
pub const murmur32v2 = hashWrap(std.hash.murmur.Murmur2_32).hash;
pub const murmur32v3 = hashWrap(std.hash.murmur.Murmur3_32).hash;
pub const murmur64v2 = hashWrap(std.hash.murmur.Murmur2_64).hash;
pub fn create(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
const function = JSC.createCallback(globalThis, ZigString.static("hash"), 1, &wyhash);
const fns = comptime .{
"wyhash",
"adler32",
"crc32",
"cityHash32",
"cityHash64",
"murmur32v2",
"murmur32v3",
"murmur64v2",
};
inline for (fns) |name| {
const value = JSC.createCallback(
globalThis,
ZigString.static(name),
1,
&@field(HashObject, name),
);
function.put(globalThis, comptime ZigString.static(name), value);
}
return function;
}
fn hashWrap(comptime Hasher_: anytype) type {
return struct {
const Hasher = Hasher_;
pub fn hash(
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const arguments = callframe.arguments(2).slice();
var args = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments);
defer args.deinit();
var input: []const u8 = "";
var input_slice = ZigString.Slice.empty;
defer input_slice.deinit();
if (args.nextEat()) |arg| {
if (arg.as(JSC.WebCore.Blob)) |blob| {
// TODO: files
input = blob.sharedView();
} else {
switch (arg.jsTypeLoose()) {
.ArrayBuffer,
.Int8Array,
.Uint8Array,
.Uint8ClampedArray,
.Int16Array,
.Uint16Array,
.Int32Array,
.Uint32Array,
.Float32Array,
.Float64Array,
.BigInt64Array,
.BigUint64Array,
.DataView,
=> {
var array_buffer = arg.asArrayBuffer(globalThis) orelse {
globalThis.throwInvalidArguments("ArrayBuffer conversion error", .{});
return .zero;
};
input = array_buffer.byteSlice();
},
else => {
input_slice = arg.toSlice(globalThis, bun.default_allocator);
input = input_slice.slice();
},
}
}
}
// std.hash has inconsistent interfaces
//
const Function = if (@hasDecl(Hasher, "hashWithSeed")) Hasher.hashWithSeed else Hasher.hash;
var function_args: std.meta.ArgsTuple(@TypeOf(Function)) = undefined;
if (comptime std.meta.fields(std.meta.ArgsTuple(@TypeOf(Function))).len == 1) {
return JSC.JSValue.jsNumber(Function(input));
} else {
var seed: u64 = 0;
if (args.nextEat()) |arg| {
if (arg.isNumber() or arg.isBigInt()) {
seed = arg.toUInt64NoTruncate();
}
}
if (comptime std.meta.trait.isNumber(@TypeOf(function_args[0]))) {
function_args[0] = @as(@TypeOf(function_args[0]), @truncate(seed));
function_args[1] = input;
} else {
function_args[0] = input;
function_args[1] = @as(@TypeOf(function_args[1]), @truncate(seed));
}
const value = @call(.auto, Function, function_args);
if (@TypeOf(value) == u32) {
return JSC.JSValue.jsNumber(@as(u32, @bitCast(value)));
}
return JSC.JSValue.fromUInt64NoTruncate(globalThis, value);
}
}
};
}
};
pub fn getTOMLObject(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
return TOMLObject.create(globalThis);
}
pub fn getSemver(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
return SemverObject.create(globalThis);
}
pub fn getUnsafe(
globalThis: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
return UnsafeObject.create(globalThis);
}
const UnsafeObject = struct {
pub fn create(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
const object = JSValue.createEmptyObject(globalThis, 3);
const fields = comptime .{
.gcAggressionLevel = &gcAggressionLevel,
.segfault = &__debug__doSegfault,
.arrayBufferToString = &arrayBufferToString,
};
inline for (comptime std.meta.fieldNames(@TypeOf(fields))) |name| {
object.put(
globalThis,
comptime ZigString.static(name),
JSC.createCallback(globalThis, comptime ZigString.static(name), 1, comptime @field(fields, name)),
);
}
return object;
}
pub fn gcAggressionLevel(
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const ret = JSValue.jsNumber(@as(i32, @intFromEnum(globalThis.bunVM().aggressive_garbage_collection)));
const value = callframe.arguments(1).ptr[0];
if (!value.isEmptyOrUndefinedOrNull()) {
switch (value.coerce(i32, globalThis)) {
1 => globalThis.bunVM().aggressive_garbage_collection = .mild,
2 => globalThis.bunVM().aggressive_garbage_collection = .aggressive,
0 => globalThis.bunVM().aggressive_garbage_collection = .none,
else => {},
}
}
return ret;
}
// For testing the segfault handler
pub fn __debug__doSegfault(
_: *JSC.JSGlobalObject,
_: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const Reporter = @import("../../report.zig");
Reporter.globalError(error.SegfaultTest, null);
}
pub fn arrayBufferToString(
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
const args = callframe.arguments(2).slice();
const array_buffer = JSC.ArrayBuffer.fromTypedArray(globalThis, args[0]);
switch (array_buffer.typed_array_type) {
.Uint16Array, .Int16Array => {
var zig_str = ZigString.init("");
zig_str._unsafe_ptr_do_not_use = @as([*]const u8, @ptrCast(@alignCast(array_buffer.ptr)));
zig_str.len = array_buffer.len;
zig_str.markUTF16();
return zig_str.toValueGC(globalThis);
},
else => {
return ZigString.init(array_buffer.slice()).toValueGC(globalThis);
},
}
}
};
const TOMLObject = struct {
const TOMLParser = @import("../../toml/toml_parser.zig").TOML;
pub fn create(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
const object = JSValue.createEmptyObject(globalThis, 1);
object.put(
globalThis,
ZigString.static("parse"),
JSC.createCallback(
globalThis,
ZigString.static("parse"),
1,
&parse,
),
);
return object;
}
pub fn parse(
globalThis: *JSC.JSGlobalObject,
callframe: *JSC.CallFrame,
) callconv(.C) JSC.JSValue {
var arena = @import("root").bun.ArenaAllocator.init(globalThis.allocator());
var allocator = arena.allocator();
defer arena.deinit();
var log = logger.Log.init(default_allocator);
const arguments = callframe.arguments(1).slice();
var input_slice = arguments[0].toSlice(globalThis, bun.default_allocator);
defer input_slice.deinit();
var source = logger.Source.initPathString("input.toml", input_slice.slice());
var parse_result = TOMLParser.parse(&source, &log, allocator) catch {
globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to parse toml"));
return .zero;
};
// for now...
var buffer_writer = js_printer.BufferWriter.init(allocator) catch {
globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to print toml"));
return .zero;
};
var writer = js_printer.BufferPrinter.init(buffer_writer);
_ = js_printer.printJSON(*js_printer.BufferPrinter, &writer, parse_result, &source) catch {
globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to print toml"));
return .zero;
};
var slice = writer.ctx.buffer.toOwnedSliceLeaky();
var out = bun.String.fromUTF8(slice);
defer out.deref();
return out.toJSForParseJSON(globalThis);
}
};
const Debugger = JSC.Debugger;
pub const Timer = struct {
last_id: i32 = 1,
warned: bool = false,
// We split up the map here to avoid storing an extra "repeat" boolean
maps: struct {
setTimeout: TimeoutMap = .{},
setInterval: TimeoutMap = .{},
setImmediate: TimeoutMap = .{},
pub inline fn get(this: *@This(), kind: Timeout.Kind) *TimeoutMap {
return switch (kind) {
.setTimeout => &this.setTimeout,
.setInterval => &this.setInterval,
.setImmediate => &this.setImmediate,
};
}
} = .{},
/// TimeoutMap is map of i32 to nullable Timeout structs
/// i32 is exposed to JavaScript and can be used with clearTimeout, clearInterval, etc.
/// When Timeout is null, it means the tasks have been scheduled but not yet executed.
/// Timeouts are enqueued as a task to be run on the next tick of the task queue
/// The task queue runs after the event loop tasks have been run
/// Therefore, there is a race condition where you cancel the task after it has already been enqueued
/// In that case, it shouldn't run. It should be skipped.
pub const TimeoutMap = std.AutoArrayHashMapUnmanaged(
i32,
?Timeout,
);
pub fn getNextID() callconv(.C) i32 {
VirtualMachine.get().timer.last_id +%= 1;
return VirtualMachine.get().timer.last_id;
}
const uws = @import("root").bun.uws;
// TODO: reference count to avoid multiple Strong references to the same
// object in setInterval
const CallbackJob = struct {
id: i32 = 0,
task: JSC.AnyTask = undefined,
ref: JSC.Ref = JSC.Ref.init(),
globalThis: *JSC.JSGlobalObject,
callback: JSC.Strong = .{},
arguments: JSC.Strong = .{},
kind: Timeout.Kind = .setTimeout,
pub const Task = JSC.AnyTask.New(CallbackJob, perform);
pub export fn CallbackJob__onResolve(_: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
const args = callframe.arguments(2);
if (args.len < 2) {
return JSValue.jsUndefined();
}
var this = args.ptr[1].asPtr(CallbackJob);
this.deinit();
return JSValue.jsUndefined();
}
pub export fn CallbackJob__onReject(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
const args = callframe.arguments(2);
if (args.len < 2) {
return JSValue.jsUndefined();
}
var this = args.ptr[1].asPtr(CallbackJob);
globalThis.bunVM().onUnhandledError(globalThis, args.ptr[0]);
this.deinit();
return JSValue.jsUndefined();
}
pub fn deinit(this: *CallbackJob) void {
this.callback.deinit();
this.arguments.deinit();
this.ref.unref(this.globalThis.bunVM());
bun.default_allocator.destroy(this);
}
pub fn perform(this: *CallbackJob) void {
var globalThis = this.globalThis;
var vm = globalThis.bunVM();
const kind = this.kind;
var map: *TimeoutMap = vm.timer.maps.get(kind);
const should_cancel_job = brk: {
// This doesn't deinit the timer
// Timers are deinit'd separately
// We do need to handle when the timer is cancelled after the job has been enqueued
if (kind != .setInterval) {
if (map.get(this.id)) |tombstone_or_timer| {
break :brk tombstone_or_timer != null;
} else {
// clearTimeout has been called
break :brk true;
}
} else {
if (map.getPtr(this.id)) |tombstone_or_timer| {
// Disable thundering herd of setInterval() calls
if (tombstone_or_timer.* != null) {
tombstone_or_timer.*.?.has_scheduled_job = false;
}
// .refresh() was called after CallbackJob enqueued
break :brk tombstone_or_timer.* == null;
}
}
break :brk false;
};
if (should_cancel_job) {
if (vm.isInspectorEnabled()) {
Debugger.didCancelAsyncCall(globalThis, .DOMTimer, Timeout.ID.asyncID(.{ .id = this.id, .kind = kind }));
}
this.deinit();
return;
} else if (kind != .setInterval) {
_ = map.swapRemove(this.id);
}
var args_buf: [8]JSC.JSValue = undefined;
var args: []JSC.JSValue = &.{};
var args_needs_deinit = false;
defer if (args_needs_deinit) bun.default_allocator.free(args);
const callback = this.callback.get() orelse @panic("Expected CallbackJob to have a callback function");
if (this.arguments.trySwap()) |arguments| {
// Bun.sleep passes a Promise
if (arguments.jsType() == .JSPromise) {
args_buf[0] = arguments;
args = args_buf[0..1];
} else {
const count = arguments.getLength(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, @as(u32, @truncate(i)));
arg += 1;
}
}
}
}
if (vm.isInspectorEnabled()) {
Debugger.willDispatchAsyncCall(globalThis, .DOMTimer, Timeout.ID.asyncID(.{ .id = this.id, .kind = kind }));
}
const result = callback.callWithGlobalThis(
globalThis,
args,
);
if (vm.isInspectorEnabled()) {
Debugger.didDispatchAsyncCall(globalThis, .DOMTimer, Timeout.ID.asyncID(.{ .id = this.id, .kind = kind }));
}
if (result.isEmptyOrUndefinedOrNull() or !result.isCell()) {
this.deinit();
return;
}
if (result.isAnyError()) {
vm.onUnhandledError(globalThis, result);
this.deinit();
return;
}
if (result.asAnyPromise()) |promise| {
switch (promise.status(globalThis.vm())) {
.Rejected => {
this.deinit();
vm.onUnhandledError(globalThis, promise.result(globalThis.vm()));
},
.Fulfilled => {
this.deinit();
// get the value out of the promise
_ = promise.result(globalThis.vm());
},
.Pending => {
result.then(globalThis, this, CallbackJob__onResolve, CallbackJob__onReject);
},
}
} else {
this.deinit();
}
}
};
pub const TimerObject = struct {
id: i32 = -1,
kind: Timeout.Kind = .setTimeout,
ref_count: u16 = 1,
interval: i32 = 0,
// we do not allow the timer to be refreshed after we call clearInterval/clearTimeout
has_cleaned_up: bool = false,
pub usingnamespace JSC.Codegen.JSTimeout;
pub fn init(globalThis: *JSGlobalObject, id: i32, kind: Timeout.Kind, interval: i32, callback: JSValue, arguments: JSValue) JSValue {
var timer = globalThis.allocator().create(TimerObject) catch unreachable;
timer.* = .{
.id = id,
.kind = kind,
.interval = interval,
};
var timer_js = timer.toJS(globalThis);
timer_js.ensureStillAlive();
TimerObject.argumentsSetCached(timer_js, globalThis, arguments);
TimerObject.callbackSetCached(timer_js, globalThis, callback);
timer_js.ensureStillAlive();
return timer_js;
}
pub fn doRef(this: *TimerObject, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
const this_value = callframe.this();
this_value.ensureStillAlive();
if (this.ref_count > 0)
this.ref_count +|= 1;
var vm = globalObject.bunVM();
switch (this.kind) {
.setTimeout, .setImmediate, .setInterval => {
if (vm.timer.maps.get(this.kind).getPtr(this.id)) |val_| {
if (val_.*) |*val| {
val.poll_ref.ref(vm);
if (val.did_unref_timer) {
val.did_unref_timer = false;
if (comptime Environment.isPosix)
vm.event_loop_handle.?.num_polls += 1;
}
}
}
},
}
return this_value;
}
pub fn doRefresh(this: *TimerObject, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
// TODO: this is not the optimal way to do this but it works, we should revisit this and optimize it
// like truly resetting the timer instead of removing and re-adding when possible
const this_value = callframe.this();
// setImmediate does not support refreshing and we do not support refreshing after cleanup
if (this.has_cleaned_up or this.id == -1 or this.kind == .setImmediate) {
return JSValue.jsUndefined();
}
const vm = globalThis.bunVM();
var map = vm.timer.maps.get(this.kind);
// reschedule the event
if (TimerObject.callbackGetCached(this_value)) |callback| {
callback.ensureStillAlive();
const id: Timeout.ID = .{
.id = this.id,
.kind = this.kind,
};
if (this.kind == .setTimeout and this.interval == 0) {
var cb: CallbackJob = .{
.callback = JSC.Strong.create(callback, globalThis),
.globalThis = globalThis,
.id = this.id,
.kind = this.kind,
};
if (TimerObject.argumentsGetCached(this_value)) |arguments| {
arguments.ensureStillAlive();
cb.arguments = JSC.Strong.create(arguments, globalThis);
}
var job = vm.allocator.create(CallbackJob) catch @panic(
"Out of memory while allocating Timeout",
);
job.* = cb;
job.task = CallbackJob.Task.init(job);
job.ref.ref(vm);
// cancel the current event if exists before re-adding it
if (map.fetchSwapRemove(this.id)) |timer| {
if (timer.value != null) {
var value = timer.value.?;
value.deinit();
}
}
vm.enqueueTask(JSC.Task.init(&job.task));
if (vm.isInspectorEnabled()) {
Debugger.didScheduleAsyncCall(globalThis, .DOMTimer, id.asyncID(), true);
}
map.put(vm.allocator, this.id, null) catch unreachable;
return this_value;
}
var timeout = Timeout{
.callback = JSC.Strong.create(callback, globalThis),
.globalThis = globalThis,
.timer = uws.Timer.create(
vm.uwsLoop(),
id,
),
};
if (TimerObject.argumentsGetCached(this_value)) |arguments| {
arguments.ensureStillAlive();
timeout.arguments = JSC.Strong.create(arguments, globalThis);
}
timeout.poll_ref.ref(vm);
// cancel the current event if exists before re-adding it
if (map.fetchSwapRemove(this.id)) |timer| {
if (timer.value != null) {
var value = timer.value.?;
value.deinit();
}
}
map.put(vm.allocator, this.id, timeout) catch unreachable;
timeout.timer.set(
id,
Timeout.run,
this.interval,
@as(i32, @intFromBool(this.kind == .setInterval)) * this.interval,
);
return this_value;
}
return JSValue.jsUndefined();
}
pub fn doUnref(this: *TimerObject, globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue {
const this_value = callframe.this();
this_value.ensureStillAlive();
this.ref_count -|= 1;
var vm = globalObject.bunVM();
switch (this.kind) {
.setTimeout, .setImmediate, .setInterval => {
if (vm.timer.maps.get(this.kind).getPtr(this.id)) |val_| {
if (val_.*) |*val| {
val.poll_ref.unref(vm);
if (!val.did_unref_timer) {
val.did_unref_timer = true;
if (comptime Environment.isPosix)
vm.event_loop_handle.?.num_polls -= 1;
}
}
}
},
}
return this_value;
}
pub fn hasRef(this: *TimerObject, globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue {
return JSValue.jsBoolean(this.ref_count > 0 and globalObject.bunVM().timer.maps.get(this.kind).contains(this.id));
}
pub fn toPrimitive(this: *TimerObject, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue {
return JSValue.jsNumber(this.id);
}
pub fn markHasClear(this: *TimerObject) void {
this.has_cleaned_up = true;
}
pub fn finalize(this: *TimerObject) callconv(.C) void {
bun.default_allocator.destroy(this);
}
};
pub const Timeout = struct {
callback: JSC.Strong = .{},
globalThis: *JSC.JSGlobalObject,
timer: *uws.Timer,
did_unref_timer: bool = false,
poll_ref: Async.KeepAlive = Async.KeepAlive.init(),
arguments: JSC.Strong = .{},
has_scheduled_job: bool = false,
pub const Kind = enum(u32) {
setTimeout,
setInterval,
setImmediate,
};
// this is sized to be the same as one pointer
pub const ID = extern struct {
id: i32,
kind: Kind = Kind.setTimeout,
pub inline fn asyncID(this: ID) u64 {
return @bitCast(this);
}
pub fn repeats(this: ID) bool {
return this.kind == .setInterval;
}
};
pub fn run(timer: *uws.Timer) callconv(.C) void {
const timer_id: ID = timer.as(ID);
// use the threadlocal despite being slow on macOS
// to handle the timeout being cancelled after already enqueued
var vm = JSC.VirtualMachine.get();
const repeats = timer_id.repeats();
var map = vm.timer.maps.get(timer_id.kind);
var this_: ?Timeout = map.get(
timer_id.id,
) orelse return;
var this = this_ orelse
return;
var globalThis = this.globalThis;
// Disable thundering herd of setInterval() calls
// Skip setInterval() calls when the previous one has not been run yet.
if (repeats and this.has_scheduled_job) {
return;
}
var cb: CallbackJob = .{
.callback = if (repeats)
JSC.Strong.create(
this.callback.get() orelse {
// if the callback was freed, that's an error
if (comptime Environment.allow_assert)
unreachable;
this.deinit();
_ = map.swapRemove(timer_id.id);
return;
},
globalThis,
)
else
this.callback,
.arguments = if (repeats and this.arguments.has())
JSC.Strong.create(
this.arguments.get() orelse {
// if the arguments freed, that's an error
if (comptime Environment.allow_assert)
unreachable;
this.deinit();
_ = map.swapRemove(timer_id.id);
return;
},
globalThis,
)
else
this.arguments,
.globalThis = globalThis,
.id = timer_id.id,
.kind = timer_id.kind,
};
// This allows us to:
// - free the memory before the job is run
// - reuse the JSC.Strong
if (!repeats) {
this.callback = .{};
this.arguments = .{};
map.put(vm.allocator, timer_id.id, null) catch unreachable;
this.deinit();
} else {
this.has_scheduled_job = true;
map.put(vm.allocator, timer_id.id, this) catch {};
}
var job = vm.allocator.create(CallbackJob) catch @panic(
"Out of memory while allocating Timeout",
);
job.* = cb;
job.task = CallbackJob.Task.init(job);
job.ref.ref(vm);
vm.enqueueTask(JSC.Task.init(&job.task));
if (vm.isInspectorEnabled()) {
Debugger.didScheduleAsyncCall(globalThis, .DOMTimer, timer_id.asyncID(), !repeats);
}
}
pub fn deinit(this: *Timeout) void {
JSC.markBinding(@src());
var vm = this.globalThis.bunVM();
this.poll_ref.unref(vm);
this.timer.deinit(false);
if (comptime Environment.isPosix)
// balance double unreffing in doUnref
vm.event_loop_handle.?.num_polls += @as(i32, @intFromBool(this.did_unref_timer));
this.callback.deinit();
this.arguments.deinit();
}
};
fn set(
id: i32,
globalThis: *JSGlobalObject,
callback: JSValue,
interval: i32,
arguments_array_or_zero: JSValue,
repeat: bool,
) !void {
JSC.markBinding(@src());
var vm = globalThis.bunVM();
const kind: Timeout.Kind = if (repeat) .setInterval else .setTimeout;
var map = vm.timer.maps.get(kind);
// setImmediate(foo)
if (kind == .setTimeout and interval == 0) {
var cb: CallbackJob = .{
.callback = JSC.Strong.create(callback, globalThis),
.globalThis = globalThis,
.id = id,
.kind = kind,
};
if (arguments_array_or_zero != .zero) {
cb.arguments = JSC.Strong.create(arguments_array_or_zero, globalThis);
}
var job = vm.allocator.create(CallbackJob) catch @panic(
"Out of memory while allocating Timeout",
);
job.* = cb;
job.task = CallbackJob.Task.init(job);
job.ref.ref(vm);
vm.enqueueImmediateTask(JSC.Task.init(&job.task));
if (vm.isInspectorEnabled()) {
Debugger.didScheduleAsyncCall(globalThis, .DOMTimer, Timeout.ID.asyncID(.{ .id = id, .kind = kind }), !repeat);
}
map.put(vm.allocator, id, null) catch unreachable;
return;
}
var timeout = Timeout{
.callback = JSC.Strong.create(callback, globalThis),
.globalThis = globalThis,
.timer = uws.Timer.create(
vm.uwsLoop(),
Timeout.ID{
.id = id,
.kind = kind,
},
),
};
if (arguments_array_or_zero != .zero) {
timeout.arguments = JSC.Strong.create(arguments_array_or_zero, globalThis);
}
timeout.poll_ref.ref(vm);
map.put(vm.allocator, id, timeout) catch unreachable;
if (vm.isInspectorEnabled()) {
Debugger.didScheduleAsyncCall(globalThis, .DOMTimer, Timeout.ID.asyncID(.{ .id = id, .kind = kind }), !repeat);
}
timeout.timer.set(
Timeout.ID{
.id = id,
.kind = kind,
},
Timeout.run,
interval,
@as(i32, @intFromBool(kind == .setInterval)) * interval,
);
}
pub fn setImmediate(
globalThis: *JSGlobalObject,
callback: JSValue,
arguments: JSValue,
) callconv(.C) JSValue {
JSC.markBinding(@src());
const id = globalThis.bunVM().timer.last_id;
globalThis.bunVM().timer.last_id +%= 1;
const interval: i32 = 0;
const wrappedCallback = callback.withAsyncContextIfNeeded(globalThis);
Timer.set(id, globalThis, wrappedCallback, interval, arguments, false) catch
return JSValue.jsUndefined();
return TimerObject.init(globalThis, id, .setTimeout, interval, wrappedCallback, arguments);
}
comptime {
if (!JSC.is_bindgen) {
@export(setImmediate, .{ .name = "Bun__Timer__setImmediate" });
}
}
pub fn setTimeout(
globalThis: *JSGlobalObject,
callback: JSValue,
countdown: JSValue,
arguments: JSValue,
) callconv(.C) JSValue {
JSC.markBinding(@src());
const id = globalThis.bunVM().timer.last_id;
globalThis.bunVM().timer.last_id +%= 1;
const interval: i32 = @max(
countdown.coerce(i32, globalThis),
// It must be 1 at minimum or setTimeout(cb, 0) will seemingly hang
1,
);
const wrappedCallback = callback.withAsyncContextIfNeeded(globalThis);
Timer.set(id, globalThis, wrappedCallback, interval, arguments, false) catch
return JSValue.jsUndefined();
return TimerObject.init(globalThis, id, .setTimeout, interval, wrappedCallback, arguments);
}
pub fn setInterval(
globalThis: *JSGlobalObject,
callback: JSValue,
countdown: JSValue,
arguments: JSValue,
) callconv(.C) JSValue {
JSC.markBinding(@src());
const id = globalThis.bunVM().timer.last_id;
globalThis.bunVM().timer.last_id +%= 1;
const wrappedCallback = callback.withAsyncContextIfNeeded(globalThis);
// We don't deal with nesting levels directly
// but we do set the minimum timeout to be 1ms for repeating timers
const interval: i32 = @max(
countdown.coerce(i32, globalThis),
1,
);
Timer.set(id, globalThis, wrappedCallback, interval, arguments, true) catch
return JSValue.jsUndefined();
return TimerObject.init(globalThis, id, .setInterval, interval, wrappedCallback, arguments);
}
pub fn clearTimer(timer_id_value: JSValue, globalThis: *JSGlobalObject, repeats: bool) void {
JSC.markBinding(@src());
const kind: Timeout.Kind = if (repeats) .setInterval else .setTimeout;
var vm = globalThis.bunVM();
var map = vm.timer.maps.get(kind);
const id: Timeout.ID = .{
.id = brk: {
if (timer_id_value.isAnyInt()) {
break :brk timer_id_value.coerce(i32, globalThis);
}
if (TimerObject.fromJS(timer_id_value)) |timer_obj| {
timer_obj.markHasClear();
break :brk timer_obj.id;
}
return;
},
.kind = kind,
};
var timer = map.fetchSwapRemove(id.id) orelse return;
if (vm.isInspectorEnabled()) {
Debugger.didCancelAsyncCall(globalThis, .DOMTimer, id.asyncID());
}
if (timer.value == null) {
// this timer was scheduled to run but was cancelled before it was run
// so long as the callback isn't already in progress, fetchSwapRemove will handle invalidating it
return;
}
timer.value.?.deinit();
}
pub fn clearTimeout(
globalThis: *JSGlobalObject,
id: JSValue,
) callconv(.C) JSValue {
JSC.markBinding(@src());
Timer.clearTimer(id, globalThis, false);
return JSValue.jsUndefined();
}
pub fn clearInterval(
globalThis: *JSGlobalObject,
id: JSValue,
) callconv(.C) JSValue {
JSC.markBinding(@src());
Timer.clearTimer(id, globalThis, true);
return JSValue.jsUndefined();
}
const Shimmer = @import("../bindings/shimmer.zig").Shimmer;
pub const shim = Shimmer("Bun", "Timer", @This());
pub const name = "Bun__Timer";
pub const include = "";
pub const namespace = shim.namespace;
pub const Export = shim.exportFunctions(.{
.setTimeout = setTimeout,
.setInterval = setInterval,
.clearTimeout = clearTimeout,
.clearInterval = clearInterval,
.getNextID = getNextID,
});
comptime {
if (!JSC.is_bindgen) {
@export(setTimeout, .{ .name = Export[0].symbol_name });
@export(setInterval, .{ .name = Export[1].symbol_name });
@export(clearTimeout, .{ .name = Export[2].symbol_name });
@export(clearInterval, .{ .name = Export[3].symbol_name });
@export(getNextID, .{ .name = Export[4].symbol_name });
}
}
};
pub const FFIObject = struct {
const fields = .{
.viewSource = JSC.wrapStaticMethod(
JSC.FFI,
"print",
false,
),
.dlopen = JSC.wrapStaticMethod(JSC.FFI, "open", false),
.callback = JSC.wrapStaticMethod(JSC.FFI, "callback", false),
.linkSymbols = JSC.wrapStaticMethod(JSC.FFI, "linkSymbols", false),
.toBuffer = JSC.wrapStaticMethod(@This(), "toBuffer", false),
.toArrayBuffer = JSC.wrapStaticMethod(@This(), "toArrayBuffer", false),
.closeCallback = JSC.wrapStaticMethod(JSC.FFI, "closeCallback", false),
.CString = JSC.wrapStaticMethod(Bun.FFIObject, "newCString", false),
};
pub fn newCString(globalThis: *JSGlobalObject, value: JSValue, byteOffset: ?JSValue, lengthValue: ?JSValue) JSC.JSValue {
switch (FFIObject.getPtrSlice(globalThis, value, byteOffset, lengthValue)) {
.err => |err| {
return err;
},
.slice => |slice| {
return WebCore.Encoder.toString(slice.ptr, slice.len, globalThis, .utf8);
},
}
}
pub const dom_call = JSC.DOMCall("FFI", @This(), "ptr", f64, JSC.DOMEffect.forRead(.TypedArrayProperties));
pub fn toJS(globalObject: *JSC.JSGlobalObject) JSC.JSValue {
const object = JSC.JSValue.createEmptyObject(globalObject, comptime std.meta.fieldNames(@TypeOf(fields)).len + 2);
inline for (comptime std.meta.fieldNames(@TypeOf(fields))) |field| {
object.put(
globalObject,
comptime ZigString.static(field),
JSC.createCallback(globalObject, comptime ZigString.static(field), 1, comptime @field(fields, field)),
);
}
dom_call.put(globalObject, object);
object.put(globalObject, ZigString.static("read"), Reader.toJS(globalObject));
return object;
}
pub const Reader = struct {
pub const DOMCalls = .{
.u8 = JSC.DOMCall("Reader", @This(), "u8", i32, JSC.DOMEffect.forRead(.World)),
.u16 = JSC.DOMCall("Reader", @This(), "u16", i32, JSC.DOMEffect.forRead(.World)),
.u32 = JSC.DOMCall("Reader", @This(), "u32", i32, JSC.DOMEffect.forRead(.World)),
.ptr = JSC.DOMCall("Reader", @This(), "ptr", i52, JSC.DOMEffect.forRead(.World)),
.i8 = JSC.DOMCall("Reader", @This(), "i8", i32, JSC.DOMEffect.forRead(.World)),
.i16 = JSC.DOMCall("Reader", @This(), "i16", i32, JSC.DOMEffect.forRead(.World)),
.i32 = JSC.DOMCall("Reader", @This(), "i32", i32, JSC.DOMEffect.forRead(.World)),
.i64 = JSC.DOMCall("Reader", @This(), "i64", i64, JSC.DOMEffect.forRead(.World)),
.u64 = JSC.DOMCall("Reader", @This(), "u64", u64, JSC.DOMEffect.forRead(.World)),
.intptr = JSC.DOMCall("Reader", @This(), "intptr", i52, JSC.DOMEffect.forRead(.World)),
.f32 = JSC.DOMCall("Reader", @This(), "f32", f64, JSC.DOMEffect.forRead(.World)),
.f64 = JSC.DOMCall("Reader", @This(), "f64", f64, JSC.DOMEffect.forRead(.World)),
};
pub fn toJS(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
const obj = JSC.JSValue.createEmptyObject(globalThis, std.meta.fieldNames(@TypeOf(Reader.DOMCalls)).len);
inline for (comptime std.meta.fieldNames(@TypeOf(Reader.DOMCalls))) |field| {
@field(Reader.DOMCalls, field).put(globalThis, obj);
}
return obj;
}
pub fn @"u8"(
_: *JSGlobalObject,
_: JSValue,
arguments: []const JSValue,
) JSValue {
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
const value = @as(*align(1) u8, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn @"u16"(
_: *JSGlobalObject,
_: JSValue,
arguments: []const JSValue,
) JSValue {
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
const value = @as(*align(1) u16, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn @"u32"(
_: *JSGlobalObject,
_: JSValue,
arguments: []const JSValue,
) JSValue {
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
const value = @as(*align(1) u32, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn ptr(
_: *JSGlobalObject,
_: JSValue,
arguments: []const JSValue,
) JSValue {
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
const value = @as(*align(1) u64, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn @"i8"(
_: *JSGlobalObject,
_: JSValue,
arguments: []const JSValue,
) JSValue {
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
const value = @as(*align(1) i8, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn @"i16"(
_: *JSGlobalObject,
_: JSValue,
arguments: []const JSValue,
) JSValue {
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
const value = @as(*align(1) i16, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn @"i32"(
_: *JSGlobalObject,
_: JSValue,
arguments: []const JSValue,
) JSValue {
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
const value = @as(*align(1) i32, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn intptr(
_: *JSGlobalObject,
_: JSValue,
arguments: []const JSValue,
) JSValue {
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
const value = @as(*align(1) i64, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn @"f32"(
_: *JSGlobalObject,
_: JSValue,
arguments: []const JSValue,
) JSValue {
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
const value = @as(*align(1) f32, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn @"f64"(
_: *JSGlobalObject,
_: JSValue,
arguments: []const JSValue,
) JSValue {
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
const value = @as(*align(1) f64, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn @"i64"(
global: *JSGlobalObject,
_: JSValue,
arguments: []const JSValue,
) JSValue {
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
const value = @as(*align(1) i64, @ptrFromInt(addr)).*;
return JSValue.fromInt64NoTruncate(global, value);
}
pub fn @"u64"(
global: *JSGlobalObject,
_: JSValue,
arguments: []const JSValue,
) JSValue {
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
const value = @as(*align(1) u64, @ptrFromInt(addr)).*;
return JSValue.fromUInt64NoTruncate(global, value);
}
pub fn u8WithoutTypeChecks(
_: *JSGlobalObject,
_: *anyopaque,
raw_addr: i64,
offset: i32,
) callconv(.C) JSValue {
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
const value = @as(*align(1) u8, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn u16WithoutTypeChecks(
_: *JSGlobalObject,
_: *anyopaque,
raw_addr: i64,
offset: i32,
) callconv(.C) JSValue {
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
const value = @as(*align(1) u16, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn u32WithoutTypeChecks(
_: *JSGlobalObject,
_: *anyopaque,
raw_addr: i64,
offset: i32,
) callconv(.C) JSValue {
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
const value = @as(*align(1) u32, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn ptrWithoutTypeChecks(
_: *JSGlobalObject,
_: *anyopaque,
raw_addr: i64,
offset: i32,
) callconv(.C) JSValue {
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
const value = @as(*align(1) u64, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn i8WithoutTypeChecks(
_: *JSGlobalObject,
_: *anyopaque,
raw_addr: i64,
offset: i32,
) callconv(.C) JSValue {
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
const value = @as(*align(1) i8, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn i16WithoutTypeChecks(
_: *JSGlobalObject,
_: *anyopaque,
raw_addr: i64,
offset: i32,
) callconv(.C) JSValue {
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
const value = @as(*align(1) i16, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn i32WithoutTypeChecks(
_: *JSGlobalObject,
_: *anyopaque,
raw_addr: i64,
offset: i32,
) callconv(.C) JSValue {
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
const value = @as(*align(1) i32, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn intptrWithoutTypeChecks(
_: *JSGlobalObject,
_: *anyopaque,
raw_addr: i64,
offset: i32,
) callconv(.C) JSValue {
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
const value = @as(*align(1) i64, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn f32WithoutTypeChecks(
_: *JSGlobalObject,
_: *anyopaque,
raw_addr: i64,
offset: i32,
) callconv(.C) JSValue {
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
const value = @as(*align(1) f32, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn f64WithoutTypeChecks(
_: *JSGlobalObject,
_: *anyopaque,
raw_addr: i64,
offset: i32,
) callconv(.C) JSValue {
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
const value = @as(*align(1) f64, @ptrFromInt(addr)).*;
return JSValue.jsNumber(value);
}
pub fn u64WithoutTypeChecks(
global: *JSGlobalObject,
_: *anyopaque,
raw_addr: i64,
offset: i32,
) callconv(.C) JSValue {
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
const value = @as(*align(1) u64, @ptrFromInt(addr)).*;
return JSValue.fromUInt64NoTruncate(global, value);
}
pub fn i64WithoutTypeChecks(
global: *JSGlobalObject,
_: *anyopaque,
raw_addr: i64,
offset: i32,
) callconv(.C) JSValue {
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
const value = @as(*align(1) i64, @ptrFromInt(addr)).*;
return JSValue.fromInt64NoTruncate(global, value);
}
};
pub fn ptr(
globalThis: *JSGlobalObject,
_: JSValue,
arguments: []const JSValue,
) JSValue {
return switch (arguments.len) {
0 => ptr_(globalThis, JSValue.zero, null),
1 => ptr_(globalThis, arguments[0], null),
else => ptr_(globalThis, arguments[0], arguments[1]),
};
}
pub fn ptrWithoutTypeChecks(
_: *JSGlobalObject,
_: *anyopaque,
array: *JSC.JSUint8Array,
) callconv(.C) JSValue {
return JSValue.fromPtrAddress(@intFromPtr(array.ptr()));
}
fn ptr_(
globalThis: *JSGlobalObject,
value: JSValue,
byteOffset: ?JSValue,
) JSValue {
if (value.isEmpty()) {
return JSC.JSValue.jsNull();
}
const array_buffer = value.asArrayBuffer(globalThis) orelse {
return JSC.toInvalidArguments("Expected ArrayBufferView but received {s}", .{@tagName(value.jsType())}, globalThis);
};
if (array_buffer.len == 0) {
return JSC.toInvalidArguments("ArrayBufferView must have a length > 0. A pointer to empty memory doesn't work", .{}, globalThis);
}
var addr: usize = @intFromPtr(array_buffer.ptr);
// const Sizes = @import("../bindings/sizes.zig");
// std.debug.assert(addr == @intFromPtr(value.asEncoded().ptr) + Sizes.Bun_FFI_PointerOffsetToTypedArrayVector);
if (byteOffset) |off| {
if (!off.isEmptyOrUndefinedOrNull()) {
if (!off.isNumber()) {
return JSC.toInvalidArguments("Expected number for byteOffset", .{}, globalThis);
}
}
const bytei64 = off.toInt64();
if (bytei64 < 0) {
addr -|= @as(usize, @intCast(bytei64 * -1));
} else {
addr += @as(usize, @intCast(bytei64));
}
if (addr > @intFromPtr(array_buffer.ptr) + @as(usize, array_buffer.byte_len)) {
return JSC.toInvalidArguments("byteOffset out of bounds", .{}, globalThis);
}
}
if (addr > max_addressible_memory) {
return JSC.toInvalidArguments("Pointer is outside max addressible memory, which usually means a bug in your program.", .{}, globalThis);
}
if (addr == 0) {
return JSC.toInvalidArguments("Pointer must not be 0", .{}, globalThis);
}
if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
return JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis);
}
if (comptime Environment.allow_assert) {
std.debug.assert(JSC.JSValue.fromPtrAddress(addr).asPtrAddress() == addr);
}
return JSC.JSValue.fromPtrAddress(addr);
}
const ValueOrError = union(enum) {
err: JSValue,
slice: []u8,
};
pub fn getPtrSlice(globalThis: *JSGlobalObject, value: JSValue, byteOffset: ?JSValue, byteLength: ?JSValue) ValueOrError {
if (!value.isNumber()) {
return .{ .err = JSC.toInvalidArguments("ptr must be a number.", .{}, globalThis) };
}
const num = value.asPtrAddress();
if (num == 0) {
return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis) };
}
// if (!std.math.isFinite(num)) {
// return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis) };
// }
var addr = @as(usize, @bitCast(num));
if (byteOffset) |byte_off| {
if (byte_off.isNumber()) {
const off = byte_off.toInt64();
if (off < 0) {
addr -|= @as(usize, @intCast(off * -1));
} else {
addr +|= @as(usize, @intCast(off));
}
if (addr == 0) {
return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis) };
}
if (!std.math.isFinite(byte_off.asNumber())) {
return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis) };
}
} else if (!byte_off.isEmptyOrUndefinedOrNull()) {
// do nothing
} else {
return .{ .err = JSC.toInvalidArguments("Expected number for byteOffset", .{}, globalThis) };
}
}
if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
return .{ .err = JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis) };
}
if (byteLength) |valueLength| {
if (!valueLength.isEmptyOrUndefinedOrNull()) {
if (!valueLength.isNumber()) {
return .{ .err = JSC.toInvalidArguments("length must be a number.", .{}, globalThis) };
}
if (valueLength.asNumber() == 0.0) {
return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis) };
}
const length_i = valueLength.toInt64();
if (length_i < 0) {
return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis) };
}
if (length_i > max_addressible_memory) {
return .{ .err = JSC.toInvalidArguments("length exceeds max addressable memory. This usually means a bug in your code.", .{}, globalThis) };
}
const length = @as(usize, @intCast(length_i));
return .{ .slice = @as([*]u8, @ptrFromInt(addr))[0..length] };
}
}
return .{ .slice = bun.span(@as([*:0]u8, @ptrFromInt(addr))) };
}
fn getCPtr(value: JSValue) ?usize {
// pointer to C function
if (value.isNumber()) {
const addr = value.asPtrAddress();
if (addr > 0) return addr;
} else if (value.isBigInt()) {
const addr = @as(u64, @bitCast(value.toUInt64NoTruncate()));
if (addr > 0) {
return addr;
}
}
return null;
}
pub fn toArrayBuffer(
globalThis: *JSGlobalObject,
value: JSValue,
byteOffset: ?JSValue,
valueLength: ?JSValue,
finalizationCtxOrPtr: ?JSValue,
finalizationCallback: ?JSValue,
) JSC.JSValue {
switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
.err => |erro| {
return erro;
},
.slice => |slice| {
var callback: JSC.C.JSTypedArrayBytesDeallocator = null;
var ctx: ?*anyopaque = null;
if (finalizationCallback) |callback_value| {
if (getCPtr(callback_value)) |callback_ptr| {
callback = @as(JSC.C.JSTypedArrayBytesDeallocator, @ptrFromInt(callback_ptr));
if (finalizationCtxOrPtr) |ctx_value| {
if (getCPtr(ctx_value)) |ctx_ptr| {
ctx = @as(*anyopaque, @ptrFromInt(ctx_ptr));
} else if (!ctx_value.isUndefinedOrNull()) {
return JSC.toInvalidArguments("Expected user data to be a C pointer (number or BigInt)", .{}, globalThis);
}
}
} else if (!callback_value.isEmptyOrUndefinedOrNull()) {
return JSC.toInvalidArguments("Expected callback to be a C pointer (number or BigInt)", .{}, globalThis);
}
} else if (finalizationCtxOrPtr) |callback_value| {
if (getCPtr(callback_value)) |callback_ptr| {
callback = @as(JSC.C.JSTypedArrayBytesDeallocator, @ptrFromInt(callback_ptr));
} else if (!callback_value.isEmptyOrUndefinedOrNull()) {
return JSC.toInvalidArguments("Expected callback to be a C pointer (number or BigInt)", .{}, globalThis);
}
}
return JSC.ArrayBuffer.fromBytes(slice, JSC.JSValue.JSType.ArrayBuffer).toJSWithContext(globalThis, ctx, callback, null);
},
}
}
pub fn toBuffer(
globalThis: *JSGlobalObject,
value: JSValue,
byteOffset: ?JSValue,
valueLength: ?JSValue,
finalizationCtxOrPtr: ?JSValue,
finalizationCallback: ?JSValue,
) JSC.JSValue {
switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
.err => |erro| {
return erro;
},
.slice => |slice| {
var callback: JSC.C.JSTypedArrayBytesDeallocator = null;
var ctx: ?*anyopaque = null;
if (finalizationCallback) |callback_value| {
if (getCPtr(callback_value)) |callback_ptr| {
callback = @as(JSC.C.JSTypedArrayBytesDeallocator, @ptrFromInt(callback_ptr));
if (finalizationCtxOrPtr) |ctx_value| {
if (getCPtr(ctx_value)) |ctx_ptr| {
ctx = @as(*anyopaque, @ptrFromInt(ctx_ptr));
} else if (!ctx_value.isEmptyOrUndefinedOrNull()) {
return JSC.toInvalidArguments("Expected user data to be a C pointer (number or BigInt)", .{}, globalThis);
}
}
} else if (!callback_value.isEmptyOrUndefinedOrNull()) {
return JSC.toInvalidArguments("Expected callback to be a C pointer (number or BigInt)", .{}, globalThis);
}
} else if (finalizationCtxOrPtr) |callback_value| {
if (getCPtr(callback_value)) |callback_ptr| {
callback = @as(JSC.C.JSTypedArrayBytesDeallocator, @ptrFromInt(callback_ptr));
} else if (!callback_value.isEmptyOrUndefinedOrNull()) {
return JSC.toInvalidArguments("Expected callback to be a C pointer (number or BigInt)", .{}, globalThis);
}
}
if (callback != null or ctx != null) {
return JSC.JSValue.createBufferWithCtx(globalThis, slice, ctx, callback);
}
return JSC.JSValue.createBuffer(globalThis, slice, null);
},
}
}
pub fn toCStringBuffer(
globalThis: *JSGlobalObject,
value: JSValue,
byteOffset: ?JSValue,
valueLength: ?JSValue,
) JSC.JSValue {
switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
.err => |erro| {
return erro;
},
.slice => |slice| {
return JSC.JSValue.createBuffer(globalThis, slice, null);
},
}
}
pub fn getter(
globalObject: *JSC.JSGlobalObject,
_: *JSC.JSObject,
) callconv(.C) JSC.JSValue {
return FFIObject.toJS(globalObject);
}
};
/// EnvironmentVariables is runtime defined.
/// Also, you can't iterate over process.env normally since it only exists at build-time otherwise
// This is aliased to Bun.env
pub const EnvironmentVariables = struct {
pub export fn Bun__getEnvCount(globalObject: *JSC.JSGlobalObject, ptr: *[*][]const u8) usize {
const bunVM = globalObject.bunVM();
ptr.* = bunVM.bundler.env.map.map.keys().ptr;
return bunVM.bundler.env.map.map.unmanaged.entries.len;
}
pub export fn Bun__getEnvKey(ptr: [*][]const u8, i: usize, data_ptr: *[*]const u8) usize {
const item = ptr[i];
data_ptr.* = item.ptr;
return item.len;
}
pub export fn Bun__getEnvValue(globalObject: *JSC.JSGlobalObject, name: *ZigString, value: *ZigString) bool {
if (getEnvValue(globalObject, name.*)) |val| {
value.* = val;
return true;
}
return false;
}
pub fn getEnvNames(globalObject: *JSC.JSGlobalObject, names: []ZigString) usize {
var vm = globalObject.bunVM();
const keys = vm.bundler.env.map.map.keys();
const len = @min(names.len, keys.len);
for (keys[0..len], names[0..len]) |key, *name| {
name.* = ZigString.initUTF8(key);
}
return len;
}
pub fn getEnvValue(globalObject: *JSC.JSGlobalObject, name: ZigString) ?ZigString {
var vm = globalObject.bunVM();
var sliced = name.toSlice(vm.allocator);
defer sliced.deinit();
const value = vm.bundler.env.map.get(sliced.slice()) orelse return null;
return ZigString.initUTF8(value);
}
};
export fn Bun__reportError(globalObject: *JSGlobalObject, err: JSC.JSValue) void {
JSC.VirtualMachine.runErrorHandlerWithDedupe(globalObject.bunVM(), err, null);
}
comptime {
if (!is_bindgen) {
_ = Bun__reportError;
_ = EnvironmentVariables.Bun__getEnvCount;
_ = EnvironmentVariables.Bun__getEnvKey;
_ = EnvironmentVariables.Bun__getEnvValue;
}
}
pub const JSZlib = struct {
export fn reader_deallocator(_: ?*anyopaque, ctx: ?*anyopaque) void {
var reader: *zlib.ZlibReaderArrayList = bun.cast(*zlib.ZlibReaderArrayList, ctx.?);
reader.list.deinit(reader.allocator);
reader.deinit();
}
export fn compressor_deallocator(_: ?*anyopaque, ctx: ?*anyopaque) void {
var compressor: *zlib.ZlibCompressorArrayList = bun.cast(*zlib.ZlibCompressorArrayList, ctx.?);
compressor.list.deinit(compressor.allocator);
compressor.deinit();
}
pub fn gzipSync(
globalThis: *JSGlobalObject,
buffer: JSC.Node.StringOrBuffer,
options_val_: ?JSValue,
) JSValue {
return gzipOrDeflateSync(globalThis, buffer, options_val_, true);
}
pub fn deflateSync(
globalThis: *JSGlobalObject,
buffer: JSC.Node.StringOrBuffer,
options_val_: ?JSValue,
) JSValue {
return gzipOrDeflateSync(globalThis, buffer, options_val_, false);
}
pub fn gzipOrDeflateSync(
globalThis: *JSGlobalObject,
buffer: JSC.Node.StringOrBuffer,
options_val_: ?JSValue,
is_gzip: bool,
) JSValue {
var opts = zlib.Options{ .gzip = is_gzip };
if (options_val_) |options_val| {
if (options_val.isObject()) {
if (options_val.get(globalThis, "windowBits")) |window| {
opts.windowBits = window.coerce(i32, globalThis);
}
if (options_val.get(globalThis, "level")) |level| {
opts.level = level.coerce(i32, globalThis);
}
if (options_val.get(globalThis, "memLevel")) |memLevel| {
opts.memLevel = memLevel.coerce(i32, globalThis);
}
if (options_val.get(globalThis, "strategy")) |strategy| {
opts.strategy = strategy.coerce(i32, globalThis);
}
}
}
var compressed = buffer.slice();
const allocator = JSC.VirtualMachine.get().allocator;
var list = std.ArrayListUnmanaged(u8).initCapacity(allocator, if (compressed.len > 512) compressed.len else 32) catch unreachable;
var reader = zlib.ZlibCompressorArrayList.init(compressed, &list, allocator, opts) catch |err| {
if (err == error.InvalidArgument) {
return JSC.toInvalidArguments("Invalid buffer", .{}, globalThis);
}
return JSC.toInvalidArguments("Unexpected", .{}, globalThis);
};
reader.readAll() catch {
defer reader.deinit();
if (reader.errorMessage()) |msg| {
return ZigString.init(msg).toErrorInstance(globalThis);
}
return ZigString.init("Zlib returned an error").toErrorInstance(globalThis);
};
reader.list = .{ .items = reader.list.toOwnedSlice(allocator) catch @panic("TODO") };
reader.list.capacity = reader.list.items.len;
reader.list_ptr = &reader.list;
var array_buffer = JSC.ArrayBuffer.fromBytes(reader.list.items, .Uint8Array);
return array_buffer.toJSWithContext(globalThis, reader, reader_deallocator, null);
}
pub fn inflateSync(
globalThis: *JSGlobalObject,
buffer: JSC.Node.StringOrBuffer,
) JSValue {
var compressed = buffer.slice();
const allocator = JSC.VirtualMachine.get().allocator;
var list = std.ArrayListUnmanaged(u8).initCapacity(allocator, if (compressed.len > 512) compressed.len else 32) catch unreachable;
var reader = zlib.ZlibReaderArrayList.initWithOptions(compressed, &list, allocator, .{
.windowBits = -15,
}) catch |err| {
if (err == error.InvalidArgument) {
return JSC.toInvalidArguments("Invalid buffer", .{}, globalThis);
}
return JSC.toInvalidArguments("Unexpected", .{}, globalThis);
};
reader.readAll() catch {
defer reader.deinit();
if (reader.errorMessage()) |msg| {
return ZigString.init(msg).toErrorInstance(globalThis);
}
return ZigString.init("Zlib returned an error").toErrorInstance(globalThis);
};
reader.list = .{ .items = reader.list.toOwnedSlice(allocator) catch @panic("TODO") };
reader.list.capacity = reader.list.items.len;
reader.list_ptr = &reader.list;
var array_buffer = JSC.ArrayBuffer.fromBytes(reader.list.items, .Uint8Array);
return array_buffer.toJSWithContext(globalThis, reader, reader_deallocator, null);
}
pub fn gunzipSync(
globalThis: *JSGlobalObject,
buffer: JSC.Node.StringOrBuffer,
) JSValue {
var compressed = buffer.slice();
const allocator = JSC.VirtualMachine.get().allocator;
var list = std.ArrayListUnmanaged(u8).initCapacity(allocator, if (compressed.len > 512) compressed.len else 32) catch unreachable;
var reader = zlib.ZlibReaderArrayList.init(compressed, &list, allocator) catch |err| {
if (err == error.InvalidArgument) {
return JSC.toInvalidArguments("Invalid buffer", .{}, globalThis);
}
return JSC.toInvalidArguments("Unexpected", .{}, globalThis);
};
reader.readAll() catch {
defer reader.deinit();
if (reader.errorMessage()) |msg| {
return ZigString.init(msg).toErrorInstance(globalThis);
}
return ZigString.init("Zlib returned an error").toErrorInstance(globalThis);
};
reader.list = .{ .items = reader.list.toOwnedSlice(allocator) catch @panic("TODO") };
reader.list.capacity = reader.list.items.len;
reader.list_ptr = &reader.list;
var array_buffer = JSC.ArrayBuffer.fromBytes(reader.list.items, .Uint8Array);
return array_buffer.toJSWithContext(globalThis, reader, reader_deallocator, null);
}
};
pub usingnamespace @import("./bun/subprocess.zig");
comptime {
if (!JSC.is_bindgen) {
_ = Crypto.JSPasswordObject.JSPasswordObject__create;
BunObject.exportAll();
}
}