From e29e830a2559083536974e65bfd720b516e81db5 Mon Sep 17 00:00:00 2001 From: SUZUKI Sosuke Date: Wed, 11 Feb 2026 15:32:08 +0900 Subject: [PATCH 1/4] perf(path): use pre-built Structure for path.parse() result objects (#26865) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Optimize `path.parse()` by caching a pre-built JSC Structure for the result object. Instead of creating a new empty object and performing 5 `putDirect` calls (each triggering a Structure transition), we now use `constructEmptyObject` with the cached Structure and write values directly via `putDirectOffset`. ## What changed - **`ZigGlobalObject.h/cpp`**: Added `m_pathParsedObjectStructure` as a `LazyPropertyOfGlobalObject` with fixed property offsets for `{root, dir, base, ext, name}`. - **`Path.cpp`**: Added `PathParsedObject__create` extern "C" factory function that constructs the object using the pre-built Structure and `putDirectOffset`. - **`path.zig`**: Replaced `toJSObject()` implementation to call the C++ factory function instead of `createEmptyObject` + 5x `.put()`. - **`bench/snippets/path-parse.mjs`**: Added benchmark for `path.parse()`. This follows the same pattern used by `JSSocketAddressDTO` and `m_jsonlParseResultStructure`. ## Benchmark results (Apple M4 Max) | Benchmark | Before (1.3.9) | After | Speedup | |---|---|---|---| | `posix.parse("/home/user/dir/file.txt")` | 266.71 ns | 119.62 ns | **2.23x** | | `posix.parse("/home/user/dir/")` | 239.10 ns | 91.46 ns | **2.61x** | | `posix.parse("file.txt")` | 232.55 ns | 89.20 ns | **2.61x** | | `posix.parse("/root")` | 246.75 ns | 92.68 ns | **2.66x** | | `posix.parse("")` | 152.19 ns | 20.72 ns | **7.34x** | | `win32.parse("/home/user/dir/file.txt")` | 260.08 ns | 118.12 ns | **2.20x** | | `win32.parse("/home/user/dir/")` | 234.35 ns | 93.47 ns | **2.51x** | | `win32.parse("file.txt")` | 224.19 ns | 80.56 ns | **2.78x** | | `win32.parse("/root")` | 241.20 ns | 88.23 ns | **2.73x** | | `win32.parse("")` | 160.39 ns | 24.20 ns | **6.63x** | **~2.2x–2.8x faster** for typical paths, **~7x faster** for empty strings. ## GC Safety - `LazyPropertyOfGlobalObject` is automatically visited via `FOR_EACH_GLOBALOBJECT_GC_MEMBER`. - JSValues created by `createUTF8ForJS` are protected by JSC's conservative stack scanning during the factory function call. ## Test All 116 existing path tests pass (`bun bd test test/js/node/path/`). --------- Co-authored-by: Claude Opus 4.6 Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- bench/snippets/path-parse.mjs | 18 ++++++++++++++++++ src/bun.js/bindings/Path.cpp | 19 +++++++++++++++++++ src/bun.js/bindings/ZigGlobalObject.cpp | 24 ++++++++++++++++++++++++ src/bun.js/bindings/ZigGlobalObject.h | 2 ++ src/bun.js/node/path.zig | 21 ++++++++++++++------- 5 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 bench/snippets/path-parse.mjs diff --git a/bench/snippets/path-parse.mjs b/bench/snippets/path-parse.mjs new file mode 100644 index 0000000000..414d3e7e9a --- /dev/null +++ b/bench/snippets/path-parse.mjs @@ -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(); diff --git a/src/bun.js/bindings/Path.cpp b/src/bun.js/bindings/Path.cpp index d8b110a425..3578ba23c9 100644 --- a/src/bun.js/bindings/Path.cpp +++ b/src/bun.js/bindings/Path.cpp @@ -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(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) diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 31373b7359..9cb9399815 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -2067,6 +2067,30 @@ void GlobalObject::finishCreation(VM& vm) init.set(structure); }); + this->m_pathParsedObjectStructure.initLater( + [](const Initializer& 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& init) { init.set(Bun::PendingVirtualModuleResult::createStructure(init.vm, init.owner, init.owner->objectPrototype())); diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 21c8d41f5c..5225b67ddd 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -567,6 +567,7 @@ public: V(public, LazyClassStructure, m_JSHTTPParserClassStructure) \ \ V(private, LazyPropertyOfGlobalObject, m_jsonlParseResultStructure) \ + V(private, LazyPropertyOfGlobalObject, m_pathParsedObjectStructure) \ V(private, LazyPropertyOfGlobalObject, m_pendingVirtualModuleResultStructure) \ V(private, LazyPropertyOfGlobalObject, m_performMicrotaskFunction) \ V(private, LazyPropertyOfGlobalObject, 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. diff --git a/src/bun.js/node/path.zig b/src/bun.js/node/path.zig index 6f2103c58f..76f09cf1dc 100644 --- a/src/bun.js/node/path.zig +++ b/src/bun.js/node/path.zig @@ -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); From ba6e84fecd024a5eecbc2b97109dd3c5a59ac42d Mon Sep 17 00:00:00 2001 From: robobun Date: Tue, 10 Feb 2026 22:32:31 -0800 Subject: [PATCH 2/4] fix(compile): seek to start of file before EXDEV cross-device copy (#26883) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What does this PR do? Fixes `bun build --compile` producing an all-zeros binary when the output directory is on a different filesystem than the temp directory. This is common in Docker containers, Gitea runners, and other environments using overlayfs. ## Problem When `inject()` finishes writing the modified executable to the temp file, the file descriptor's offset is at EOF. If the subsequent `renameat()` to the output path fails with `EXDEV` (cross-device — the temp file and output dir are on different filesystems), the code falls back to `copyFileZSlowWithHandle()`, which: 1. Calls `fallocate()` to pre-allocate the output file to the correct size (filled with zeros) 2. Calls `bun.copyFile(in_handle, out_handle)` — but `in_handle`'s offset is at EOF 3. `copy_file_range` / `sendfile` / `read` all use the current file offset (EOF), read 0 bytes, and return immediately 4. Result: output file is the correct size but entirely zeros This explains user reports of `bun build --compile --target=bun-darwin-arm64` producing invalid binaries that `file` identifies as "data" rather than a Mach-O executable. ## Fix Seek the input fd to offset 0 in `copyFileZSlowWithHandle` before calling `bun.copyFile`. ## How did you verify your code works? - `bun bd` compiles successfully - `bun bd test test/bundler/bun-build-compile.test.ts` — 6/6 pass - Added tests that verify compiled binaries have valid executable headers and produce correct output - Manually verified cross-compilation: `bun build --compile --target=bun-darwin-arm64` produces a valid Mach-O binary 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Bot Co-authored-by: Claude --- src/sys.zig | 6 +++ test/bundler/bun-build-compile.test.ts | 67 ++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/sys.zig b/src/sys.zig index 285878683d..cb606398f4 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -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 => {}, diff --git a/test/bundler/bun-build-compile.test.ts b/test/bundler/bun-build-compile.test.ts index 8004a7bb78..491e3b8438 100644 --- a/test/bundler/bun-build-compile.test.ts +++ b/test/bundler/bun-build-compile.test.ts @@ -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 From e8f73601c0e3c6a5f44037e52bd5d4a79d536727 Mon Sep 17 00:00:00 2001 From: robobun Date: Tue, 10 Feb 2026 23:04:46 -0800 Subject: [PATCH 3/4] fix(compile): use remaining buffer in standalone POSIX write loop (#26882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What does this PR do? Fixes the write loop in `StandaloneModuleGraph.inject()` for POSIX targets (the `else` branch handling ELF/Linux standalone binaries) to pass `remain` instead of `bytes` to `Syscall.write()`. ## Problem The write loop that appends the bundled module graph to the end of the executable uses a standard partial-write retry pattern, but passes the full `bytes` buffer on every iteration instead of the remaining portion: ```zig var remain = bytes; while (remain.len > 0) { switch (Syscall.write(cloned_executable_fd, bytes)) { // bug: should be 'remain' .result => |written| remain = remain[written..], ... } } ``` If a partial write occurs, the next iteration re-writes from the start of the buffer instead of continuing where it left off, corrupting the output binary. The analogous read loop elsewhere in the same file already correctly uses `remain`. ## Fix One-character change: `bytes` → `remain` in the `Syscall.write()` call. ## How did you verify your code works? - `bun bd` compiles successfully - `bun bd test test/bundler/bun-build-compile.test.ts` — 4/4 pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Bot Co-authored-by: Claude Co-authored-by: Alistair Smith --- src/StandaloneModuleGraph.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/StandaloneModuleGraph.zig b/src/StandaloneModuleGraph.zig index 3985a6aee6..dd63115ee9 100644 --- a/src/StandaloneModuleGraph.zig +++ b/src/StandaloneModuleGraph.zig @@ -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("error: failed to write to temporary file\n{f}", .{err}); From 50e478dcdc92d6873947f91c39af79240c7780b0 Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 10 Feb 2026 23:06:22 -0800 Subject: [PATCH 4/4] fix(crypto): correct method and constructor names mangled by number renamer (#26876) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem The bundler's number renamer was mangling `.name` properties on crypto class prototype methods and constructors: - `hash.update.name` → `"update2"` instead of `"update"` - `verify.verify.name` → `"verify2"` instead of `"verify"` - `cipher.update.name` → `"update3"` instead of `"update"` - `crypto.Hash.name` → `"Hash2"` instead of `"Hash"` ### Root causes 1. **Named function expressions on prototypes** collided with other bindings after scope flattening (e.g. `Verify.prototype.verify = function verify(...)` collided with the imported `verify`) 2. **Block-scoped constructor declarations** (`Hash`, `Hmac`) got renamed when the bundler hoisted them out of block scope 3. **Shared function declarations** in the Cipher/Decipher block all got numeric suffixes (`update3`, `final2`, `setAutoPadding2`, etc.) ## Fix - Use `Object.assign` with object literals for prototype methods — object literal property keys correctly infer `.name` and aren't subject to the renamer - Remove unnecessary block scopes around `Hash` and `Hmac` constructors so they stay at module level and don't get renamed - Inline `Cipheriv` methods and copy references to `Decipheriv` ## Tests Added comprehensive `.name` tests for all crypto classes: Hash, Hmac, Sign, Verify, Cipheriv, Decipheriv, DiffieHellman, ECDH, plus factory functions and constructor names. --- src/js/node/crypto.ts | 266 +++++++++++------------- test/js/node/crypto/node-crypto.test.js | 104 +++++++++ 2 files changed, 229 insertions(+), 141 deletions(-) diff --git a/src/js/node/crypto.ts b/src/js/node/crypto.ts index d6c3218b87..8b5edca9cf 100644 --- a/src/js/node/crypto.ts +++ b/src/js/node/crypto.ts @@ -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; diff --git a/test/js/node/crypto/node-crypto.test.js b/test/js/node/crypto/node-crypto.test.js index 2316d0ba5e..9d6ea12efa 100644 --- a/test/js/node/crypto/node-crypto.test.js +++ b/test/js/node/crypto/node-crypto.test.js @@ -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"); }); });