Skip to content
Merged
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
37 changes: 22 additions & 15 deletions src/windows_sys/externs.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
//! Raw Win32 extern fn declarations + tier-0 Win32 typedefs.
//! `bun_sys::windows` re-exports FROM here (see the layering doc). This crate is a tier-0 leaf: it depends on nothing above
//! `libuv_sys`.
//!
//! `#[link(name = "...")]` on every `extern` block is wrapped in
//! `#[cfg_attr(windows, ...)]`. This crate is depended on unconditionally (not
//! behind `[target.'cfg(windows)']`) by several workspace members that need
//! the POD typedefs on every target, so an unconditional `#[link]` would bake
//! `-lntdll`/`-lkernel32`/... into the rlib and break linking any standalone
//! test/bench binary on a non-Windows host.

use core::ffi::{c_char, c_int, c_long, c_short, c_uint, c_ulong, c_ushort, c_void};

Expand Down Expand Up @@ -544,7 +551,7 @@ impl FILE_INFORMATION_CLASS {
pub mod ntdll {
use super::*;

#[link(name = "ntdll")]
#[cfg_attr(windows, link(name = "ntdll"))]
unsafe extern "system" {
pub fn RtlCaptureStackBackTrace(
FramesToSkip: u32,
Expand Down Expand Up @@ -692,7 +699,7 @@ pub mod kernel32 {
}
pub const MEM_FREE: u32 = 0x10000;

#[link(name = "kernel32")]
#[cfg_attr(windows, link(name = "kernel32"))]
unsafe extern "system" {
/// No preconditions; reads thread-local Win32 error slot.
pub safe fn GetLastError() -> DWORD;
Expand Down Expand Up @@ -775,7 +782,7 @@ pub mod kernel32 {
Add: BOOL,
) -> BOOL;
}
#[link(name = "kernel32")]
#[cfg_attr(windows, link(name = "kernel32"))]
unsafe extern "system" {
/// `GetConsoleScreenBufferInfo` (`wincon.h`).
pub fn GetConsoleScreenBufferInfo(
Expand Down Expand Up @@ -838,7 +845,7 @@ pub const INFINITE: DWORD = 0xFFFF_FFFF;
pub const WAIT_FAILED: DWORD = 0xFFFF_FFFF;
pub const STARTF_USESTDHANDLES: DWORD = 0x0000_0100;

#[link(name = "kernel32")]
#[cfg_attr(windows, link(name = "kernel32"))]
unsafe extern "system" {
#[link_name = "WaitForSingleObject"]
fn WaitForSingleObject_raw(hHandle: HANDLE, dwMilliseconds: DWORD) -> DWORD;
Expand Down Expand Up @@ -919,7 +926,7 @@ pub const fn NT_ERROR(status: NTSTATUS) -> bool {
}
pub const STATUS_SUCCESS: NTSTATUS = NTSTATUS::SUCCESS;

#[link(name = "ntdll")]
#[cfg_attr(windows, link(name = "ntdll"))]
unsafe extern "system" {
/// Total over `NTSTATUS`; no preconditions.
pub safe fn RtlNtStatusToDosError(status: NTSTATUS) -> DWORD;
Expand Down Expand Up @@ -953,7 +960,7 @@ pub mod ws2_32 {
pub ai_next: *mut addrinfo,
}

#[link(name = "ws2_32")]
#[cfg_attr(windows, link(name = "ws2_32"))]
unsafe extern "system" {
pub fn getaddrinfo(
node: *const c_char,
Expand Down Expand Up @@ -1142,7 +1149,7 @@ pub mod ws2_32 {
pub const WSA_QOS_RESERVED_PETYPE: Self = Self(11031);
}

#[link(name = "ws2_32")]
#[cfg_attr(windows, link(name = "ws2_32"))]
unsafe extern "system" {
/// Raw `WSAGetLastError`. The `Option<SystemErrno>` wrapper lives in `errno`
/// because `SystemErrno` is a higher-tier type. No preconditions; reads
Expand Down Expand Up @@ -1330,12 +1337,12 @@ impl Win32Error {
pub type LPDWORD = *mut DWORD;
pub type HPCON = *mut c_void;

#[link(name = "shell32")]
#[cfg_attr(windows, link(name = "shell32"))]
unsafe extern "system" {
pub fn CommandLineToArgvW(lpCmdLine: LPCWSTR, pNumArgs: *mut c_int) -> *mut LPWSTR;
}

#[link(name = "kernel32")]
#[cfg_attr(windows, link(name = "kernel32"))]
unsafe extern "system" {
pub fn GetFileInformationByHandle(
hFile: HANDLE,
Expand Down Expand Up @@ -1389,7 +1396,7 @@ pub struct SYSTEM_INFO {
pub wProcessorLevel: WORD,
pub wProcessorRevision: WORD,
}
#[link(name = "kernel32")]
#[cfg_attr(windows, link(name = "kernel32"))]
unsafe extern "system" {
pub fn GetSystemInfo(lpSystemInfo: *mut SYSTEM_INFO);
}
Expand All @@ -1398,7 +1405,7 @@ pub const TOKEN_QUERY: DWORD = 0x0008;
/// `TOKEN_INFORMATION_CLASS::TokenIsAppContainer`
pub const TOKEN_IS_APP_CONTAINER: c_int = 29;

#[link(name = "advapi32")]
#[cfg_attr(windows, link(name = "advapi32"))]
unsafe extern "system" {
pub fn SaferiIsExecutableFileType(szFullPathname: LPCWSTR, bFromShellExecute: BOOLEAN) -> BOOL;

Expand All @@ -1420,7 +1427,7 @@ unsafe extern "system" {
// `GetProcAddress`/`LoadLibraryA` are kernel32 stdcall — use `extern "system"` so the
// callconv is correct on all targets (winapi == C only on x64). `GetProcAddress`
// takes `LPCSTR` (narrow), not wide.
#[link(name = "kernel32")]
#[cfg_attr(windows, link(name = "kernel32"))]
unsafe extern "system" {
pub fn GetProcAddress(ptr: *mut c_void, name: *const c_char) -> *mut c_void;

Expand All @@ -1429,7 +1436,7 @@ unsafe extern "system" {

// Declared as `extern "system"` so the callconv is correct on all targets
// (winapi == C only on x64).
#[link(name = "kernel32")]
#[cfg_attr(windows, link(name = "kernel32"))]
unsafe extern "system" {
pub fn CopyFileW(source: LPCWSTR, dest: LPCWSTR, bFailIfExists: BOOL) -> BOOL;

Expand Down Expand Up @@ -1753,7 +1760,7 @@ pub const CTRL_CLOSE_EVENT: DWORD = 2;
pub const CTRL_LOGOFF_EVENT: DWORD = 5;
pub const CTRL_SHUTDOWN_EVENT: DWORD = 6;

#[link(name = "kernel32")]
#[cfg_attr(windows, link(name = "kernel32"))]
unsafe extern "system" {
pub fn CreateDirectoryExW(
lpTemplateDirectory: *const u16,
Expand Down Expand Up @@ -1789,7 +1796,7 @@ unsafe extern "C" {
pub safe fn GetConsoleCP() -> u32;
}

#[link(name = "kernel32")]
#[cfg_attr(windows, link(name = "kernel32"))]
unsafe extern "system" {
/// No preconditions; returns 0 on failure.
pub safe fn SetConsoleCP(wCodePageID: UINT) -> BOOL;
Expand Down
43 changes: 43 additions & 0 deletions test/internal/rust-windows-sys-link.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// bun_windows_sys is a #![no_std] leaf crate that several workspace crates
// (bun_errno, bun_spawn_sys) depend on unconditionally so that Win32 POD types
// like IO_COUNTERS resolve on every target. Its #[link(name = "...")]
// attributes must therefore be gated behind cfg(windows), or the linker for
// any standalone test binary on a non-Windows host sees
// ld.lld: error: unable to find library -lntdll / -lkernel32 / ...
// as soon as bun_windows_sys.rlib lands on the link line.
//
// `cargo check --tests` type-checks only and will not catch this; it has to
// be `cargo test --no-run` so rustc actually drives the linker. The crate has
// zero dependencies, so linking completes in well under a second on a warm tree.
import { which } from "bun";
import { expect, test } from "bun:test";
import { isWindows } from "harness";
import { existsSync } from "node:fs";
import { join } from "node:path";

const cargo = which("cargo");
const repoRoot = join(import.meta.dir, "..", "..");
// Cargo parses the whole workspace manifest (including path deps) before
// applying -p, so it needs vendor/lolhtml on disk even for a zero-dep crate.
// Test-only CI lanes run a prebuilt binary and never fetch it; skip there.
// Same prerequisite check as linear-fifo.test.ts / scripts/rust-miri.ts.
const workspaceResolvable =
existsSync(join(repoRoot, "vendor", "lolhtml", "Cargo.toml")) &&
existsSync(join(repoRoot, "build", "debug", "codegen", "build_options.rs"));

test.skipIf(isWindows || !cargo || !workspaceResolvable)(
"cargo test -p bun_windows_sys links on non-Windows hosts",
async () => {
await using proc = Bun.spawn({
cmd: [cargo!, "test", "--locked", "-p", "bun_windows_sys", "--no-run", "--quiet"],
cwd: repoRoot,
env: { ...process.env, CARGO_TERM_COLOR: "never" },
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stderr).not.toContain("unable to find library");
expect({ stdout, stderr, exitCode }).toMatchObject({ exitCode: 0 });
},
);
30 changes: 30 additions & 0 deletions test/internal/source-lints/windows-sys-link-cfg.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import path from "node:path";

// bun_windows_sys is depended on unconditionally (not behind
// [target.'cfg(windows)']) by several workspace crates that need its Win32 POD
// typedefs on every target. A bare #[link(name = "...")] on any extern block
// therefore bakes -lntdll / -lkernel32 / ... into the rlib on non-Windows
// hosts, and cargo test / cargo bench for any transitive dependent fails to
// link with "ld.lld: error: unable to find library -lntdll".
//
// The bare spelling is correct elsewhere in the tree (e.g. backend_wic.rs,
// threading/Mutex.rs) where the enclosing module is already #[cfg(windows)],
// so this lint is scoped to externs.rs only.
test("bun_windows_sys: every #[link(name = ...)] is gated behind cfg(windows)", () => {
const file = path.resolve(import.meta.dir, "..", "..", "..", "src", "windows_sys", "externs.rs");
const source = readFileSync(file, "utf8");

const violations: string[] = [];
for (const [lineIndex, line] of source.split("\n").entries()) {
const code = line.replace(/\/\/.*/, "");
// Bare `#[link(name = ...)]` not wrapped in cfg_attr. `#[link_name = ...]`
// (symbol rename) is a different attribute and is fine.
if (/#\[\s*link\s*\(\s*name\s*=/.test(code) && !/cfg_attr\s*\(\s*windows/.test(code)) {
violations.push(`src/windows_sys/externs.rs:${lineIndex + 1}: ${line.trim()}`);
}
}

expect(violations).toEqual([]);
});
Loading