Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions cmake/targets/BuildBun.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -1260,6 +1260,7 @@ endif()

if(WIN32)
target_link_libraries(${bun} PRIVATE
advapi32
winmm
bcrypt
ntdll
Expand Down Expand Up @@ -1308,32 +1309,32 @@ if(NOT BUN_CPP_ONLY)
OUTPUTS
${BUILD_PATH}/${bunStripExe}
)

# Then sign both executables on Windows
if(WIN32 AND ENABLE_WINDOWS_CODESIGNING)
set(SIGN_SCRIPT "${CMAKE_SOURCE_DIR}/.buildkite/scripts/sign-windows.ps1")

# Verify signing script exists
if(NOT EXISTS "${SIGN_SCRIPT}")
message(FATAL_ERROR "Windows signing script not found: ${SIGN_SCRIPT}")
endif()

# Use PowerShell for Windows code signing (native Windows, no path issues)
find_program(POWERSHELL_EXECUTABLE
find_program(POWERSHELL_EXECUTABLE
NAMES pwsh.exe powershell.exe
PATHS
PATHS
"C:/Program Files/PowerShell/7"
"C:/Program Files (x86)/PowerShell/7"
"C:/Windows/System32/WindowsPowerShell/v1.0"
DOC "Path to PowerShell executable"
)

if(NOT POWERSHELL_EXECUTABLE)
set(POWERSHELL_EXECUTABLE "powershell.exe")
endif()

message(STATUS "Using PowerShell executable: ${POWERSHELL_EXECUTABLE}")

# Sign both bun-profile.exe and bun.exe after stripping
register_command(
TARGET
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const test_skipped = [
"uv_getrusage_thread",
"uv_os_homedir",
"uv_thread_detach",
"uv_thread_getname",
"uv_thread_getpriority",
Expand Down
4 changes: 2 additions & 2 deletions src/bun.js/bindings/uv-posix-stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1129,10 +1129,10 @@ UV_EXTERN int uv_os_getpriority(uv_pid_t pid, int* priority)
__builtin_unreachable();
}

extern int bunuv__os_homedir(char* buffer, size_t* size);
UV_EXTERN int uv_os_homedir(char* buffer, size_t* size)
{
__bun_throw_not_implemented("uv_os_homedir");
__builtin_unreachable();
return bunuv__os_homedir(buffer, size);
}

UV_EXTERN int uv_os_setenv(const char* name, const char* value)
Expand Down
79 changes: 6 additions & 73 deletions src/bun.js/node/node_os.zig
Original file line number Diff line number Diff line change
Expand Up @@ -302,80 +302,13 @@ pub fn getPriority(global: *jsc.JSGlobalObject, pid: i32) bun.JSError!i32 {
}

pub fn homedir(global: *jsc.JSGlobalObject) !bun.String {
// In Node.js, this is a wrapper around uv_os_homedir.
if (Environment.isWindows) {
var out: bun.PathBuffer = undefined;
var size: usize = out.len;
if (libuv.uv_os_homedir(&out, &size).toError(.uv_os_homedir)) |err| {
switch (bun.os.queryHomeDir()) {
.result => |r| {
return bun.String.cloneUTF8(r);
},
.err => |err| {
return global.throwValue(err.toJS(global));
}
return bun.String.cloneUTF8(out[0..size]);
} else {

// The posix implementation of uv_os_homedir first checks the HOME
// environment variable, then falls back to reading the passwd entry.
if (bun.getenvZ("HOME")) |home| {
if (home.len > 0)
return bun.String.init(home);
}

// From libuv:
// > Calling sysconf(_SC_GETPW_R_SIZE_MAX) would get the suggested size, but it
// > is frequently 1024 or 4096, so we can just use that directly. The pwent
// > will not usually be large.
// Instead of always using an allocation, first try a stack allocation
// of 4096, then fallback to heap.
var stack_string_bytes: [4096]u8 = undefined;
var string_bytes: []u8 = &stack_string_bytes;
defer if (string_bytes.ptr != &stack_string_bytes)
bun.default_allocator.free(string_bytes);

var pw: bun.c.passwd = undefined;
var result: ?*bun.c.passwd = null;

const ret = while (true) {
const ret = bun.c.getpwuid_r(
bun.c.geteuid(),
&pw,
string_bytes.ptr,
string_bytes.len,
&result,
);

if (ret == @intFromEnum(bun.sys.E.INTR))
continue;

// If the system call wants more memory, double it.
if (ret == @intFromEnum(bun.sys.E.RANGE)) {
const len = string_bytes.len;
bun.default_allocator.free(string_bytes);
string_bytes = "";
string_bytes = try bun.default_allocator.alloc(u8, len * 2);
continue;
}

break ret;
};

if (ret != 0) {
return global.throwValue(bun.sys.Error.fromCode(
@enumFromInt(ret),
.uv_os_homedir,
).toJS(global));
}

if (result == null) {
// in uv__getpwuid_r, null result throws UV_ENOENT.
return global.throwValue(bun.sys.Error.fromCode(
.NOENT,
.uv_os_homedir,
).toJS(global));
}

return if (pw.pw_dir) |dir|
bun.String.cloneUTF8(bun.span(dir))
else
bun.String.empty;
},
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/bun.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3798,6 +3798,8 @@ pub fn getUseSystemCA(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFra
return jsc.JSValue.jsBoolean(Arguments.Bun__Node__UseSystemCA);
}

pub const os = @import("./os.zig");

const CopyFile = @import("./copy_file.zig");
const builtin = @import("builtin");
const std = @import("std");
Expand Down
2 changes: 2 additions & 0 deletions src/c-headers-for-zig.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
#include "../packages/bun-native-bundler-plugin-api/bundler_plugin.h"

#if POSIX
#include <errno.h>
#include <ifaddrs.h>
#include <limits.h>
#include <netdb.h>
#include <pwd.h>
#include <stdlib.h>
Expand Down
15 changes: 7 additions & 8 deletions src/cli/install_completions_command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub const InstallCompletionsCommand = struct {

// if that fails, try $HOME/.bun/bin
outer: {
if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
if (bun.os.queryHomeDir().asValue()) |home_dir| {
target = std.fmt.bufPrint(&target_buf, "{s}/.bun/bin/" ++ bunx_name, .{home_dir}) catch unreachable;
std.posix.symlink(exe, target) catch break :outer;
return;
Expand All @@ -34,7 +34,7 @@ pub const InstallCompletionsCommand = struct {

// if that fails, try $HOME/.local/bin
outer: {
if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
if (bun.os.queryHomeDir().asValue()) |home_dir| {
target = std.fmt.bufPrint(&target_buf, "{s}/.local/bin/" ++ bunx_name, .{home_dir}) catch unreachable;
std.posix.symlink(exe, target) catch break :outer;
return;
Expand Down Expand Up @@ -229,7 +229,7 @@ pub const InstallCompletionsCommand = struct {
}
}

if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
if (bun.os.queryHomeDir().asValue()) |home_dir| {
outer: {
var paths = [_]string{ home_dir, "./.config/fish/completions" };
completions_dir = resolve_path.joinAbsString(cwd, &paths, .auto);
Expand Down Expand Up @@ -287,7 +287,7 @@ pub const InstallCompletionsCommand = struct {
}
}

if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
if (bun.os.queryHomeDir().asValue()) |home_dir| {
{
outer: {
var paths = [_]string{ home_dir, "./.oh-my-zsh/completions" };
Expand Down Expand Up @@ -339,7 +339,7 @@ pub const InstallCompletionsCommand = struct {
}
}

if (bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
if (bun.os.queryHomeDir().asValue()) |home_dir| {
{
outer: {
var paths = [_]string{ home_dir, "./.oh-my-bash/custom/completions" };
Expand Down Expand Up @@ -449,7 +449,7 @@ pub const InstallCompletionsCommand = struct {
}

second: {
if (bun.getenvZ(bun.DotEnv.home_env)) |zdot_dir| {
if (bun.os.queryHomeDir().asValue()) |zdot_dir| {
bun.copy(u8, &zshrc_filepath, zdot_dir);
bun.copy(u8, zshrc_filepath[zdot_dir.len..], "/.zshrc");
zshrc_filepath[zdot_dir.len + "/.zshrc".len] = 0;
Expand All @@ -459,7 +459,7 @@ pub const InstallCompletionsCommand = struct {
}

third: {
if (bun.getenvZ(bun.DotEnv.home_env)) |zdot_dir| {
if (bun.os.queryHomeDir().asValue()) |zdot_dir| {
bun.copy(u8, &zshrc_filepath, zdot_dir);
bun.copy(u8, zshrc_filepath[zdot_dir.len..], "/.zshenv");
zshrc_filepath[zdot_dir.len + "/.zshenv".len] = 0;
Expand Down Expand Up @@ -531,7 +531,6 @@ pub const InstallCompletionsCommand = struct {

const string = []const u8;

const DotEnv = @import("../env_loader.zig");
const ShellCompletions = @import("./shell_completions.zig");
const fs = @import("../fs.zig");
const resolve_path = @import("../resolver/resolve_path.zig");
Expand Down
10 changes: 8 additions & 2 deletions src/cli/upgrade_command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ pub const UpgradeCommand = struct {
save_dir.deleteFileZ(tmpname) catch {};
Global.exit(1);
}
} else if (Environment.isWindows) {
} else if (comptime Environment.isWindows) {
// Run a powershell script to unzip the file
const unzip_script = try std.fmt.allocPrint(
ctx.allocator,
Expand All @@ -572,7 +572,13 @@ pub const UpgradeCommand = struct {
const powershell_path =
bun.which(&buf, bun.getenvZ("PATH") orelse "", "", "powershell") orelse
hardcoded_system_powershell: {
const system_root = bun.getenvZ("SystemRoot") orelse "C:\\Windows";
const system_root = switch (bun.os.win32.queryWinDir()) {
.err => {
Output.prettyErrorln("<r><red>error:<r> Failed to unzip {s} due to PowerShell not being installed.", .{tmpname});
Global.exit(1);
},
.result => |v| v,
};
const hardcoded_system_powershell = bun.path.joinAbsStringBuf(system_root, &buf, &.{ system_root, "System32\\WindowsPowerShell\\v1.0\\powershell.exe" }, .windows);
if (bun.sys.exists(hardcoded_system_powershell)) {
break :hardcoded_system_powershell hardcoded_system_powershell;
Expand Down
42 changes: 7 additions & 35 deletions src/fs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -530,43 +530,15 @@ pub const FileSystem = struct {
file_limit: usize = 32,
file_quota: usize = 32,

pub var win_tempdir_cache: ?[]const u8 = undefined;

pub fn platformTempDir() []const u8 {
return switch (Environment.os) {
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw#remarks
.windows => win_tempdir_cache orelse {
const value = bun.getenvZ("TEMP") orelse bun.getenvZ("TMP") orelse brk: {
if (bun.getenvZ("SystemRoot") orelse bun.getenvZ("windir")) |windir| {
break :brk std.fmt.allocPrint(
bun.default_allocator,
"{s}\\Temp",
.{strings.withoutTrailingSlash(windir)},
) catch |err| bun.handleOom(err);
}

if (bun.getenvZ("USERPROFILE")) |profile| {
var buf: bun.PathBuffer = undefined;
var parts = [_]string{"AppData\\Local\\Temp"};
const out = bun.path.joinAbsStringBuf(profile, &buf, &parts, .loose);
break :brk bun.handleOom(bun.default_allocator.dupe(u8, out));
}

var tmp_buf: bun.PathBuffer = undefined;
const cwd = std.posix.getcwd(&tmp_buf) catch @panic("Failed to get cwd for platformTempDir");
const root = bun.path.windowsFilesystemRoot(cwd);
break :brk std.fmt.allocPrint(
bun.default_allocator,
"{s}\\Windows\\Temp",
.{strings.withoutTrailingSlash(root)},
) catch |err| bun.handleOom(err);
};
win_tempdir_cache = value;
return value;
switch (bun.os.querySysTmpDir()) {
.result => |s| {
return s;
},
.mac => "/private/tmp",
else => "/tmp",
};
.err => |e| {
bun.Output.panic("Could not retrieve the system temporary directory: {}", .{e});
},
}
}

pub const Tmpfile = switch (Environment.os) {
Expand Down
3 changes: 2 additions & 1 deletion src/install/PackageManager.zig
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,8 @@ pub fn init(
try env.load(entries_option.entries, &[_][]u8{}, .production, false);

initializeStore();
if (bun.getenvZ("XDG_CONFIG_HOME") orelse bun.getenvZ(bun.DotEnv.home_env)) |data_dir| {

if (bun.getenvZ("XDG_CONFIG_HOME") orelse bun.os.queryHomeDir().asValue()) |data_dir| {
var buf: bun.PathBuffer = undefined;
var parts = [_]string{
"./.npmrc",
Expand Down
2 changes: 1 addition & 1 deletion src/install/PackageManager/PackageManagerOptions.zig
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ pub fn openGlobalBinDir(opts_: ?*const Api.BunInstall) !std.fs.Dir {
return try std.fs.cwd().makeOpenPath(path, .{});
}

if (bun.getenvZ("XDG_CACHE_HOME") orelse bun.getenvZ(bun.DotEnv.home_env)) |home_dir| {
if (bun.getenvZ("XDG_CACHE_HOME") orelse bun.os.queryHomeDir().asValue()) |home_dir| {
var buf: bun.PathBuffer = undefined;
var parts = [_]string{
".bun",
Expand Down
16 changes: 2 additions & 14 deletions src/install/repository.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,9 @@ const SloppyGlobalGitConfig = struct {
}

pub fn loadAndParse() void {
const home_dir_path = brk: {
if (comptime Environment.isWindows) {
if (bun.getenvZ("USERPROFILE")) |env|
break :brk env;
} else {
if (bun.getenvZ("HOME")) |env|
break :brk env;
}

// won't find anything
return;
};

const home_dir = (bun.os.queryHomeDir()).unwrap() catch return;
var config_file_path_buf: bun.PathBuffer = undefined;
const config_file_path = bun.path.joinAbsStringBufZ(home_dir_path, &config_file_path_buf, &.{".gitconfig"}, .auto);
const config_file_path = bun.path.joinAbsStringBufZ(home_dir, &config_file_path_buf, &.{".gitconfig"}, .auto);
var stack_fallback = std.heap.stackFallback(4096, bun.default_allocator);
const allocator = stack_fallback.get();
const source = File.toSource(config_file_path, allocator, .{ .convert_bom = true }).unwrap() catch {
Expand Down
2 changes: 1 addition & 1 deletion src/linker.lds
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ BUN_1.2 {
uv_write2;
uv_wtf8_length_as_utf16;
uv_wtf8_to_utf16;


extern "C++" {
v8::*;
Expand Down
19 changes: 19 additions & 0 deletions src/memory.zig
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,25 @@ pub fn dropSentinel(ptr: anytype, allocator: std.mem.Allocator) blk: {
return allocator.dupe(Child, slice);
}

extern "C" fn __lsan_ignore_object(object: *const anyopaque) callconv(.C) void;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify LSan symbol availability across build configurations.

The extern declaration for __lsan_ignore_object will cause linker errors if LeakSanitizer is not available or enabled. Even though the symbol is only called in debug builds (line 215), the extern declaration itself must resolve at link time in all configurations.

Consider adding a conditional compilation guard or verifying that LSan is always linked in your build system. Run the following script to check how the build system handles sanitizer flags:


🏁 Script executed:

#!/bin/bash
# Check build configurations and sanitizer flags
echo "=== Checking build scripts for sanitizer configuration ==="
rg -n -C3 'sanitize|lsan|LSAN' --type-add 'build:*.{zig,cmake,make,sh,json}' --type build

echo -e "\n=== Checking if __lsan_ignore_object is used elsewhere ==="
ast-grep --pattern '__lsan_ignore_object'

Length of output: 16242


Based on my verification analysis, the original review comment raises a legitimate portability concern. Here's what I found:

  • AddressSanitizer integrates LeakSanitizer and enables it by default on supported platforms, but LeakSanitizer is supported on x86_64 Linux and macOS.
  • The build system installs liblsan0 and conditionally enables ASAN with -fsanitize=address, but the new __lsan_ignore_object extern declaration in line 205 is unconditional.
  • Even though the call to __lsan_ignore_object at line 217 is guarded by debug checks, the extern declaration must resolve at link time regardless of runtime conditions.
  • The codebase already demonstrates the correct pattern in src/asan.zig where extern symbols are conditionally declared based on bun.Environment.enable_asan.

The extern declaration should be conditionally compiled to avoid linker errors on builds where:

  • LSan is not explicitly enabled
  • Non-LLVM/Clang compilers are used
  • Unsupported platforms are targeted
  • Builds disable sanitizers

The approach followed in src/asan.zig should be mirrored here to ensure the symbol is only declared when available.

🤖 Prompt for AI Agents
In src/memory.zig around line 205, the extern declaration for
__lsan_ignore_object is unconditional which can cause linker errors on
platforms/builds without LeakSanitizer; change it to follow the pattern in
src/asan.zig by wrapping the extern declaration in a comptime conditional (e.g.,
bun.Environment.enable_asan or the same compile-time predicate used in asan.zig)
so the symbol is only declared when ASAN/LSan is enabled and supported, and
remove the unconditional extern.


/// Mark the given object as intentionally leaked, so that leak sanitizers do not report it.
pub fn INTENTIONALLY_LEAK(
comptime alloc: anytype,
object: *anyopaque,
comptime why: []const u8,
) void {
_ = why;

if (comptime bun.Environment.isDebug) {
if (comptime bun.allocators.isDefault(alloc)) {
__lsan_ignore_object(object);
} else {
@compileError("INTENTIONALLY_LEAK is not yet implemented for non-default allocators");
}
}
}

const std = @import("std");
const Allocator = std.mem.Allocator;

Expand Down
Loading