Compare commits

...

13 Commits

Author SHA1 Message Date
Colin McDonnell
4f2095d1c6 Resolve outbase 2023-06-07 14:38:04 -07:00
Colin McDonnell
cfd68a4e9b Remove log 2023-06-07 11:14:21 -07:00
Colin McDonnell
dba07b8675 Remove logging 2023-06-06 23:06:37 -07:00
Colin McDonnell
599162ce73 Add resolveDir and tests 2023-06-06 23:05:13 -07:00
Jarred Sumner
ec71e7afe4 Rename bun-link-lld-debug to link 2023-06-05 18:14:44 -07:00
dave caruso
17bca62df1 add a test for lodash-es (#3217)
* add bundling tests for lodash-es

* add isBuffer tests
2023-06-05 17:38:03 -07:00
Jarred Sumner
0c11762c31 [node:vm] Fix crash when new ArrayBuffer() is returned 2023-06-05 17:17:48 -07:00
Dylan Conway
fe7d5357d8 allow v flag in regexp literal (#3213)
* add v to possible regexp flags

* alphabetical
2023-06-05 13:33:02 -07:00
Dylan Conway
568f170e12 [transpiler] Fix new length for raw template contents (#3215)
* use correct length for raw template contents

* tests for raw template contents
2023-06-05 12:55:56 -07:00
Jarred Sumner
c87d65856c [Inspector] Introduce inspector: true in Bun.serve()
This exposes the WebKit inspector debugger protocol over WebSockets at the endpoint `/bun:inspect` when `Bun.serve()`.

To enable, pass:
```js
Bun.serve({inspector: true, development: true, fetch(req){ /* rest of params *... });
```

Both `development` and `inspector` must be true, as this is very security sensitive to expose publicly.
2023-06-05 04:31:13 -07:00
Jarred Sumner
9b996e702e Implement Bun.password and Bun.passwordSync (#3204)
* Implement `Bun.password.{verify, hash}` and `Bun.passwordSync.{verify, hash}`

* flip the booleans

* delete unused

* Add `cost` for `"bcrypt"`, add `"memoryCost"` and `"timeCost'` for argon2, use SHA512

* Update bun.zig

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-06-04 18:20:04 -07:00
Dylan Conway
2cb1376a93 removeAllListeners return this (#3208)
* return this in `removeAllListeners`

* move `UNUSED_PARAM` for used params
2023-06-04 18:19:41 -07:00
Jarred Sumner
1f3da24fe0 [Transpiler] Fix normalizing \r\n in template string literals (#3209) 2023-06-04 18:07:38 -07:00
28 changed files with 1768 additions and 54 deletions

View File

@@ -2,7 +2,7 @@
"git.autoRepositoryDetection": "openEditors",
"search.quickOpen.includeSymbols": false,
"search.seedWithNearestWord": true,
"search.smartCase": true,
"search.smartCase": false,
"search.exclude": {},
"search.followSymlinks": false,
"search.useIgnoreFiles": true,

View File

@@ -1085,7 +1085,7 @@ dev-obj-linux:
$(ZIG) build obj -Dtarget=x86_64-linux-gnu -Dcpu="$(CPU_TARGET)"
.PHONY: dev
dev: mkdir-dev esm dev-obj bun-link-lld-debug
dev: mkdir-dev esm dev-obj link
mkdir-dev:
mkdir -p $(DEBUG_PACKAGE_DIR)
@@ -1356,15 +1356,21 @@ mimalloc-wasm:
cd $(BUN_DEPS_DIR)/mimalloc; emcmake cmake -DMI_BUILD_SHARED=OFF -DMI_BUILD_STATIC=ON -DMI_BUILD_TESTS=OFF -DMI_BUILD_OBJECT=ON ${MIMALLOC_OVERRIDE_FLAG} -DMI_USE_CXX=ON .; emmake make;
cp $(BUN_DEPS_DIR)/mimalloc/$(MIMALLOC_INPUT_PATH) $(BUN_DEPS_OUT_DIR)/$(MIMALLOC_FILE).wasm
bun-link-lld-debug:
# alias for link, incase anyone still types that
.PHONY: bun-link-lld-debug
bun-link-lld-debug: link
# link a debug build of bun
.PHONY: link
link:
$(CXX) $(BUN_LLD_FLAGS_DEBUG) $(DEBUG_FLAGS) $(SYMBOLS) \
-g \
$(DEBUG_BIN)/bun-debug.o \
-W \
-o $(DEBUG_BIN)/bun-debug
@rm -f $(DEBUG_BIN)/bun-debug.o.o 2> /dev/null # workaround for https://github.com/ziglang/zig/issues/14080
@rm -f $(DEBUG_BIN)/bun-debug.o.o 2> /dev/null # workaround for https://github.com/ziglang/zig/issues/14080
bun-link-lld-debug-no-jsc:
link-no-jsc:
$(CXX) $(BUN_LLD_FLAGS_WITHOUT_JSC) $(SYMBOLS) \
-g \
$(DEBUG_BIN)/bun-debug.o \

BIN
bun.lockb

Binary file not shown.

View File

@@ -1012,6 +1012,197 @@ declare module "bun" {
// importSource?: string; // default: "react"
// };
}
namespace Password {
export type AlgorithmLabel = "bcrypt" | "argon2id" | "argon2d" | "argon2i";
export interface Argon2Algorithm {
algorithm: "argon2id" | "argon2d" | "argon2i";
/**
* Memory cost, which defines the memory usage, given in kibibytes.
*/
memoryCost?: number;
/**
* Defines the amount of computation realized and therefore the execution
* time, given in number of iterations.
*/
timeCost?: number;
}
export interface BCryptAlgorithm {
algorithm: "bcrypt";
/**
* A number between 4 and 31. The default is 10.
*/
cost?: number;
}
}
/**
* Hash and verify passwords using argon2 or bcrypt. The default is argon2.
* Password hashing functions are necessarily slow, and this object will
* automatically run in a worker thread.
*
* The underlying implementation of these functions are provided by the Zig
* Standard Library. Thanks to @jedisct1 and other Zig constributors for their
* work on this.
*
* ### Example with argon2
*
* ```ts
* import {password} from "bun";
*
* const hash = await password.hash("hello world");
* const verify = await password.verify("hello world", hash);
* console.log(verify); // true
* ```
*
* ### Example with bcrypt
* ```ts
* import {password} from "bun";
*
* const hash = await password.hash("hello world", "bcrypt");
* // algorithm is optional, will be inferred from the hash if not specified
* const verify = await password.verify("hello world", hash, "bcrypt");
*
* console.log(verify); // true
* ```
*/
export const password: {
/**
* Verify a password against a previously hashed password.
*
* @returns true if the password matches, false otherwise
*
* @example
* ```ts
* import {password} from "bun";
* await password.verify("hey", "$argon2id$v=19$m=65536,t=2,p=1$ddbcyBcbAcagei7wSkZFiouX6TqnUQHmTyS5mxGCzeM$+3OIaFatZ3n6LtMhUlfWbgJyNp7h8/oIsLK+LzZO+WI");
* // true
* ```
*
* @throws If the algorithm is specified and does not match the hash
* @throws If the algorithm is invalid
* @throws if the hash is invalid
*
*/
verify(
/**
* The password to verify.
*
* If empty, always returns false
*/
password: StringOrBuffer,
/**
* Previously hashed password.
* If empty, always returns false
*/
hash: StringOrBuffer,
/**
* If not specified, the algorithm will be inferred from the hash.
*
* If specified and the algorithm does not match the hash, this function
* throws an error.
*/
algorithm?: Password.AlgorithmLabel,
): Promise<boolean>;
/**
* Asynchronously hash a password using argon2 or bcrypt. The default is argon2.
*
* @returns A promise that resolves to the hashed password
*
* ## Example with argon2
* ```ts
* import {password} from "bun";
* const hash = await password.hash("hello world");
* console.log(hash); // $argon2id$v=1...
* const verify = await password.verify("hello world", hash);
* ```
* ## Example with bcrypt
* ```ts
* import {password} from "bun";
* const hash = await password.hash("hello world", "bcrypt");
* console.log(hash); // $2b$10$...
* const verify = await password.verify("hello world", hash);
* ```
*/
hash(
/**
* The password to hash
*
* If empty, this function throws an error. It is usually a programming
* mistake to hash an empty password.
*/
password: StringOrBuffer,
/**
* @default "argon2id"
*
* When using bcrypt, passwords exceeding 72 characters will be SHA512'd before
*/
algorithm?:
| Password.AlgorithmLabel
| Password.Argon2Algorithm
| Password.BCryptAlgorithm,
): Promise<string>;
};
/**
* Synchronously hash and verify passwords using argon2 or bcrypt. The default is argon2.
* Warning: password hashing is slow, consider using {@link Bun.password}
* instead which runs in a worker thread.
*
* The underlying implementation of these functions are provided by the Zig
* Standard Library. Thanks to @jedisct1 and other Zig constributors for their
* work on this.
*
* ### Example with argon2
*
* ```ts
* import {password} from "bun";
*
* const hash = await password.hash("hello world");
* const verify = await password.verify("hello world", hash);
* console.log(verify); // true
* ```
*
* ### Example with bcrypt
* ```ts
* import {password} from "bun";
*
* const hash = await password.hash("hello world", "bcrypt");
* // algorithm is optional, will be inferred from the hash if not specified
* const verify = await password.verify("hello world", hash, "bcrypt");
*
* console.log(verify); // true
* ```
*/
export const passwordSync: {
verify(
password: StringOrBuffer,
hash: StringOrBuffer,
/**
* If not specified, the algorithm will be inferred from the hash.
*/
algorithm?: Password.AlgorithmLabel,
): boolean;
hash(
/**
* The password to hash
*
* If empty, this function throws an error. It is usually a programming
* mistake to hash an empty password.
*/
password: StringOrBuffer,
/**
* @default "argon2id"
*
* When using bcrypt, passwords exceeding 72 characters will be SHA256'd before
*/
algorithm?:
| Password.AlgorithmLabel
| Password.Argon2Algorithm
| Password.BCryptAlgorithm,
): string;
};
interface BuildArtifact extends Blob {
path: string;
@@ -2746,7 +2937,10 @@ declare module "bun" {
* The kind of import this resolve is for.
*/
kind: ImportKind;
// resolveDir: string;
/**
* The= directory of the importing module. Commonly used to resolve `args.path` to an absolute path.
*/
resolveDir: string;
// pluginData: any;
}

View File

@@ -893,6 +893,7 @@ pub const JSBundler = struct {
importer: *const ZigString,
context: *anyopaque,
u8,
resolve_dir: *const ZigString,
) void;
pub fn hasAnyMatches(
@@ -949,7 +950,10 @@ pub const JSBundler = struct {
ZigString.fromUTF8(namespace);
const path_string = ZigString.fromUTF8(path);
const importer_string = ZigString.fromUTF8(importer);
JSBundlerPlugin__matchOnResolve(globalThis, this, &namespace_string, &path_string, &importer_string, context, @enumToInt(import_record_kind));
// TODO: improve this for virtual modules
const resolve_dir = std.fs.path.dirname(importer) orelse "/";
const resolve_dir_string = ZigString.fromUTF8(resolve_dir);
JSBundlerPlugin__matchOnResolve(globalThis, this, &namespace_string, &path_string, &importer_string, context, @enumToInt(import_record_kind), &resolve_dir_string);
}
pub fn addPlugin(

View File

@@ -1632,6 +1632,803 @@ pub const Crypto = struct {
return ZigString.fromUTF8(error_message).toErrorInstance(globalThis);
}
const unknwon_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", unknwon_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 = @intCast(u6, 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 = @intCast(u32, 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 = @intCast(u32, 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", unknwon_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();
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, sync: bool) JSC.JSValue {
var object = JSValue.createEmptyObject(globalObject, 2);
object.put(
globalObject,
ZigString.static("hash"),
if (!sync)
JSC.NewFunction(globalObject, ZigString.static("hash"), 2, JSPasswordObject__hash, false)
else
JSC.NewFunction(globalObject, ZigString.static("hash"), 2, JSPasswordObject__hashSync, false),
);
object.put(
globalObject,
ZigString.static("verify"),
if (!sync)
JSC.NewFunction(globalObject, ZigString.static("verify"), 2, JSPasswordObject__verify, false)
else
JSC.NewFunction(globalObject, ZigString.static("verify"), 2, JSPasswordObject__verifySync, false),
);
return object;
}
const HashJob = struct {
algorithm: PasswordObject.Algorithm.Value,
password: []const u8,
promise: JSC.JSPromise.Strong,
event_loop: *JSC.EventLoop,
global: *JSC.JSGlobalObject,
ref: JSC.PollRef = .{},
task: JSC.WorkPoolTask = .{ .callback = &run },
pub const Result = struct {
value: Value,
ref: JSC.PollRef = .{},
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: JSC.PollRef = .{},
task: JSC.WorkPoolTask = .{ .callback = &run },
pub const Result = struct {
value: Value,
ref: JSC.PollRef = .{},
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", unknwon_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", unknwon_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,
@@ -4306,3 +5103,9 @@ pub const JSZlib = struct {
};
pub usingnamespace @import("./bun/subprocess.zig");
comptime {
if (!JSC.is_bindgen) {
_ = Crypto.JSPasswordObject.JSPasswordObject__create;
}
}

View File

@@ -136,6 +136,8 @@ pub const ServerConfig = struct {
websocket: ?WebSocketServer = null,
inspector: bool = false,
pub const SSLConfig = struct {
server_name: [*c]const u8 = null,
@@ -741,7 +743,19 @@ pub const ServerConfig = struct {
}
if (arg.get(global, "development")) |dev| {
args.development = dev.toBoolean();
args.development = dev.coerce(bool, global);
}
if (arg.get(global, "inspector")) |inspector| {
args.inspector = inspector.coerce(bool, global);
if (args.inspector and !args.development) {
JSC.throwInvalidArguments("Cannot enable inspector in production. Please set development: true in Bun.serve()", .{}, global, exception);
if (args.ssl_config) |*conf| {
conf.deinit();
}
return args;
}
}
if (arg.getTruthy(global, "tls")) |tls| {
@@ -5239,6 +5253,10 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
if (comptime debug_mode) {
this.app.get("/bun:info", *ThisServer, this, onBunInfoRequest);
if (this.config.inspector) {
Bun__addInspector(ssl_enabled, this.app, this.globalThis);
}
this.app.get("/src:/*", *ThisServer, this, onSrcRequest);
}
@@ -5290,3 +5308,5 @@ pub const AnyServer = union(enum) {
};
const welcome_page_html_gz = @embedFile("welcome-page.html.gz");
extern fn Bun__addInspector(bool, *anyopaque, *JSC.JSGlobalObject) void;

View File

@@ -0,0 +1,184 @@
#include "root.h"
#include <uws/src/App.h>
#include <JavaScriptCore/InspectorFrontendChannel.h>
#include <JavaScriptCore/JSGlobalObjectDebuggable.h>
namespace Bun {
using namespace JSC;
template<bool isSSL>
class BunInspectorConnection : public Inspector::FrontendChannel {
public:
using BunInspectorSocket = uWS::WebSocket<isSSL, true, BunInspectorConnection*>;
BunInspectorConnection(BunInspectorSocket* ws, JSC::JSGlobalObject* globalObject)
: ws(ws)
, globalObject(globalObject)
, pendingMessages()
{
}
~BunInspectorConnection()
{
}
Inspector::FrontendChannel::ConnectionType connectionType() const override
{
return Inspector::FrontendChannel::ConnectionType::Remote;
}
void onOpen(JSC::JSGlobalObject* globalObject)
{
this->globalObject = globalObject;
this->globalObject->inspectorDebuggable().connect(*this);
}
void onClose()
{
this->globalObject->inspectorDebuggable().disconnect(*this);
this->pendingMessages.clear();
}
void sendMessageToFrontend(const String& message) override
{
send(message);
}
void send(const WTF::String& message)
{
if (ws->getBufferedAmount() == 0) {
WTF::CString messageCString = message.utf8();
ws->send(std::string_view { messageCString.data(), messageCString.length() }, uWS::OpCode::TEXT);
} else {
pendingMessages.append(message);
}
}
void onMessage(std::string_view message)
{
WTF::String messageString = WTF::String::fromUTF8(message.data(), message.length());
this->globalObject->inspectorDebuggable().dispatchMessageFromRemote(WTFMove(messageString));
}
void drain()
{
if (pendingMessages.size() == 0)
return;
if (ws->getBufferedAmount() == 0) {
ws->cork([&]() {
for (auto& message : pendingMessages) {
WTF::CString messageCString = message.utf8();
ws->send(std::string_view { messageCString.data(), messageCString.length() }, uWS::OpCode::TEXT);
}
pendingMessages.clear();
});
}
}
WTF::Vector<WTF::String> pendingMessages;
JSC::JSGlobalObject* globalObject;
BunInspectorSocket* ws;
};
using BunInspectorConnectionNoSSL = BunInspectorConnection<false>;
using SSLBunInspectorConnection = BunInspectorConnection<true>;
template<bool isSSL>
static void addInspector(void* app, JSC::JSGlobalObject* globalObject)
{
if constexpr (isSSL) {
auto handler = uWS::SSLApp::WebSocketBehavior<SSLBunInspectorConnection*> {
/* Settings */
.compression = uWS::DISABLED,
.maxPayloadLength = 16 * 1024 * 1024,
.idleTimeout = 960,
.maxBackpressure = 16 * 1024 * 1024,
.closeOnBackpressureLimit = false,
.resetIdleTimeoutOnSend = true,
.sendPingsAutomatically = true,
/* Handlers */
.upgrade = nullptr,
.open = [globalObject](auto* ws) {
globalObject->setInspectable(true);
*ws->getUserData() = new SSLBunInspectorConnection(ws, globalObject);
SSLBunInspectorConnection* inspector = *ws->getUserData();
inspector->onOpen(globalObject);
//
},
.message = [](auto* ws, std::string_view message, uWS::OpCode opCode) {
SSLBunInspectorConnection* inspector = *(SSLBunInspectorConnection**)ws->getUserData();
inspector->onMessage(message);
//
},
.drain = [](auto* ws) {
SSLBunInspectorConnection* inspector = *(SSLBunInspectorConnection**)ws->getUserData();
inspector->drain();
//
},
.ping = [](auto* /*ws*/, std::string_view) {
/* Not implemented yet */ },
.pong = [](auto* /*ws*/, std::string_view) {
/* Not implemented yet */ },
.close = [](auto* ws, int /*code*/, std::string_view /*message*/) {
SSLBunInspectorConnection* inspector = *(SSLBunInspectorConnection**)ws->getUserData();
inspector->onClose();
delete inspector; }
};
((uWS::SSLApp*)app)->ws<SSLBunInspectorConnection*>("/bun:inspect", std::move(handler));
} else {
auto handler = uWS::App::WebSocketBehavior<BunInspectorConnectionNoSSL*> {
/* Settings */
.compression = uWS::DISABLED,
.maxPayloadLength = 16 * 1024 * 1024,
.idleTimeout = 960,
.maxBackpressure = 16 * 1024 * 1024,
.closeOnBackpressureLimit = false,
.resetIdleTimeoutOnSend = true,
.sendPingsAutomatically = true,
/* Handlers */
.upgrade = nullptr,
.open = [globalObject](auto* ws) {
globalObject->setInspectable(true);
*ws->getUserData() = new BunInspectorConnectionNoSSL(ws, globalObject);
BunInspectorConnectionNoSSL* inspector = *ws->getUserData();
inspector->onOpen(globalObject);
//
},
.message = [](auto* ws, std::string_view message, uWS::OpCode opCode) {
BunInspectorConnectionNoSSL* inspector = *(BunInspectorConnectionNoSSL**)ws->getUserData();
inspector->onMessage(message);
//
},
.drain = [](auto* ws) {
BunInspectorConnectionNoSSL* inspector = *(BunInspectorConnectionNoSSL**)ws->getUserData();
inspector->drain();
//
},
.ping = [](auto* /*ws*/, std::string_view) {
/* Not implemented yet */ },
.pong = [](auto* /*ws*/, std::string_view) {
/* Not implemented yet */ },
.close = [](auto* ws, int /*code*/, std::string_view /*message*/) {
BunInspectorConnectionNoSSL* inspector = *(BunInspectorConnectionNoSSL**)ws->getUserData();
inspector->onClose();
delete inspector; }
};
((uWS::App*)app)->ws<BunInspectorConnectionNoSSL*>("/bun:inspect", std::move(handler));
}
}
extern "C" void Bun__addInspector(bool isSSL, void* app, JSC::JSGlobalObject* globalObject)
{
if (isSSL) {
addInspector<true>((uWS::TemplatedApp<true>*)app, globalObject);
} else {
addInspector<false>((uWS::TemplatedApp<false>*)app, globalObject);
}
};
}

View File

@@ -315,7 +315,7 @@ extern "C" void JSBundlerPlugin__matchOnLoad(JSC::JSGlobalObject* globalObject,
}
}
extern "C" void JSBundlerPlugin__matchOnResolve(JSC::JSGlobalObject* globalObject, Bun::JSBundlerPlugin* plugin, const ZigString* namespaceString, const ZigString* path, const ZigString* importer, void* context, uint8_t kindId)
extern "C" void JSBundlerPlugin__matchOnResolve(JSC::JSGlobalObject* globalObject, Bun::JSBundlerPlugin* plugin, const ZigString* namespaceString, const ZigString* path, const ZigString* importer, void* context, uint8_t kindId, const ZigString* resolveDir)
{
WTF::String namespaceStringStr = namespaceString ? Zig::toStringCopy(*namespaceString) : WTF::String("file"_s);
if (namespaceStringStr.length() == 0) {
@@ -323,6 +323,7 @@ extern "C" void JSBundlerPlugin__matchOnResolve(JSC::JSGlobalObject* globalObjec
}
WTF::String pathStr = path ? Zig::toStringCopy(*path) : WTF::String();
WTF::String importerStr = importer ? Zig::toStringCopy(*importer) : WTF::String();
WTF::String resolveDirStr = resolveDir ? Zig::toStringCopy(*resolveDir) : WTF::String();
auto& vm = globalObject->vm();
JSFunction* function = plugin->onResolveFunction.get(plugin);
@@ -341,6 +342,7 @@ extern "C" void JSBundlerPlugin__matchOnResolve(JSC::JSGlobalObject* globalObjec
arguments.append(JSC::jsString(vm, importerStr));
arguments.append(WRAP_BUNDLER_PLUGIN(context));
arguments.append(JSC::jsNumber(kindId));
arguments.append(JSC::jsString(vm, resolveDirStr));
auto result = call(globalObject, function, callData, plugin, arguments);

View File

@@ -361,6 +361,17 @@ extern "C" bool Zig__GlobalObject__resetModuleRegistryMap(JSC__JSGlobalObject* g
return true;
}
#define BUN_LAZY_GETTER_FN_NAME(GetterName) BunLazyGetter##GetterName##_getter
#define DEFINE_BUN_LAZY_GETTER(GetterName, __propertyName) \
JSC_DEFINE_CUSTOM_GETTER(GetterName, \
(JSC::JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, \
JSC::PropertyName)) \
{ \
Zig::GlobalObject* thisObject = JSC::jsCast<Zig::GlobalObject*>(lexicalGlobalObject); \
return JSC::JSValue::encode(thisObject->__propertyName()); \
}
#define GENERATED_CONSTRUCTOR_GETTER(ConstructorName) \
JSC_DECLARE_CUSTOM_GETTER(ConstructorName##_getter); \
JSC_DEFINE_CUSTOM_GETTER(ConstructorName##_getter, \
@@ -2492,6 +2503,7 @@ JSC::JSValue GlobalObject::formatStackTrace(JSC::VM& vm, JSC::JSGlobalObject* le
}
extern "C" void Bun__remapStackFramePositions(JSC::JSGlobalObject*, ZigStackFrame*, size_t);
extern "C" EncodedJSValue JSPasswordObject__create(JSC::JSGlobalObject*, bool);
JSC_DECLARE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace);
JSC_DEFINE_HOST_FUNCTION(errorConstructorFuncCaptureStackTrace, (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame))
@@ -2599,6 +2611,24 @@ void GlobalObject::finishCreation(VM& vm)
init.set(result.toObject(globalObject));
});
m_lazyPasswordObject.initLater(
[](const Initializer<JSObject>& init) {
JSC::VM& vm = init.vm;
JSC::JSGlobalObject* globalObject = init.owner;
JSValue result = JSValue::decode(JSPasswordObject__create(globalObject, false));
init.set(result.toObject(globalObject));
});
m_lazyPasswordSyncObject.initLater(
[](const Initializer<JSObject>& init) {
JSC::VM& vm = init.vm;
JSC::JSGlobalObject* globalObject = init.owner;
JSValue result = JSValue::decode(JSPasswordObject__create(globalObject, true));
init.set(result.toObject(globalObject));
});
m_lazyPreloadTestModuleObject.initLater(
[](const Initializer<JSObject>& init) {
JSC::VM& vm = init.vm;
@@ -3525,6 +3555,9 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm)
extern "C" void Crypto__randomUUID__put(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value);
extern "C" void Crypto__getRandomValues__put(JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value);
DEFINE_BUN_LAZY_GETTER(BUN_LAZY_GETTER_FN_NAME(password), passwordObject)
DEFINE_BUN_LAZY_GETTER(BUN_LAZY_GETTER_FN_NAME(passwordSync), passwordSyncObject)
// This is not a publicly exposed API currently.
// This is used by the bundler to make Response, Request, FetchEvent,
// and any other objects available globally.
@@ -3583,6 +3616,19 @@ void GlobalObject::installAPIGlobals(JSClassRef* globals, int count, JSC::VM& vm
JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontDelete | 0);
}
// TODO: code generate these
{
JSC::Identifier identifier = JSC::Identifier::fromString(vm, "password"_s);
object->putDirectCustomAccessor(vm, identifier, JSC::CustomGetterSetter::create(vm, BUN_LAZY_GETTER_FN_NAME(password), nullptr),
JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
}
{
JSC::Identifier identifier = JSC::Identifier::fromString(vm, "passwordSync"_s);
object->putDirectCustomAccessor(vm, identifier, JSC::CustomGetterSetter::create(vm, BUN_LAZY_GETTER_FN_NAME(passwordSync), nullptr),
JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | 0);
}
{
JSC::Identifier identifier = JSC::Identifier::fromString(vm, "readableStreamToArrayBuffer"_s);
object->putDirectBuiltinFunction(vm, this, identifier, readableStreamReadableStreamToArrayBufferCodeGenerator(vm),
@@ -3853,6 +3899,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_lazyTestModuleObject.visit(visitor);
thisObject->m_lazyPreloadTestModuleObject.visit(visitor);
thisObject->m_commonJSModuleObjectStructure.visit(visitor);
thisObject->m_lazyPasswordObject.visit(visitor);
thisObject->m_lazyPasswordSyncObject.visit(visitor);
thisObject->m_commonJSFunctionArgumentsStructure.visit(visitor);
thisObject->m_cachedGlobalObjectStructure.visit(visitor);
thisObject->m_cachedGlobalProxyStructure.visit(visitor);
@@ -4094,7 +4142,6 @@ JSC::JSObject* GlobalObject::moduleLoaderCreateImportMetaProperties(JSGlobalObje
JSModuleRecord* record,
JSValue val)
{
JSC::VM& vm = globalObject->vm();
JSC::JSString* keyString = key.toStringOrNull(globalObject);
if (UNLIKELY(!keyString))
@@ -4108,7 +4155,6 @@ JSC::JSValue GlobalObject::moduleLoaderEvaluate(JSGlobalObject* globalObject,
JSValue moduleRecordValue, JSValue scriptFetcher,
JSValue sentValue, JSValue resumeMode)
{
if (UNLIKELY(scriptFetcher && scriptFetcher.isObject())) {
return scriptFetcher;
}

View File

@@ -264,6 +264,9 @@ public:
Structure* commonJSFunctionArgumentsStructure() { return m_commonJSFunctionArgumentsStructure.getInitializedOnMainThread(this); }
JSObject* passwordSyncObject() { return m_lazyPasswordSyncObject.getInitializedOnMainThread(this); }
JSObject* passwordObject() { return m_lazyPasswordObject.getInitializedOnMainThread(this); }
JSWeakMap* vmModuleContextMap() { return m_vmModuleContextMap.getInitializedOnMainThread(this); }
JSC::JSObject* processObject()
@@ -476,6 +479,8 @@ private:
LazyProperty<JSGlobalObject, JSObject> m_lazyRequireCacheObject;
LazyProperty<JSGlobalObject, JSObject> m_lazyTestModuleObject;
LazyProperty<JSGlobalObject, JSObject> m_lazyPreloadTestModuleObject;
LazyProperty<JSGlobalObject, JSObject> m_lazyPasswordSyncObject;
LazyProperty<JSGlobalObject, JSObject> m_lazyPasswordObject;
LazyProperty<JSGlobalObject, JSFunction> m_bunSleepThenCallback;
LazyProperty<JSGlobalObject, Structure> m_cachedGlobalObjectStructure;

View File

@@ -224,8 +224,6 @@ static inline JSC::EncodedJSValue addListener(JSC::JSGlobalObject* lexicalGlobal
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
JSC::JSValue actualThis = callFrame->thisValue();
UNUSED_PARAM(throwScope);
UNUSED_PARAM(callFrame);
auto& impl = castedThis->wrapped();
if (UNLIKELY(callFrame->argumentCount() < 2))
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
@@ -330,8 +328,6 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_removeListener
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
JSC::JSValue actualThis = callFrame->thisValue();
UNUSED_PARAM(throwScope);
UNUSED_PARAM(callFrame);
auto& impl = castedThis->wrapped();
if (UNLIKELY(callFrame->argumentCount() < 1))
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
@@ -362,12 +358,11 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_removeAllListe
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(throwScope);
UNUSED_PARAM(callFrame);
JSValue actualThis = callFrame->thisValue();
auto& impl = castedThis->wrapped();
if (callFrame->argumentCount() == 0) {
impl.removeAllListeners();
return JSValue::encode(jsUndefined());
RELEASE_AND_RETURN(throwScope, JSValue::encode(actualThis));
}
EnsureStillAliveScope argument0 = callFrame->uncheckedArgument(0);
@@ -375,7 +370,8 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_removeAllListe
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
auto result = JSValue::encode(toJS<IDLUndefined>(*lexicalGlobalObject, throwScope, [&]() -> decltype(auto) { return impl.removeAllListenersForBindings(WTFMove(eventType)); }));
RETURN_IF_EXCEPTION(throwScope, encodedJSValue());
return result;
impl.setThisObject(actualThis);
RELEASE_AND_RETURN(throwScope, JSValue::encode(actualThis));
}
JSC_DEFINE_HOST_FUNCTION(jsEventEmitterPrototypeFunction_removeAllListeners, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
@@ -387,8 +383,6 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_emitBody(JSC::
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(throwScope);
UNUSED_PARAM(callFrame);
auto& impl = castedThis->wrapped();
size_t argumentCount = callFrame->argumentCount();
if (UNLIKELY(argumentCount < 1))
@@ -411,7 +405,6 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_eventNamesBody
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(throwScope);
UNUSED_PARAM(callFrame);
auto& impl = castedThis->wrapped();
JSC::MarkedArgumentBuffer args;
@@ -430,8 +423,6 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_listenerCountB
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(throwScope);
UNUSED_PARAM(callFrame);
auto& impl = castedThis->wrapped();
if (UNLIKELY(callFrame->argumentCount() < 1))
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
@@ -449,8 +440,6 @@ static inline JSC::EncodedJSValue jsEventEmitterPrototypeFunction_listenersBody(
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(throwScope);
UNUSED_PARAM(callFrame);
auto& impl = castedThis->wrapped();
if (UNLIKELY(callFrame->argumentCount() < 1))
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
@@ -534,8 +523,6 @@ JSC_DEFINE_HOST_FUNCTION(Events_functionGetEventListeners,
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(throwScope);
UNUSED_PARAM(callFrame);
if (UNLIKELY(callFrame->argumentCount() < 2))
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
auto argument0 = jsEventEmitterCast(vm, lexicalGlobalObject, callFrame->uncheckedArgument(0));
@@ -558,8 +545,6 @@ JSC_DEFINE_HOST_FUNCTION(Events_functionListenerCount,
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(throwScope);
UNUSED_PARAM(callFrame);
if (UNLIKELY(callFrame->argumentCount() < 2))
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
auto argument0 = jsEventEmitterCast(vm, lexicalGlobalObject, callFrame->uncheckedArgument(0));
@@ -578,8 +563,6 @@ JSC_DEFINE_HOST_FUNCTION(Events_functionOnce,
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(throwScope);
UNUSED_PARAM(callFrame);
if (UNLIKELY(callFrame->argumentCount() < 3))
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));
@@ -604,8 +587,6 @@ JSC_DEFINE_HOST_FUNCTION(Events_functionOn,
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
UNUSED_PARAM(throwScope);
UNUSED_PARAM(callFrame);
if (UNLIKELY(callFrame->argumentCount() < 3))
return throwVMError(lexicalGlobalObject, throwScope, createNotEnoughArgumentsError(lexicalGlobalObject));

View File

@@ -51,6 +51,10 @@ JSC::JSArrayBuffer* WebCoreTypedArrayController::toJS(JSC::JSGlobalObject* lexic
void WebCoreTypedArrayController::registerWrapper(JSC::JSGlobalObject* globalObject, JSC::ArrayBuffer* native, JSC::JSArrayBuffer* wrapper)
{
// require("vm") can be used to create an ArrayBuffer
if (UNLIKELY(!globalObject->inherits<JSDOMGlobalObject>()))
return;
cacheWrapper(JSC::jsCast<JSDOMGlobalObject*>(globalObject)->world(), native, wrapper);
}

View File

@@ -312,6 +312,18 @@ pub const SliceOrBuffer = union(Tag) {
string: JSC.ZigString.Slice,
buffer: Buffer,
pub fn ensureCloned(this: *SliceOrBuffer, allocator: std.mem.Allocator) !void {
if (this.* == .string) {
this.string = try this.string.cloneIfNeeded(allocator);
return;
}
const bytes = this.buffer.buffer.byteSlice();
this.* = .{
.string = JSC.ZigString.Slice.from(try allocator.dupe(u8, bytes), allocator),
};
}
pub fn deinit(this: SliceOrBuffer) void {
switch (this) {
.string => {

View File

@@ -25,7 +25,7 @@ pub const huge_allocator_threshold: comptime_int = @import("./memory_allocator.z
pub const fs_allocator = default_allocator;
pub const C = @import("c.zig");
pub const sha = @import("./sha.zig");
pub const FeatureFlags = @import("feature_flags.zig");
pub const meta = @import("./meta.zig");
pub const ComptimeStringMap = @import("./comptime_string_map.zig").ComptimeStringMap;

View File

@@ -189,11 +189,19 @@ export function runSetupFunction(this: BundlerPlugin, setup: Setup, config: Buil
return processSetupResult();
}
export function runOnResolvePlugins(this: BundlerPlugin, specifier, inputNamespace, importer, internalID, kindId) {
export function runOnResolvePlugins(
this: BundlerPlugin,
specifier,
inputNamespace,
importer,
internalID,
kindId,
resolveDir,
) {
// Must be kept in sync with ImportRecord.label
const kind = $ImportKindIdToLabel[kindId];
var promiseResult: any = (async (inputPath, inputNamespace, importer, kind) => {
var promiseResult: any = (async (inputPath, inputNamespace, importer, kind, resolveDir) => {
var { onResolve, onLoad } = this;
var results = onResolve.$get(inputNamespace);
if (!results) {
@@ -207,7 +215,7 @@ export function runOnResolvePlugins(this: BundlerPlugin, specifier, inputNamespa
path: inputPath,
importer,
namespace: inputNamespace,
// resolveDir
resolveDir,
kind,
// pluginData
});
@@ -229,8 +237,12 @@ export function runOnResolvePlugins(this: BundlerPlugin, specifier, inputNamespa
}
var { path, namespace: userNamespace = inputNamespace, external } = result;
if (!(typeof path === "string") || !(typeof userNamespace === "string")) {
throw new TypeError("onResolve plugins must return an object with a string 'path' and string 'loader' field");
if (!(typeof path === "string")) {
throw new TypeError("onResolve: expected 'path' to be a string");
}
if (!(typeof userNamespace === "string")) {
throw new TypeError("onResolve: expected 'namespace' to be a string");
}
if (!path) {
@@ -241,7 +253,7 @@ export function runOnResolvePlugins(this: BundlerPlugin, specifier, inputNamespa
userNamespace = inputNamespace;
}
if (typeof external !== "boolean" && !$isUndefinedOrNull(external)) {
throw new TypeError('onResolve plugins "external" field must be boolean or unspecified');
throw new TypeError("onResolve: expected 'external' to be boolean");
}
if (!external) {
@@ -271,7 +283,7 @@ export function runOnResolvePlugins(this: BundlerPlugin, specifier, inputNamespa
this.onResolveAsync(internalID, null, null, null);
return null;
})(specifier, inputNamespace, importer, kind);
})(specifier, inputNamespace, importer, kind, resolveDir);
while (
promiseResult &&
@@ -329,6 +341,9 @@ export function runOnLoadPlugins(this: BundlerPlugin, internalID, path, namespac
}
var { contents, loader = defaultLoader } = result as OnLoadResultSourceCode & OnLoadResultObject;
// TODO: Support "object" loader
if (!(typeof contents === "string") && !$isTypedArrayView(contents)) {
throw new TypeError('onLoad plugins must return an object with "contents" as a string or Uint8Array');
}

View File

@@ -22,9 +22,9 @@ const char* const s_bundlerPluginRunSetupFunctionCode = "(function (_,E){\"use s
const JSC::ConstructAbility s_bundlerPluginRunOnResolvePluginsCodeConstructAbility = JSC::ConstructAbility::CannotConstruct;
const JSC::ConstructorKind s_bundlerPluginRunOnResolvePluginsCodeConstructorKind = JSC::ConstructorKind::None;
const JSC::ImplementationVisibility s_bundlerPluginRunOnResolvePluginsCodeImplementationVisibility = JSC::ImplementationVisibility::Public;
const int s_bundlerPluginRunOnResolvePluginsCodeLength = 1711;
const int s_bundlerPluginRunOnResolvePluginsCodeLength = 1732;
static const JSC::Intrinsic s_bundlerPluginRunOnResolvePluginsCodeIntrinsic = JSC::NoIntrinsic;
const char* const s_bundlerPluginRunOnResolvePluginsCode = "(function (_,w,g,y,b){\"use strict\";const j=[\"entry-point\",\"import-statement\",\"require-call\",\"dynamic-import\",\"require-resolve\",\"import-rule\",\"url-token\",\"internal\"][b];var q=(async(z,A,B,C)=>{var{onResolve:E,onLoad:F}=this,G=E.@get(A);if(!G)return this.onResolveAsync(y,null,null,null),null;for(let[O,Q]of G)if(O.test(z)){var H=Q({path:z,importer:B,namespace:A,kind:C});while(H&&@isPromise(H)&&(@getPromiseInternalField(H,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)H=@getPromiseInternalField(H,@promiseFieldReactionsOrResult);if(H&&@isPromise(H))H=await H;if(!H||!@isObject(H))continue;var{path:J,namespace:K=A,external:M}=H;if(typeof J!==\"string\"||typeof K!==\"string\")@throwTypeError(\"onResolve plugins must return an object with a string 'path' and string 'loader' field\");if(!J)continue;if(!K)K=A;if(typeof M!==\"boolean\"&&!@isUndefinedOrNull(M))@throwTypeError('onResolve plugins \"external\" field must be boolean or unspecified');if(!M){if(K===\"file\"){if(darwin!==\"win32\"){if(J[0]!==\"/\"||J.includes(\"..\"))@throwTypeError('onResolve plugin \"path\" must be absolute when the namespace is \"file\"')}}if(K===\"dataurl\"){if(!J.startsWith(\"data:\"))@throwTypeError('onResolve plugin \"path\" must start with \"data:\" when the namespace is \"dataurl\"')}if(K&&K!==\"file\"&&(!F||!F.@has(K)))@throwTypeError(`Expected onLoad plugin for namespace ${K} to exist`)}return this.onResolveAsync(y,J,K,M),null}return this.onResolveAsync(y,null,null,null),null})(_,w,g,j);while(q&&@isPromise(q)&&(@getPromiseInternalField(q,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)q=@getPromiseInternalField(q,@promiseFieldReactionsOrResult);if(q&&@isPromise(q))q.then(()=>{},(z)=>{this.addError(y,z,0)})})\n";
const char* const s_bundlerPluginRunOnResolvePluginsCode = "(function (_,g,y,F,j,q){\"use strict\";const w=[\"entry-point\",\"import-statement\",\"require-call\",\"dynamic-import\",\"require-resolve\",\"import-rule\",\"url-token\",\"internal\"][j];var z=(async(A,B,C,E,G)=>{var{onResolve:H,onLoad:J}=this,K=H.@get(B);if(!K)return this.onResolveAsync(F,null,null,null),null;for(let[T,U]of K)if(T.test(A)){var M=U({path:A,importer:C,namespace:B,resolveDir:G,kind:E});while(M&&@isPromise(M)&&(@getPromiseInternalField(M,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)M=@getPromiseInternalField(M,@promiseFieldReactionsOrResult);if(M&&@isPromise(M))M=await M;if(!M||!@isObject(M))continue;var{path:O,namespace:Q=B,external:S}=M;if(typeof O!==\"string\")@throwTypeError(\"onResolve: expected 'path' to be a string\");if(typeof Q!==\"string\")@throwTypeError(\"onResolve: expected 'namespace' to be a string\");if(!O)continue;if(!Q)Q=B;if(typeof S!==\"boolean\"&&!@isUndefinedOrNull(S))@throwTypeError(\"onResolve: expected 'external' to be boolean\");if(!S){if(Q===\"file\"){if(darwin!==\"win32\"){if(O[0]!==\"/\"||O.includes(\"..\"))@throwTypeError('onResolve plugin \"path\" must be absolute when the namespace is \"file\"')}}if(Q===\"dataurl\"){if(!O.startsWith(\"data:\"))@throwTypeError('onResolve plugin \"path\" must start with \"data:\" when the namespace is \"dataurl\"')}if(Q&&Q!==\"file\"&&(!J||!J.@has(Q)))@throwTypeError(`Expected onLoad plugin for namespace ${Q} to exist`)}return this.onResolveAsync(F,O,Q,S),null}return this.onResolveAsync(F,null,null,null),null})(_,g,y,w,q);while(z&&@isPromise(z)&&(@getPromiseInternalField(z,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)z=@getPromiseInternalField(z,@promiseFieldReactionsOrResult);if(z&&@isPromise(z))z.then(()=>{},(A)=>{this.addError(F,A,0)})})\n";
// runOnLoadPlugins
const JSC::ConstructAbility s_bundlerPluginRunOnLoadPluginsCodeConstructAbility = JSC::ConstructAbility::CannotConstruct;
@@ -32,7 +32,7 @@ const JSC::ConstructorKind s_bundlerPluginRunOnLoadPluginsCodeConstructorKind =
const JSC::ImplementationVisibility s_bundlerPluginRunOnLoadPluginsCodeImplementationVisibility = JSC::ImplementationVisibility::Public;
const int s_bundlerPluginRunOnLoadPluginsCodeLength = 1325;
static const JSC::Intrinsic s_bundlerPluginRunOnLoadPluginsCodeIntrinsic = JSC::NoIntrinsic;
const char* const s_bundlerPluginRunOnLoadPluginsCode = "(function (b,g,j,q){\"use strict\";const v={jsx:0,js:1,ts:2,tsx:3,css:4,file:5,json:6,toml:7,wasm:8,napi:9,base64:10,dataurl:11,text:12},w=[\"jsx\",\"js\",\"ts\",\"tsx\",\"css\",\"file\",\"json\",\"toml\",\"wasm\",\"napi\",\"base64\",\"dataurl\",\"text\"][q];var x=(async(y,z,B,C)=>{var F=this.onLoad.@get(B);if(!F)return this.onLoadAsync(y,null,null),null;for(let[K,Q]of F)if(K.test(z)){var G=Q({path:z,namespace:B,loader:C});while(G&&@isPromise(G)&&(@getPromiseInternalField(G,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)G=@getPromiseInternalField(G,@promiseFieldReactionsOrResult);if(G&&@isPromise(G))G=await G;if(!G||!@isObject(G))continue;var{contents:H,loader:J=C}=G;if(typeof H!==\"string\"&&!@isTypedArrayView(H))@throwTypeError('onLoad plugins must return an object with \"contents\" as a string or Uint8Array');if(typeof J!==\"string\")@throwTypeError('onLoad plugins must return an object with \"loader\" as a string');const T=v[J];if(T===@undefined)@throwTypeError(`Loader ${J} is not supported.`);return this.onLoadAsync(y,H,T),null}return this.onLoadAsync(y,null,null),null})(b,g,j,w);while(x&&@isPromise(x)&&(@getPromiseInternalField(x,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)x=@getPromiseInternalField(x,@promiseFieldReactionsOrResult);if(x&&@isPromise(x))x.then(()=>{},(y)=>{this.addError(b,y,1)})})\n";
const char* const s_bundlerPluginRunOnLoadPluginsCode = "(function (w,y,_,g){\"use strict\";const j={jsx:0,js:1,ts:2,tsx:3,css:4,file:5,json:6,toml:7,wasm:8,napi:9,base64:10,dataurl:11,text:12},q=[\"jsx\",\"js\",\"ts\",\"tsx\",\"css\",\"file\",\"json\",\"toml\",\"wasm\",\"napi\",\"base64\",\"dataurl\",\"text\"][g];var v=(async(x,z,B,C)=>{var F=this.onLoad.@get(B);if(!F)return this.onLoadAsync(x,null,null),null;for(let[K,P]of F)if(K.test(z)){var G=P({path:z,namespace:B,loader:C});while(G&&@isPromise(G)&&(@getPromiseInternalField(G,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)G=@getPromiseInternalField(G,@promiseFieldReactionsOrResult);if(G&&@isPromise(G))G=await G;if(!G||!@isObject(G))continue;var{contents:H,loader:J=C}=G;if(typeof H!==\"string\"&&!@isTypedArrayView(H))@throwTypeError('onLoad plugins must return an object with \"contents\" as a string or Uint8Array');if(typeof J!==\"string\")@throwTypeError('onLoad plugins must return an object with \"loader\" as a string');const Q=j[J];if(Q===@undefined)@throwTypeError(`Loader ${J} is not supported.`);return this.onLoadAsync(x,H,Q),null}return this.onLoadAsync(x,null,null),null})(w,y,_,q);while(v&&@isPromise(v)&&(@getPromiseInternalField(v,@promiseFieldFlags)&@promiseStateMask)===@promiseStateFulfilled)v=@getPromiseInternalField(v,@promiseFieldReactionsOrResult);if(v&&@isPromise(v))v.then(()=>{},(x)=>{this.addError(w,x,1)})})\n";
#define DEFINE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \
JSC::FunctionExecutable* codeName##Generator(JSC::VM& vm) \

View File

@@ -42,7 +42,7 @@ extern const JSC::ImplementationVisibility s_bundlerPluginRunOnLoadPluginsCodeIm
#define WEBCORE_FOREACH_BUNDLERPLUGIN_BUILTIN_DATA(macro) \
macro(runSetupFunction, bundlerPluginRunSetupFunction, 2) \
macro(runOnResolvePlugins, bundlerPluginRunOnResolvePlugins, 5) \
macro(runOnResolvePlugins, bundlerPluginRunOnResolvePlugins, 7) \
macro(runOnLoadPlugins, bundlerPluginRunOnLoadPlugins, 4) \
#define WEBCORE_FOREACH_BUNDLERPLUGIN_BUILTIN_CODE(macro) \

View File

@@ -2070,13 +2070,14 @@ fn NewLexer_(
lexer.step();
var has_set_flags_start = false;
const min_flag = comptime std.mem.min(u8, "dgimsuy");
const max_flag = comptime std.mem.max(u8, "dgimsuy");
const flag_characters = "dgimsuvy";
const min_flag = comptime std.mem.min(u8, flag_characters);
const max_flag = comptime std.mem.max(u8, flag_characters);
const RegexpFlags = std.bit_set.IntegerBitSet((max_flag - min_flag) + 1);
var flags = RegexpFlags.initEmpty();
while (isIdentifierContinue(lexer.code_point)) {
switch (lexer.code_point) {
'd', 'g', 'i', 'm', 's', 'u', 'y' => {
'd', 'g', 'i', 'm', 's', 'u', 'y', 'v' => {
if (!has_set_flags_start) {
lexer.regex_flags_start = @truncate(u16, lexer.end - lexer.start);
has_set_flags_start = true;
@@ -2640,7 +2641,7 @@ fn NewLexer_(
// them. <CR><LF> and <CR> LineTerminatorSequences are normalized to
// <LF> for both TV and TRV. An explicit EscapeSequence is needed to
// include a <CR> or <CR><LF> sequence.
var bytes = MutableString.init(lexer.allocator, text.len) catch unreachable;
var bytes = MutableString.initCopy(lexer.allocator, text) catch @panic("Out of memory");
var end: usize = 0;
var i: usize = 0;
var c: u8 = '0';
@@ -2662,7 +2663,7 @@ fn NewLexer_(
end += 1;
}
return bytes.toOwnedSliceLength(end + 1);
return bytes.toOwnedSliceLength(end);
}
fn parseNumericLiteralOrDot(lexer: *LexerType) !void {

Binary file not shown.

View File

@@ -894,4 +894,42 @@ describe("bundler", () => {
stdout: "it worked\nit worked\nit worked\nit worked",
},
});
itBundled("edgecase/IsBuffer1", {
files: {
"/entry.js": /* js */ `
import isBuffer from 'lodash-es/isBuffer';
if(isBuffer !== 1) throw 'fail';
console.log('pass');
`,
"/node_modules/lodash-es/isBuffer.js": /* js */ `
var freeExports = typeof exports == 'object';
// this is using the 'freeExports' variable but giving a predictable outcome
const isBuffer = freeExports ? 1 : 1;
export default isBuffer;
`,
},
run: {
stdout: "pass",
},
});
itBundled("edgecase/IsBuffer2", {
files: {
"/entry.js": /* js */ `
import isBuffer from 'lodash-es/isBuffer';
if(isBuffer !== 1) throw 'fail';
console.log('pass');
`,
"/node_modules/lodash-es/isBuffer.js": /* js */ `
var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;
var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;
// this is using the 'freeExports' variable but giving a predictable outcome
const isBuffer = [freeExports, freeModule] ? 1 : 1;
export default isBuffer;
`,
},
run: {
stdout: "pass",
},
});
});

View File

@@ -43,4 +43,29 @@ describe("bundler", () => {
stdout: "<!DOCTYPE html><html><head></head><body><h1>Hello World</h1><p>This is an example.</p></body></html>",
},
});
itBundled("npm/LodashES", {
install: ["lodash-es"],
files: {
"/entry.ts": /* tsx */ `
import { isEqual, isBuffer } from "lodash-es";
// https://github.com/oven-sh/bun/issues/3206
if(!isEqual({a: 1}, {a: 1})) throw "error 1";
if(isEqual({a: 1}, {a: 2})) throw "error 2";
// Uncomment when https://github.com/lodash/lodash/issues/5660 is fixed
// It prevents isBuffer from working at all since it evaluates to 'stubFalse'
// if(!isBuffer(Buffer.from("hello"))) throw "error 3";
// if(isBuffer("hello")) throw "error 4";
// if(isBuffer({})) throw "error 5";
// if(isBuffer(new Uint8Array([1]))) throw "error 6";
// if(isBuffer(new ArrayBuffer(1))) throw "error 7";
console.log('pass')
`,
},
run: {
stdout: "pass",
},
});
});

View File

@@ -830,4 +830,32 @@ describe("bundler", () => {
},
};
});
itBundled("plugin/resolveDir", ({ getConfigRef, root }) => {
return {
run: true,
files: {
"index.ts": /* ts */ `
import "./foo.magic";
`,
},
entryPoints: ["./index.ts"],
plugins(build) {
build.onResolve({ "filter": /.magic$/ }, args => {
console.log({ root, resolveDir: args.resolveDir });
expect(args.resolveDir).toBeDefined();
expect(args.resolveDir).toEqual(root);
return {
path: "magic",
"namespace": "magic",
};
});
build.onLoad({ namespace: "magic", "filter": /.*/ }, _args => {
return {
contents: "",
loader: "ts",
};
});
},
};
});
});

View File

@@ -3,6 +3,7 @@
*/
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
import path from "path";
import fs from "fs";
import dedent from "dedent";
import { bunEnv, bunExe } from "harness";
import { tmpdir } from "os";
@@ -42,7 +43,7 @@ export const RUN_UNCHECKED_TESTS = false;
const outBaseTemplate = path.join(tmpdir(), "bun-build-tests", `${ESBUILD ? "esbuild" : "bun"}-`);
if (!existsSync(path.dirname(outBaseTemplate))) mkdirSync(path.dirname(outBaseTemplate), { recursive: true });
const outBase = mkdtempSync(outBaseTemplate);
const outBase = fs.realpathSync(mkdtempSync(outBaseTemplate));
const testsRan = new Set();
if (ESBUILD) {
@@ -410,7 +411,7 @@ function expectBundled(
backend = plugins !== undefined ? "api" : "cli";
}
const root = path.join(outBase, id.replaceAll("/", path.sep));
const root = await path.join(outBase, id.replaceAll("/", path.sep));
if (DEBUG) console.log("root:", root);
const entryPaths = entryPoints.map(file => path.join(root, file));
@@ -1186,6 +1187,9 @@ for (const [key, blob] of build.outputs) {
},
stdio: ["ignore", "pipe", "pipe"],
});
if (DEBUG) {
console.log({ stdout: stdout!.toString("utf-8"), stderr: stderr!.toString("utf-8") });
}
if (run.error) {
if (success) {
@@ -1287,6 +1291,7 @@ export function itBundled(
): BundlerTestRef {
if (typeof opts === "function") {
const fn = opts;
// console.log("outbase", outBase);
opts = opts({ root: path.join(outBase, id.replaceAll("/", path.sep)), getConfigRef });
// @ts-expect-error
opts._referenceFn = fn;

View File

@@ -30,7 +30,7 @@ fi
export FORCE_COLOR=1
$BUN test bundler_ esbuild/ 2>&1 \
| perl -ne 'print unless /^\e\[0m$/' \
| grep -v -P '\x1b\[0m\x1b\[33m-\x1b\[2m \x1b\[0m\x1b\[2mbundler' --text \
| grep -v '\x1b\[0m\x1b\[33m-\x1b\[2m \x1b\[0m\x1b\[2mbundler' --text \
| grep -v ".test.ts:$" --text \
| tee /tmp/run-single-bundler-test.txt \
| grep "root:" -v --text

View File

@@ -0,0 +1,279 @@
import { test, expect, describe } from "bun:test";
import { Password, password, passwordSync } from "bun";
const placeholder = "hey";
describe("hash", () => {
describe("arguments parsing", () => {
for (let { hash } of [password, passwordSync]) {
test("no blank password allowed", () => {
expect(() => hash("")).toThrow("password must not be empty");
});
test("password is required", () => {
// @ts-expect-error
expect(() => hash()).toThrow();
});
test("invalid algorithm throws", () => {
// @ts-expect-error
expect(() => hash(placeholder, "scrpyt")).toThrow();
// @ts-expect-error
expect(() => hash(placeholder, 123)).toThrow();
expect(() =>
hash(placeholder, {
// @ts-expect-error
toString() {
return "scrypt";
},
}),
).toThrow();
expect(() =>
hash(placeholder, {
// @ts-expect-error
algorithm: "poop",
}),
).toThrow();
expect(() =>
hash(placeholder, {
algorithm: "bcrypt",
cost: Infinity,
}),
).toThrow();
expect(() =>
hash(placeholder, {
algorithm: "argon2id",
memoryCost: -1,
}),
).toThrow();
expect(() =>
hash(placeholder, {
algorithm: "argon2id",
timeCost: -1,
}),
).toThrow();
expect(() =>
hash(placeholder, {
algorithm: "bcrypt",
cost: -999,
}),
).toThrow();
});
test("coercion throwing doesn't crash", () => {
// @ts-expect-error
expect(() => hash(Symbol())).toThrow();
expect(() =>
// @ts-expect-error
hash({
toString() {
throw new Error("toString() failed");
},
}),
).toThrow();
});
for (let ArrayBufferView of [
Uint8Array,
Uint16Array,
Uint32Array,
Int8Array,
Int16Array,
Int32Array,
Float32Array,
Float64Array,
ArrayBuffer,
]) {
test(`empty ${ArrayBufferView.name} throws`, () => {
expect(() => hash(new ArrayBufferView(0))).toThrow("password must not be empty");
});
}
}
});
});
describe("verify", () => {
describe("arguments parsing", () => {
for (let { verify } of [password, passwordSync]) {
test("minimum args", () => {
// @ts-expect-error
expect(() => verify()).toThrow();
// @ts-expect-error
expect(() => verify("")).toThrow();
});
test("empty values return false", async () => {
expect(await verify("", "$")).toBeFalse();
expect(await verify("$", "")).toBeFalse();
});
test("invalid algorithm throws", () => {
// @ts-expect-error
expect(() => verify(placeholder, "$", "scrpyt")).toThrow();
// @ts-expect-error
expect(() => verify(placeholder, "$", 123)).toThrow();
expect(() =>
// @ts-expect-error
verify(placeholder, "$", {
toString() {
return "scrypt";
},
}),
).toThrow();
});
test("coercion throwing doesn't crash", () => {
// @ts-expect-error
expect(() => verify(Symbol(), Symbol())).toThrow();
expect(() =>
verify(
// @ts-expect-error
{
toString() {
throw new Error("toString() failed");
},
},
"valid",
),
).toThrow();
expect(() =>
// @ts-expect-error
verify("valid", {
toString() {
throw new Error("toString() failed");
},
}),
).toThrow();
});
for (let ArrayBufferView of [
Uint8Array,
Uint16Array,
Uint32Array,
Int8Array,
Int16Array,
Int32Array,
Float32Array,
Float64Array,
ArrayBuffer,
]) {
test(`empty ${ArrayBufferView.name} returns false`, async () => {
expect(await verify(new ArrayBufferView(0), new ArrayBufferView(0))).toBeFalse();
expect(await verify("", new ArrayBufferView(0))).toBeFalse();
expect(await verify(new ArrayBufferView(0), "")).toBeFalse();
});
}
}
});
});
test("bcrypt longer than 72 characters is the SHA-512", async () => {
const boop = Buffer.from("hey".repeat(100));
const hashed = await password.hash(boop, "bcrypt");
expect(await password.verify(Bun.SHA512.hash(boop), hashed, "bcrypt")).toBeTrue();
});
test("bcrypt shorter than 72 characters is NOT the SHA-512", async () => {
const boop = Buffer.from("hey".repeat(3));
const hashed = await password.hash(boop, "bcrypt");
expect(await password.verify(Bun.SHA512.hash(boop), hashed, "bcrypt")).toBeFalse();
});
const defaultAlgorithm = "argon2id";
const algorithms = [undefined, "argon2id", "bcrypt"];
const argons = ["argon2i", "argon2id", "argon2d"];
for (let algorithmValue of algorithms) {
const prefix = algorithmValue === "bcrypt" ? "$2" : "$" + (algorithmValue || defaultAlgorithm);
describe(algorithmValue ? algorithmValue : "default", () => {
const hash = (value: string | TypedArray) => {
return algorithmValue ? passwordSync.hash(value, algorithmValue as any) : passwordSync.hash(value);
};
const hashSync = (value: string | TypedArray) => {
return algorithmValue ? passwordSync.hash(value, algorithmValue as any) : passwordSync.hash(value);
};
const verify = (pw: string | TypedArray, value: string | TypedArray) => {
return algorithmValue ? password.verify(pw, value, algorithmValue as any) : password.verify(pw, value);
};
const verifySync = (pw: string | TypedArray, value: string | TypedArray) => {
return algorithmValue ? passwordSync.verify(pw, value, algorithmValue as any) : passwordSync.verify(pw, value);
};
for (let input of [placeholder, Buffer.from(placeholder)]) {
describe(typeof input === "string" ? "string" : "buffer", () => {
test("passwordSync", () => {
const hashed = hashSync(input);
expect(hashed).toStartWith(prefix);
expect(verifySync(input, hashed)).toBeTrue();
expect(() => verifySync(hashed, input)).toThrow();
expect(verifySync(input + "\0", hashed)).toBeFalse();
});
test("password", async () => {
async function runSlowTest(algorithm = algorithmValue as any) {
const hashed = await password.hash(input, algorithm);
const prefix = "$" + algorithm;
expect(hashed).toStartWith(prefix);
expect(await password.verify(input, hashed, algorithm)).toBeTrue();
expect(() => password.verify(hashed, input, algorithm)).toThrow();
expect(await password.verify(input + "\0", hashed, algorithm)).toBeFalse();
}
async function runSlowTestWithOptions(algorithmLabel: any) {
const algorithm = { algorithm: algorithmLabel, timeCost: 5, memoryCost: 4 };
const hashed = await password.hash(input, algorithm);
const prefix = "$" + algorithmLabel;
expect(hashed).toStartWith(prefix);
expect(hashed).toContain("t=5");
expect(hashed).toContain("m=4");
expect(await password.verify(input, hashed, algorithmLabel)).toBeTrue();
expect(() => password.verify(hashed, input, algorithmLabel)).toThrow();
expect(await password.verify(input + "\0", hashed, algorithmLabel)).toBeFalse();
}
async function runSlowBCryptTest() {
const algorithm = { algorithm: "bcrypt", cost: 4 } as const;
const hashed = await password.hash(input, algorithm);
const prefix = "$" + "2b";
expect(hashed).toStartWith(prefix);
expect(await password.verify(input, hashed, "bcrypt")).toBeTrue();
expect(() => password.verify(hashed, input, "bcrypt")).toThrow();
expect(await password.verify(input + "\0", hashed, "bcrypt")).toBeFalse();
}
if (algorithmValue === defaultAlgorithm) {
// these tests are very slow
// run the hashing tests in parallel
await Promise.all([...argons.map(runSlowTest), ...argons.map(runSlowTestWithOptions)]);
return;
}
async function defaultTest() {
const hashed = await hash(input);
expect(hashed).toStartWith(prefix);
expect(await verify(input, hashed)).toBeTrue();
expect(() => verify(hashed, input)).toThrow();
expect(await verify(input + "\0", hashed)).toBeFalse();
}
if (algorithmValue === "bcrypt") {
await Promise.all([defaultTest(), runSlowBCryptTest()]);
} else {
await defaultTest();
}
});
});
}
});
}

View File

@@ -74,6 +74,27 @@ function testRunInContext(
const result = fn("1 + 1; 2 * 2; 3 / 3", context);
expect(result).toBe(1);
});
for (let View of [
ArrayBuffer,
SharedArrayBuffer,
Uint8Array,
Int8Array,
Uint16Array,
Int16Array,
Uint32Array,
Int32Array,
Float32Array,
Float64Array,
BigInt64Array,
BigUint64Array,
]) {
test(`new ${View.name}() in VM context doesn't crash`, () => {
const context = createContext({});
expect(fn(`new ${View.name}(2)`, context)).toHaveLength(2);
});
}
test("can return a function", () => {
const context = createContext({});
const result = fn("() => 'bar';", context);

View File

@@ -2906,6 +2906,47 @@ console.log(foo, array);
});
});
it("raw template literal contents", () => {
expectPrinted("String.raw`\r`", "String.raw`\n`");
expectPrinted("String.raw`\r\n`", "String.raw`\n`");
expectPrinted("String.raw`\n`", "String.raw`\n`");
expectPrinted("String.raw`\r\r\r\r\r\n\r`", "String.raw`\n\n\n\n\n\n`");
expectPrinted("String.raw`\n\r`", "String.raw`\n\n`");
var code = `String.raw\`
<head>
<meta charset="UTF-8" />
<title>${"meow123"}</title>
<link rel="stylesheet" href="/css/style.css" />
</head>
\``;
var result = code;
expectPrinted(code, code);
code = `String.raw\`
<head>\r\n\n\r\r\r
<meta charset="UTF-8" />\r
<title>${"meow123"}</title>\n
\r
\r
\n\r
<link rel="stylesheet" href="/css/style.css" />
</head>
\``;
result = `String.raw\`
<head>\n\n\n\n
<meta charset="UTF-8" />
<title>${"meow123"}</title>\n
\n
<link rel="stylesheet" href="/css/style.css" />
</head>
\``;
expectPrinted(code, result);
});
describe("scan", () => {
it("reports all export names", () => {
const { imports, exports } = transpiler.scan(code);