Merge remote-tracking branch 'origin/main' into dylan/native-windows-aarch64-build

This commit is contained in:
Dylan Conway
2026-02-11 12:46:58 -08:00
10 changed files with 380 additions and 149 deletions

View File

@@ -0,0 +1,18 @@
import { posix, win32 } from "path";
import { bench, run } from "../runner.mjs";
const paths = ["/home/user/dir/file.txt", "/home/user/dir/", "file.txt", "/root", ""];
paths.forEach(p => {
bench(`posix.parse(${JSON.stringify(p)})`, () => {
globalThis.abc = posix.parse(p);
});
});
paths.forEach(p => {
bench(`win32.parse(${JSON.stringify(p)})`, () => {
globalThis.abc = win32.parse(p);
});
});
await run();

View File

@@ -935,7 +935,7 @@ pub const StandaloneModuleGraph = struct {
var remain = bytes;
while (remain.len > 0) {
switch (Syscall.write(cloned_executable_fd, bytes)) {
switch (Syscall.write(cloned_executable_fd, remain)) {
.result => |written| remain = remain[written..],
.err => |err| {
Output.prettyErrorln("<r><red>error<r><d>:<r> failed to write to temporary file\n{f}", .{err});

View File

@@ -114,6 +114,25 @@ static JSC::JSObject* createPath(JSGlobalObject* globalThis, bool isWindows)
} // namespace Zig
extern "C" JSC::EncodedJSValue PathParsedObject__create(
JSC::JSGlobalObject* globalObject,
JSC::EncodedJSValue root,
JSC::EncodedJSValue dir,
JSC::EncodedJSValue base,
JSC::EncodedJSValue ext,
JSC::EncodedJSValue name)
{
auto* global = JSC::jsCast<Zig::GlobalObject*>(globalObject);
auto& vm = JSC::getVM(globalObject);
JSC::JSObject* result = JSC::constructEmptyObject(vm, global->pathParsedObjectStructure());
result->putDirectOffset(vm, 0, JSC::JSValue::decode(root));
result->putDirectOffset(vm, 1, JSC::JSValue::decode(dir));
result->putDirectOffset(vm, 2, JSC::JSValue::decode(base));
result->putDirectOffset(vm, 3, JSC::JSValue::decode(ext));
result->putDirectOffset(vm, 4, JSC::JSValue::decode(name));
return JSC::JSValue::encode(result);
}
namespace Bun {
JSC::JSValue createNodePathBinding(Zig::GlobalObject* globalObject)

View File

@@ -2067,6 +2067,30 @@ void GlobalObject::finishCreation(VM& vm)
init.set(structure);
});
this->m_pathParsedObjectStructure.initLater(
[](const Initializer<Structure>& init) {
// { root, dir, base, ext, name } — path.parse() result
Structure* structure = init.owner->structureCache().emptyObjectStructureForPrototype(
init.owner, init.owner->objectPrototype(), 5);
PropertyOffset offset;
structure = Structure::addPropertyTransition(init.vm, structure,
Identifier::fromString(init.vm, "root"_s), 0, offset);
RELEASE_ASSERT(offset == 0);
structure = Structure::addPropertyTransition(init.vm, structure,
Identifier::fromString(init.vm, "dir"_s), 0, offset);
RELEASE_ASSERT(offset == 1);
structure = Structure::addPropertyTransition(init.vm, structure,
Identifier::fromString(init.vm, "base"_s), 0, offset);
RELEASE_ASSERT(offset == 2);
structure = Structure::addPropertyTransition(init.vm, structure,
Identifier::fromString(init.vm, "ext"_s), 0, offset);
RELEASE_ASSERT(offset == 3);
structure = Structure::addPropertyTransition(init.vm, structure,
init.vm.propertyNames->name, 0, offset);
RELEASE_ASSERT(offset == 4);
init.set(structure);
});
this->m_pendingVirtualModuleResultStructure.initLater(
[](const Initializer<Structure>& init) {
init.set(Bun::PendingVirtualModuleResult::createStructure(init.vm, init.owner, init.owner->objectPrototype()));

View File

@@ -567,6 +567,7 @@ public:
V(public, LazyClassStructure, m_JSHTTPParserClassStructure) \
\
V(private, LazyPropertyOfGlobalObject<Structure>, m_jsonlParseResultStructure) \
V(private, LazyPropertyOfGlobalObject<Structure>, m_pathParsedObjectStructure) \
V(private, LazyPropertyOfGlobalObject<Structure>, m_pendingVirtualModuleResultStructure) \
V(private, LazyPropertyOfGlobalObject<JSFunction>, m_performMicrotaskFunction) \
V(private, LazyPropertyOfGlobalObject<JSFunction>, m_nativeMicrotaskTrampoline) \
@@ -702,6 +703,7 @@ public:
void reload();
JSC::Structure* jsonlParseResultStructure() { return m_jsonlParseResultStructure.get(this); }
JSC::Structure* pathParsedObjectStructure() { return m_pathParsedObjectStructure.get(this); }
JSC::Structure* pendingVirtualModuleResultStructure() { return m_pendingVirtualModuleResultStructure.get(this); }
// We need to know if the napi module registered itself or we registered it.

View File

@@ -71,13 +71,12 @@ fn PathParsed(comptime T: type) type {
name: []const T = "",
pub fn toJSObject(this: @This(), globalObject: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue {
var jsObject = jsc.JSValue.createEmptyObject(globalObject, 5);
jsObject.put(globalObject, jsc.ZigString.static("root"), try bun.String.createUTF8ForJS(globalObject, this.root));
jsObject.put(globalObject, jsc.ZigString.static("dir"), try bun.String.createUTF8ForJS(globalObject, this.dir));
jsObject.put(globalObject, jsc.ZigString.static("base"), try bun.String.createUTF8ForJS(globalObject, this.base));
jsObject.put(globalObject, jsc.ZigString.static("ext"), try bun.String.createUTF8ForJS(globalObject, this.ext));
jsObject.put(globalObject, jsc.ZigString.static("name"), try bun.String.createUTF8ForJS(globalObject, this.name));
return jsObject;
const root = try bun.String.createUTF8ForJS(globalObject, this.root);
const dir = try bun.String.createUTF8ForJS(globalObject, this.dir);
const base = try bun.String.createUTF8ForJS(globalObject, this.base);
const ext = try bun.String.createUTF8ForJS(globalObject, this.ext);
const name_val = try bun.String.createUTF8ForJS(globalObject, this.name);
return PathParsedObject__create(globalObject, root, dir, base, ext, name_val);
}
};
}
@@ -2748,6 +2747,14 @@ pub fn resolveJS_T(comptime T: type, globalObject: *jsc.JSGlobalObject, allocato
}
extern "c" fn Process__getCachedCwd(*jsc.JSGlobalObject) jsc.JSValue;
extern "c" fn PathParsedObject__create(
*jsc.JSGlobalObject,
jsc.JSValue,
jsc.JSValue,
jsc.JSValue,
jsc.JSValue,
jsc.JSValue,
) jsc.JSValue;
pub fn resolve(globalObject: *jsc.JSGlobalObject, isWindows: bool, args_ptr: [*]jsc.JSValue, args_len: u16) bun.JSError!jsc.JSValue {
var arena = bun.ArenaAllocator.init(bun.default_allocator);

View File

@@ -199,18 +199,18 @@ function Sign(algorithm, options): void {
}
$toClass(Sign, "Sign", Writable);
Sign.prototype._write = function _write(chunk, encoding, callback) {
this.update(chunk, encoding);
callback();
};
Sign.prototype.update = function update(data, encoding) {
return this[kHandle].update(this, data, encoding);
};
Sign.prototype.sign = function sign(options, encoding) {
return this[kHandle].sign(options, encoding);
};
Object.assign(Sign.prototype, {
_write: function (chunk, encoding, callback) {
this.update(chunk, encoding);
callback();
},
update: function (data, encoding) {
return this[kHandle].update(this, data, encoding);
},
sign: function (options, encoding) {
return this[kHandle].sign(options, encoding);
},
});
crypto_exports.Sign = Sign;
crypto_exports.sign = sign;
@@ -237,9 +237,11 @@ $toClass(Verify, "Verify", Writable);
Verify.prototype._write = Sign.prototype._write;
Verify.prototype.update = Sign.prototype.update;
Verify.prototype.verify = function verify(options, signature, sigEncoding) {
return this[kHandle].verify(options, signature, sigEncoding);
};
Object.assign(Verify.prototype, {
verify: function (options, signature, sigEncoding) {
return this[kHandle].verify(options, signature, sigEncoding);
},
});
crypto_exports.Verify = Verify;
crypto_exports.verify = verify;
@@ -250,82 +252,76 @@ function createVerify(algorithm, options?) {
crypto_exports.createVerify = createVerify;
{
function Hash(algorithm, options?): void {
if (!new.target) {
return new Hash(algorithm, options);
}
const handle = new _Hash(algorithm, options);
this[kHandle] = handle;
LazyTransform.$apply(this, [options]);
function Hash(algorithm, options?): void {
if (!new.target) {
return new Hash(algorithm, options);
}
$toClass(Hash, "Hash", LazyTransform);
Hash.prototype.copy = function copy(options) {
const handle = new _Hash(algorithm, options);
this[kHandle] = handle;
LazyTransform.$apply(this, [options]);
}
$toClass(Hash, "Hash", LazyTransform);
Object.assign(Hash.prototype, {
copy: function (options) {
return new Hash(this[kHandle], options);
};
Hash.prototype._transform = function _transform(chunk, encoding, callback) {
},
_transform: function (chunk, encoding, callback) {
this[kHandle].update(this, chunk, encoding);
callback();
};
Hash.prototype._flush = function _flush(callback) {
},
_flush: function (callback) {
this.push(this[kHandle].digest(null, false));
callback();
};
Hash.prototype.update = function update(data, encoding) {
},
update: function (data, encoding) {
return this[kHandle].update(this, data, encoding);
};
Hash.prototype.digest = function digest(outputEncoding) {
},
digest: function (outputEncoding) {
return this[kHandle].digest(outputEncoding);
};
},
});
crypto_exports.Hash = Hash;
crypto_exports.createHash = function createHash(algorithm, options) {
return new Hash(algorithm, options);
};
}
crypto_exports.Hash = Hash;
crypto_exports.createHash = function createHash(algorithm, options) {
return new Hash(algorithm, options);
};
{
function Hmac(hmac, key, options?): void {
if (!new.target) {
return new Hmac(hmac, key, options);
}
const handle = new _Hmac(hmac, key, options);
this[kHandle] = handle;
LazyTransform.$apply(this, [options]);
function Hmac(hmac, key, options?): void {
if (!new.target) {
return new Hmac(hmac, key, options);
}
$toClass(Hmac, "Hmac", LazyTransform);
Hmac.prototype.update = function update(data, encoding) {
const handle = new _Hmac(hmac, key, options);
this[kHandle] = handle;
LazyTransform.$apply(this, [options]);
}
$toClass(Hmac, "Hmac", LazyTransform);
Object.assign(Hmac.prototype, {
update: function (data, encoding) {
return this[kHandle].update(this, data, encoding);
};
Hmac.prototype.digest = function digest(outputEncoding) {
},
digest: function (outputEncoding) {
return this[kHandle].digest(outputEncoding);
};
Hmac.prototype._transform = function _transform(chunk, encoding, callback) {
},
_transform: function (chunk, encoding, callback) {
this[kHandle].update(this, chunk, encoding);
callback();
};
Hmac.prototype._flush = function _flush(callback) {
},
_flush: function (callback) {
this.push(this[kHandle].digest());
callback();
};
},
});
crypto_exports.Hmac = Hmac;
crypto_exports.createHmac = function createHmac(hmac, key, options) {
return new Hmac(hmac, key, options);
};
}
crypto_exports.Hmac = Hmac;
crypto_exports.createHmac = function createHmac(hmac, key, options) {
return new Hmac(hmac, key, options);
};
crypto_exports.getHashes = getHashes;
@@ -390,62 +386,6 @@ crypto_exports.createECDH = function createECDH(curve) {
return decoder;
}
function setAutoPadding(ap) {
this[kHandle].setAutoPadding(ap);
return this;
}
function getAuthTag() {
return this[kHandle].getAuthTag();
}
function setAuthTag(tagbuf, encoding) {
this[kHandle].setAuthTag(tagbuf, encoding);
return this;
}
function setAAD(aadbuf, options) {
this[kHandle].setAAD(aadbuf, options);
return this;
}
function _transform(chunk, encoding, callback) {
this.push(this[kHandle].update(chunk, encoding));
callback();
}
function _flush(callback) {
try {
this.push(this[kHandle].final());
} catch (e) {
callback(e);
return;
}
callback();
}
function update(data, inputEncoding, outputEncoding) {
const res = this[kHandle].update(data, inputEncoding);
if (outputEncoding && outputEncoding !== "buffer") {
this._decoder = getDecoder(this._decoder, outputEncoding);
return this._decoder.write(res);
}
return res;
}
function final(outputEncoding) {
const res = this[kHandle].final();
if (outputEncoding && outputEncoding !== "buffer") {
this._decoder = getDecoder(this._decoder, outputEncoding);
return this._decoder.end(res);
}
return res;
}
function Cipheriv(cipher, key, iv, options): void {
if (!new.target) {
return new Cipheriv(cipher, key, iv, options);
@@ -457,13 +397,52 @@ crypto_exports.createECDH = function createECDH(curve) {
}
$toClass(Cipheriv, "Cipheriv", LazyTransform);
Cipheriv.prototype.setAutoPadding = setAutoPadding;
Cipheriv.prototype.getAuthTag = getAuthTag;
Cipheriv.prototype.setAAD = setAAD;
Cipheriv.prototype._transform = _transform;
Cipheriv.prototype._flush = _flush;
Cipheriv.prototype.update = update;
Cipheriv.prototype.final = final;
Object.assign(Cipheriv.prototype, {
setAutoPadding: function (ap) {
this[kHandle].setAutoPadding(ap);
return this;
},
getAuthTag: function () {
return this[kHandle].getAuthTag();
},
setAAD: function (aadbuf, options) {
this[kHandle].setAAD(aadbuf, options);
return this;
},
_transform: function (chunk, encoding, callback) {
this.push(this[kHandle].update(chunk, encoding));
callback();
},
_flush: function (callback) {
try {
this.push(this[kHandle].final());
} catch (e) {
callback(e);
return;
}
callback();
},
update: function (data, inputEncoding, outputEncoding) {
const res = this[kHandle].update(data, inputEncoding);
if (outputEncoding && outputEncoding !== "buffer") {
this._decoder = getDecoder(this._decoder, outputEncoding);
return this._decoder.write(res);
}
return res;
},
final: function (outputEncoding) {
const res = this[kHandle].final();
if (outputEncoding && outputEncoding !== "buffer") {
this._decoder = getDecoder(this._decoder, outputEncoding);
return this._decoder.end(res);
}
return res;
},
});
function Decipheriv(cipher, key, iv, options): void {
if (!new.target) {
@@ -476,13 +455,18 @@ crypto_exports.createECDH = function createECDH(curve) {
}
$toClass(Decipheriv, "Decipheriv", LazyTransform);
Decipheriv.prototype.setAutoPadding = setAutoPadding;
Decipheriv.prototype.setAuthTag = setAuthTag;
Decipheriv.prototype.setAAD = setAAD;
Decipheriv.prototype._transform = _transform;
Decipheriv.prototype._flush = _flush;
Decipheriv.prototype.update = update;
Decipheriv.prototype.final = final;
Object.assign(Decipheriv.prototype, {
setAutoPadding: Cipheriv.prototype.setAutoPadding,
setAuthTag: function (tagbuf, encoding) {
this[kHandle].setAuthTag(tagbuf, encoding);
return this;
},
setAAD: Cipheriv.prototype.setAAD,
_transform: Cipheriv.prototype._transform,
_flush: Cipheriv.prototype._flush,
update: Cipheriv.prototype.update,
final: Cipheriv.prototype.final,
});
crypto_exports.Cipheriv = Cipheriv;
crypto_exports.Decipheriv = Decipheriv;

View File

@@ -4092,6 +4092,12 @@ pub fn copyFileZSlowWithHandle(in_handle: bun.FileDescriptor, to_dir: bun.FileDe
_ = std.os.linux.fallocate(out_handle.cast(), 0, 0, @intCast(stat_.size));
}
// Seek input to beginning — the caller may have written to this fd,
// leaving the file offset at EOF. copy_file_range / sendfile / read
// all use the current offset when called with null offsets.
// Ignore errors: the fd may be non-seekable (e.g. a pipe).
_ = setFileOffset(in_handle, 0);
switch (bun.copyFile(in_handle, out_handle)) {
.err => |e| return .{ .err = e },
.result => {},

View File

@@ -121,4 +121,71 @@ describe("Bun.build compile", () => {
});
});
describe("compiled binary validity", () => {
test("output binary has valid executable header", async () => {
using dir = tempDir("build-compile-valid-header", {
"app.js": `console.log("hello");`,
});
const outfile = join(dir + "", "app-out");
const result = await Bun.build({
entrypoints: [join(dir + "", "app.js")],
compile: {
outfile,
},
});
expect(result.success).toBe(true);
// Read the first 4 bytes and verify it's a valid executable magic number
const file = Bun.file(result.outputs[0].path);
const header = new Uint8Array(await file.slice(0, 4).arrayBuffer());
if (isMacOS) {
// MachO magic: 0xCFFAEDFE (little-endian)
expect(header[0]).toBe(0xcf);
expect(header[1]).toBe(0xfa);
expect(header[2]).toBe(0xed);
expect(header[3]).toBe(0xfe);
} else if (isLinux) {
// ELF magic: 0x7F 'E' 'L' 'F'
expect(header[0]).toBe(0x7f);
expect(header[1]).toBe(0x45); // 'E'
expect(header[2]).toBe(0x4c); // 'L'
expect(header[3]).toBe(0x46); // 'F'
} else if (isWindows) {
// PE magic: 'M' 'Z'
expect(header[0]).toBe(0x4d); // 'M'
expect(header[1]).toBe(0x5a); // 'Z'
}
});
test("compiled binary runs and produces expected output", async () => {
using dir = tempDir("build-compile-runs", {
"app.js": `console.log("compile-test-output");`,
});
const outfile = join(dir + "", "app-run");
const result = await Bun.build({
entrypoints: [join(dir + "", "app.js")],
compile: {
outfile,
},
});
expect(result.success).toBe(true);
await using proc = Bun.spawn({
cmd: [result.outputs[0].path],
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout.trim()).toBe("compile-test-output");
expect(exitCode).toBe(0);
});
});
// file command test works well

View File

@@ -504,6 +504,110 @@ describe("Hash", () => {
expect(hash.update.name).toBe("update");
expect(hash.digest.name).toBe("digest");
expect(hash.copy.name).toBe("copy");
expect(hash._transform.name).toBe("_transform");
expect(hash._flush.name).toBe("_flush");
});
});
describe("Hmac", () => {
it("should have correct method names", () => {
const hmac = crypto.createHmac("sha256", "key");
expect(hmac.update.name).toBe("update");
expect(hmac.digest.name).toBe("digest");
expect(hmac._transform.name).toBe("_transform");
expect(hmac._flush.name).toBe("_flush");
});
});
describe("Sign", () => {
it("should have correct method names", () => {
const sign = crypto.createSign("sha256");
expect(sign.update.name).toBe("update");
expect(sign.sign.name).toBe("sign");
expect(sign._write.name).toBe("_write");
});
});
describe("Verify", () => {
it("should have correct method names", () => {
const verify = crypto.createVerify("sha256");
expect(verify.update.name).toBe("update");
expect(verify.verify.name).toBe("verify");
expect(verify._write.name).toBe("_write");
});
});
describe("Cipheriv", () => {
it("should have correct method names", () => {
const cipher = crypto.createCipheriv("aes-256-cbc", Buffer.alloc(32), Buffer.alloc(16));
expect(cipher.update.name).toBe("update");
expect(cipher.final.name).toBe("final");
expect(cipher.setAutoPadding.name).toBe("setAutoPadding");
expect(cipher.getAuthTag.name).toBe("getAuthTag");
expect(cipher.setAAD.name).toBe("setAAD");
expect(cipher._transform.name).toBe("_transform");
expect(cipher._flush.name).toBe("_flush");
});
});
describe("Decipheriv", () => {
it("should have correct method names", () => {
const decipher = crypto.createDecipheriv("aes-256-cbc", Buffer.alloc(32), Buffer.alloc(16));
expect(decipher.update.name).toBe("update");
expect(decipher.final.name).toBe("final");
expect(decipher.setAutoPadding.name).toBe("setAutoPadding");
expect(decipher.setAuthTag.name).toBe("setAuthTag");
expect(decipher.setAAD.name).toBe("setAAD");
expect(decipher._transform.name).toBe("_transform");
expect(decipher._flush.name).toBe("_flush");
});
});
describe("DiffieHellman", () => {
it("should have correct method names", () => {
const dh = crypto.createDiffieHellman(512);
expect(dh.generateKeys.name).toBe("generateKeys");
expect(dh.computeSecret.name).toBe("computeSecret");
expect(dh.getPrime.name).toBe("getPrime");
expect(dh.getGenerator.name).toBe("getGenerator");
expect(dh.getPublicKey.name).toBe("getPublicKey");
expect(dh.getPrivateKey.name).toBe("getPrivateKey");
expect(dh.setPublicKey.name).toBe("setPublicKey");
expect(dh.setPrivateKey.name).toBe("setPrivateKey");
});
});
describe("ECDH", () => {
it("should have correct method names", () => {
const ecdh = crypto.createECDH("prime256v1");
expect(ecdh.generateKeys.name).toBe("generateKeys");
expect(ecdh.computeSecret.name).toBe("computeSecret");
expect(ecdh.getPublicKey.name).toBe("getPublicKey");
expect(ecdh.getPrivateKey.name).toBe("getPrivateKey");
expect(ecdh.setPublicKey.name).toBe("setPublicKey");
expect(ecdh.setPrivateKey.name).toBe("setPrivateKey");
});
});
describe("crypto module", () => {
it("should have correct factory function names", () => {
expect(crypto.createHash.name).toBe("createHash");
expect(crypto.createHmac.name).toBe("createHmac");
expect(crypto.createSign.name).toBe("createSign");
expect(crypto.createVerify.name).toBe("createVerify");
expect(crypto.createCipheriv.name).toBe("createCipheriv");
expect(crypto.createDecipheriv.name).toBe("createDecipheriv");
expect(crypto.createDiffieHellman.name).toBe("createDiffieHellman");
expect(crypto.createECDH.name).toBe("createECDH");
expect(crypto.hash.name).toBe("hash");
expect(crypto.pbkdf2.name).toBe("pbkdf2");
});
it("should have correct constructor names", () => {
expect(crypto.Hash.name).toBe("Hash");
expect(crypto.Hmac.name).toBe("Hmac");
expect(crypto.Sign.name).toBe("Sign");
expect(crypto.Verify.name).toBe("Verify");
});
});