diff --git a/src/bun.js/bindings/JSBuffer.cpp b/src/bun.js/bindings/JSBuffer.cpp index 1891fd1faa..ebffd1a33c 100644 --- a/src/bun.js/bindings/JSBuffer.cpp +++ b/src/bun.js/bindings/JSBuffer.cpp @@ -1495,10 +1495,8 @@ static inline JSC::EncodedJSValue jsBufferToString(JSC::VM& vm, JSC::JSGlobalObj return JSC::JSValue::encode(JSC::jsEmptyString(vm)); } else { auto str = String::createUninitialized(u16length, data); - // always zero out the last byte of the string incase the buffer is not a multiple of 2 - data[u16length - 1] = 0; - memcpy(data, reinterpret_cast(castedThis->typedVector() + offset), length); - return JSC::JSValue::encode(JSC::jsString(vm, WTFMove(str))); + memcpy(reinterpret_cast(data), reinterpret_cast(castedThis->typedVector() + offset), u16length * 2); + return JSC::JSValue::encode(JSC::jsString(vm, str)); } break; diff --git a/src/bundler/entry_points.zig b/src/bundler/entry_points.zig index 5b76d439e5..da3f548412 100644 --- a/src/bundler/entry_points.zig +++ b/src/bundler/entry_points.zig @@ -315,12 +315,20 @@ pub const MacroEntryPoint = struct { \\Bun.registerMacro({d}, Macros['{s}']); , .{ - dir_to_use, - import_path.filename, + bun.fmt.fmtPath(u8, dir_to_use, .{ + .escape_backslashes = true, + }), + bun.fmt.fmtPath(u8, import_path.filename, .{ + .escape_backslashes = true, + }), function_name, function_name, - dir_to_use, - import_path.filename, + bun.fmt.fmtPath(u8, dir_to_use, .{ + .escape_backslashes = true, + }), + bun.fmt.fmtPath(u8, import_path.filename, .{ + .escape_backslashes = true, + }), macro_id, function_name, }, diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index f51ea93f73..b44cdf8ce5 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -493,7 +493,7 @@ pub const CreateCommand = struct { progress_.refresh(); - Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path) }); + Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); Global.exit(1); }; }; @@ -513,7 +513,7 @@ pub const CreateCommand = struct { } CopyFile.copyFile(infile.handle, outfile.handle) catch |err| { - Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path) }); + Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); Global.exit(1); }; } diff --git a/src/fmt.zig b/src/fmt.zig index 65b2b94937..06aa0302df 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -45,33 +45,107 @@ pub fn formatUTF16Type(comptime Slice: type, slice_: Slice, writer: anytype) !vo } } +pub fn formatUTF16TypeEscapeBackslashes(comptime Slice: type, slice_: Slice, writer: anytype) !void { + var chunk = getSharedBuffer(); + + // Defensively ensure recursion doesn't cause the buffer to be overwritten in-place + shared_temp_buffer_ptr = null; + defer { + if (shared_temp_buffer_ptr) |existing| { + if (existing != chunk.ptr) { + bun.default_allocator.destroy(@as(*SharedTempBuffer, @ptrCast(chunk.ptr))); + } + } else { + shared_temp_buffer_ptr = @ptrCast(chunk.ptr); + } + } + + var slice = slice_; + + while (slice.len > 0) { + const result = strings.copyUTF16IntoUTF8(chunk, Slice, slice, true); + if (result.read == 0 or result.written == 0) + break; + + const to_write = chunk[0..result.written]; + var ptr = to_write; + while (strings.indexOfChar(ptr, '\\')) |i| { + try writer.writeAll(ptr[0 .. i + 1]); + try writer.writeAll("\\"); + ptr = ptr[i + 1 ..]; + } + try writer.writeAll(ptr); + slice = slice[result.read..]; + } +} + pub fn formatUTF16(slice_: []align(1) const u16, writer: anytype) !void { return formatUTF16Type([]align(1) const u16, slice_, writer); } pub const FormatUTF16 = struct { buf: []const u16, - pub fn format(self: @This(), comptime _: []const u8, opts: anytype, writer: anytype) !void { - _ = opts; - try formatUTF16Type([]const u16, self.buf, writer); + escape_backslashes: bool = false, + pub fn format(self: @This(), comptime _: []const u8, _: anytype, writer: anytype) !void { + if (self.escape_backslashes) { + try formatUTF16TypeEscapeBackslashes([]const u16, self.buf, writer); + } else { + try formatUTF16Type([]const u16, self.buf, writer); + } } }; pub const FormatUTF8 = struct { buf: []const u8, + escape_backslashes: bool = false, pub fn format(self: @This(), comptime _: []const u8, _: anytype, writer: anytype) !void { - try writer.writeAll(self.buf); + if (self.escape_backslashes) { + var ptr = self.buf; + while (strings.indexOfChar(ptr, '\\')) |i| { + try writer.writeAll(ptr[0 .. i + 1]); + try writer.writeAll("\\"); + ptr = ptr[i + 1 ..]; + } + try writer.writeAll(ptr); + } else { + try writer.writeAll(self.buf); + } } }; +pub const PathFormatOptions = struct { + escape_backslashes: bool = false, +}; + pub fn fmtUTF16(buf: []const u16) FormatUTF16 { return FormatUTF16{ .buf = buf }; } pub const FormatOSPath = if (Environment.isWindows) FormatUTF16 else FormatUTF8; -pub fn fmtOSPath(buf: bun.OSPathSlice) FormatOSPath { - return FormatOSPath{ .buf = buf }; +pub fn fmtOSPath(buf: bun.OSPathSlice, options: PathFormatOptions) FormatOSPath { + return FormatOSPath{ + .buf = buf, + .escape_backslashes = options.escape_backslashes, + }; +} + +pub fn fmtPath( + comptime T: type, + path: []const T, + options: PathFormatOptions, +) if (T == u8) FormatUTF8 else FormatUTF16 { + if (T == u8) { + return FormatUTF8{ + .buf = path, + .escape_backslashes = options.escape_backslashes, + }; + } + + return FormatUTF16{ + .buf = path, + .escape_backslashes = options.escape_backslashes, + }; } pub fn formatLatin1(slice_: []const u8, writer: anytype) !void { diff --git a/src/install/install.zig b/src/install/install.zig index 4fdcaa578f..8c09a31752 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -1291,7 +1291,7 @@ pub const PackageInstall = struct { progress_.refresh(); - Output.prettyErrorln("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path) }); + Output.prettyErrorln("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); Global.crash(); }; }; @@ -1316,7 +1316,7 @@ pub const PackageInstall = struct { progress_.refresh(); - Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path) }); + Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); Global.crash(); }; } else { @@ -1330,7 +1330,7 @@ pub const PackageInstall = struct { progress_.refresh(); - Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path) }); + Output.prettyError("{s}: copying file {}", .{ @errorName(err), bun.fmt.fmtOSPath(entry.path, .{}) }); Global.crash(); }; } @@ -1378,8 +1378,8 @@ pub const PackageInstall = struct { defer subdir.close(); - var buf: if (Environment.isWindows) bun.WPathBuffer else [0]u16 = undefined; - var buf2: if (Environment.isWindows) bun.WPathBuffer else [0]u16 = undefined; + var buf: bun.windows.WPathBuffer = undefined; + var buf2: bun.windows.WPathBuffer = undefined; var to_copy_buf: []u16 = undefined; var to_copy_buf2: []u16 = undefined; if (comptime Environment.isWindows) { diff --git a/src/libarchive/libarchive.zig b/src/libarchive/libarchive.zig index d5d00754e0..542f013175 100644 --- a/src/libarchive/libarchive.zig +++ b/src/libarchive/libarchive.zig @@ -548,7 +548,7 @@ pub const Archive = struct { const path_slice: bun.OSPathSlice = pathname.ptr[0..pathname.len]; if (comptime log) { - Output.prettyln(" {}", .{bun.fmt.fmtOSPath(path_slice)}); + Output.prettyln(" {}", .{bun.fmt.fmtOSPath(path_slice, .{})}); } count += 1; @@ -699,7 +699,7 @@ pub const Archive = struct { lib.ARCHIVE_RETRY => { if (comptime log) { Output.err("libarchive error", "extracting {}, retry {d} / {d}", .{ - bun.fmt.fmtOSPath(path_slice), + bun.fmt.fmtOSPath(path_slice, .{}), retries_remaining, 5, }); @@ -709,7 +709,7 @@ pub const Archive = struct { if (comptime log) { const archive_error = std.mem.span(lib.archive_error_string(archive)); Output.err("libarchive error", "extracting {}: {s}", .{ - bun.fmt.fmtOSPath(path_slice), + bun.fmt.fmtOSPath(path_slice, .{}), archive_error, }); } diff --git a/test/transpiler/macro-test.test.ts b/test/transpiler/macro-test.test.ts index a7de24f6b1..401168bb10 100644 --- a/test/transpiler/macro-test.test.ts +++ b/test/transpiler/macro-test.test.ts @@ -1,5 +1,3 @@ -// @known-failing-on-windows: panic "TODO on Windows" - import { expect, test } from "bun:test"; import { addStrings, addStringsUTF16, escape, identity } from "./macro.ts" assert { type: "macro" }; import { escapeHTML } from "bun" assert { type: "macro" };