mirror of
https://github.com/oven-sh/bun
synced 2026-02-21 08:12:21 +00:00
Compare commits
1 Commits
toaster/fi
...
claude/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89e492240c |
@@ -1,5 +1,5 @@
|
||||
ARG LLVM_VERSION="21"
|
||||
ARG REPORTED_LLVM_VERSION="21.1.8"
|
||||
ARG LLVM_VERSION="19"
|
||||
ARG REPORTED_LLVM_VERSION="19.1.7"
|
||||
ARG OLD_BUN_VERSION="1.1.38"
|
||||
ARG BUILDKITE_AGENT_TAGS="queue=linux,os=linux,arch=${TARGETARCH}"
|
||||
|
||||
|
||||
@@ -109,12 +109,12 @@ const buildPlatforms = [
|
||||
{ os: "linux", arch: "x64", distro: "amazonlinux", release: "2023", features: ["docker"] },
|
||||
{ os: "linux", arch: "x64", baseline: true, distro: "amazonlinux", release: "2023", features: ["docker"] },
|
||||
{ os: "linux", arch: "x64", profile: "asan", distro: "amazonlinux", release: "2023", features: ["docker"] },
|
||||
{ os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.23" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.23" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.23" },
|
||||
{ os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.22" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.22" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.22" },
|
||||
{ os: "windows", arch: "x64", release: "2019" },
|
||||
{ os: "windows", arch: "x64", baseline: true, release: "2019" },
|
||||
// TODO: Re-enable when Windows ARM64 VS component installation is resolved on Buildkite runners
|
||||
// TODO: Enable when Windows ARM64 CI runners are ready
|
||||
// { os: "windows", arch: "aarch64", release: "2019" },
|
||||
];
|
||||
|
||||
@@ -133,9 +133,9 @@ const testPlatforms = [
|
||||
{ os: "linux", arch: "aarch64", distro: "ubuntu", release: "25.04", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", distro: "ubuntu", release: "25.04", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "25.04", tier: "latest" },
|
||||
{ os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.23", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.23", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.23", tier: "latest" },
|
||||
{ os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.22", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.22", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.22", tier: "latest" },
|
||||
{ os: "windows", arch: "x64", release: "2019", tier: "oldest" },
|
||||
{ os: "windows", arch: "x64", release: "2019", baseline: true, tier: "oldest" },
|
||||
// TODO: Enable when Windows ARM64 CI runners are ready
|
||||
@@ -304,13 +304,6 @@ function getCppAgent(platform, options) {
|
||||
};
|
||||
}
|
||||
|
||||
// Cross-compile Windows ARM64 from x64 runners
|
||||
if (os === "windows" && arch === "aarch64") {
|
||||
return getEc2Agent({ ...platform, arch: "x64" }, options, {
|
||||
instanceType: "c7i.4xlarge",
|
||||
});
|
||||
}
|
||||
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: arch === "aarch64" ? "c8g.4xlarge" : "c7i.4xlarge",
|
||||
});
|
||||
@@ -333,10 +326,8 @@ function getLinkBunAgent(platform, options) {
|
||||
}
|
||||
|
||||
if (os === "windows") {
|
||||
// Cross-compile Windows ARM64 from x64 runners
|
||||
const agentPlatform = arch === "aarch64" ? { ...platform, arch: "x64" } : platform;
|
||||
return getEc2Agent(agentPlatform, options, {
|
||||
instanceType: "r7i.large",
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: arch === "aarch64" ? "r8g.large" : "r7i.large",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -354,7 +345,7 @@ function getZigPlatform() {
|
||||
arch: "aarch64",
|
||||
abi: "musl",
|
||||
distro: "alpine",
|
||||
release: "3.23",
|
||||
release: "3.22",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -465,17 +456,6 @@ function getBuildCommand(target, options, label) {
|
||||
return `bun run build:${buildProfile}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extra flags needed when cross-compiling Windows ARM64 from x64.
|
||||
* Applied to C++ and link steps (not Zig, which has its own toolchain handling).
|
||||
*/
|
||||
function getWindowsArm64CrossFlags(target) {
|
||||
if (target.os === "windows" && target.arch === "aarch64") {
|
||||
return " --toolchain windows-aarch64";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Platform} platform
|
||||
* @param {PipelineOptions} options
|
||||
@@ -483,8 +463,6 @@ function getWindowsArm64CrossFlags(target) {
|
||||
*/
|
||||
function getBuildCppStep(platform, options) {
|
||||
const command = getBuildCommand(platform, options);
|
||||
const crossFlags = getWindowsArm64CrossFlags(platform);
|
||||
|
||||
return {
|
||||
key: `${getTargetKey(platform)}-build-cpp`,
|
||||
label: `${getTargetLabel(platform)} - build-cpp`,
|
||||
@@ -498,7 +476,7 @@ function getBuildCppStep(platform, options) {
|
||||
// We used to build the C++ dependencies and bun in separate steps.
|
||||
// However, as long as the zig build takes longer than both sequentially,
|
||||
// it's cheaper to run them in the same step. Can be revisited in the future.
|
||||
command: [`${command}${crossFlags} --target bun`, `${command}${crossFlags} --target dependencies`],
|
||||
command: [`${command} --target bun`, `${command} --target dependencies`],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -555,7 +533,7 @@ function getLinkBunStep(platform, options) {
|
||||
ASAN_OPTIONS: "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=0",
|
||||
...getBuildEnv(platform, options),
|
||||
},
|
||||
command: `${getBuildCommand(platform, options, "build-bun")}${getWindowsArm64CrossFlags(platform)} --target bun`,
|
||||
command: `${getBuildCommand(platform, options, "build-bun")} --target bun`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1201,8 +1179,6 @@ async function getPipeline(options = {}) {
|
||||
buildImages || publishImages
|
||||
? [...buildPlatforms, ...testPlatforms]
|
||||
.filter(({ os }) => os !== "darwin")
|
||||
// Windows ARM64 cross-compiles from x64 runners, no separate image needed
|
||||
.filter(({ os, arch }) => !(os === "windows" && arch === "aarch64"))
|
||||
.map(platform => [getImageKey(platform), platform])
|
||||
: [],
|
||||
);
|
||||
|
||||
@@ -219,8 +219,9 @@ function create_release() {
|
||||
bun-windows-x64-profile.zip
|
||||
bun-windows-x64-baseline.zip
|
||||
bun-windows-x64-baseline-profile.zip
|
||||
bun-windows-aarch64.zip
|
||||
bun-windows-aarch64-profile.zip
|
||||
# TODO: Enable when Windows ARM64 CI runners are ready
|
||||
# bun-windows-aarch64.zip
|
||||
# bun-windows-aarch64-profile.zip
|
||||
)
|
||||
|
||||
function upload_artifact() {
|
||||
|
||||
4
.github/workflows/CLAUDE.md
vendored
4
.github/workflows/CLAUDE.md
vendored
@@ -33,8 +33,8 @@ The workflow runs all three formatters simultaneously:
|
||||
|
||||
#### 3. Tool Installation
|
||||
|
||||
##### Clang-format-21
|
||||
- Installs ONLY `clang-format-21` package (not the entire LLVM toolchain)
|
||||
##### Clang-format-19
|
||||
- Installs ONLY `clang-format-19` package (not the entire LLVM toolchain)
|
||||
- Uses `--no-install-recommends --no-install-suggests` to skip unnecessary packages
|
||||
- Quiet installation with `-qq` and `-o=Dpkg::Use-Pty=0`
|
||||
|
||||
|
||||
4
.github/workflows/format.yml
vendored
4
.github/workflows/format.yml
vendored
@@ -10,8 +10,8 @@ on:
|
||||
merge_group:
|
||||
env:
|
||||
BUN_VERSION: "1.3.2"
|
||||
LLVM_VERSION: "21.1.8"
|
||||
LLVM_VERSION_MAJOR: "21"
|
||||
LLVM_VERSION: "19.1.7"
|
||||
LLVM_VERSION_MAJOR: "19"
|
||||
|
||||
jobs:
|
||||
autofix:
|
||||
|
||||
@@ -35,7 +35,7 @@ $ sudo pacman -S base-devel cmake git go libiconv libtool make ninja pkg-config
|
||||
```
|
||||
|
||||
```bash#Fedora
|
||||
$ sudo dnf install cargo clang21 llvm21 lld21 cmake git golang libtool ninja-build pkg-config rustc ruby libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)'
|
||||
$ sudo dnf install cargo clang19 llvm19 lld19 cmake git golang libtool ninja-build pkg-config rustc ruby libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)'
|
||||
```
|
||||
|
||||
```bash#openSUSE Tumbleweed
|
||||
@@ -90,17 +90,17 @@ Our build scripts will automatically detect and use `ccache` if available. You c
|
||||
|
||||
## Install LLVM
|
||||
|
||||
Bun requires LLVM 21.1.8 (`clang` is part of LLVM). This version is enforced by the build system — mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
|
||||
Bun requires LLVM 19 (`clang` is part of LLVM). This version requirement is to match WebKit (precompiled), as mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
|
||||
|
||||
{% codetabs group="os" %}
|
||||
|
||||
```bash#macOS (Homebrew)
|
||||
$ brew install llvm@21
|
||||
$ brew install llvm@19
|
||||
```
|
||||
|
||||
```bash#Ubuntu/Debian
|
||||
$ # LLVM has an automatic installation script that is compatible with all versions of Ubuntu
|
||||
$ wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 21 all
|
||||
$ wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 19 all
|
||||
```
|
||||
|
||||
```bash#Arch
|
||||
@@ -112,17 +112,17 @@ $ sudo dnf install llvm clang lld-devel
|
||||
```
|
||||
|
||||
```bash#openSUSE Tumbleweed
|
||||
$ sudo zypper install clang21 lld21 llvm21
|
||||
$ sudo zypper install clang19 lld19 llvm19
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
|
||||
If none of the above solutions apply, you will have to install it [manually](https://github.com/llvm/llvm-project/releases/tag/llvmorg-21.1.8).
|
||||
If none of the above solutions apply, you will have to install it [manually](https://github.com/llvm/llvm-project/releases/tag/llvmorg-19.1.7).
|
||||
|
||||
Make sure Clang/LLVM 21 is in your path:
|
||||
Make sure Clang/LLVM 19 is in your path:
|
||||
|
||||
```bash
|
||||
$ which clang-21
|
||||
$ which clang-19
|
||||
```
|
||||
|
||||
If not, run this to manually add it:
|
||||
@@ -131,13 +131,13 @@ If not, run this to manually add it:
|
||||
|
||||
```bash#macOS (Homebrew)
|
||||
# use fish_add_path if you're using fish
|
||||
# use path+="$(brew --prefix llvm@21)/bin" if you are using zsh
|
||||
$ export PATH="$(brew --prefix llvm@21)/bin:$PATH"
|
||||
# use path+="$(brew --prefix llvm@19)/bin" if you are using zsh
|
||||
$ export PATH="$(brew --prefix llvm@19)/bin:$PATH"
|
||||
```
|
||||
|
||||
```bash#Arch
|
||||
# use fish_add_path if you're using fish
|
||||
$ export PATH="$PATH:/usr/lib/llvm21/bin"
|
||||
$ export PATH="$PATH:/usr/lib/llvm19/bin"
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
@@ -299,7 +299,7 @@ The issue may manifest when initially running `bun setup` as Clang being unable
|
||||
```
|
||||
The C++ compiler
|
||||
|
||||
"/usr/bin/clang++-21"
|
||||
"/usr/bin/clang++-19"
|
||||
|
||||
is not able to compile a simple test program.
|
||||
```
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
// Benchmark for AbortController/AbortSignal abort() performance
|
||||
// Tests the optimization of skipping Event creation when no listeners are registered
|
||||
|
||||
import { bench, group, run } from "../runner.mjs";
|
||||
|
||||
// Warmup: ensure JIT compilation
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
}
|
||||
|
||||
group("AbortController.abort()", () => {
|
||||
bench("no listener", () => {
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
bench("with addEventListener", () => {
|
||||
const controller = new AbortController();
|
||||
controller.signal.addEventListener("abort", () => {});
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
bench("with onabort property", () => {
|
||||
const controller = new AbortController();
|
||||
controller.signal.onabort = () => {};
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
bench("with 3 listeners", () => {
|
||||
const controller = new AbortController();
|
||||
controller.signal.addEventListener("abort", () => {});
|
||||
controller.signal.addEventListener("abort", () => {});
|
||||
controller.signal.addEventListener("abort", () => {});
|
||||
controller.abort();
|
||||
});
|
||||
});
|
||||
|
||||
group("AbortSignal static methods", () => {
|
||||
bench("AbortSignal.abort() - pre-aborted", () => {
|
||||
const signal = AbortSignal.abort();
|
||||
// Signal is already aborted, no event dispatch needed
|
||||
});
|
||||
|
||||
bench("AbortSignal.any([]) - empty array", () => {
|
||||
const signal = AbortSignal.any([]);
|
||||
});
|
||||
|
||||
bench("AbortSignal.any([signal, signal]) - 2 signals", () => {
|
||||
const a = new AbortController();
|
||||
const b = new AbortController();
|
||||
const signal = AbortSignal.any([a.signal, b.signal]);
|
||||
});
|
||||
|
||||
bench("AbortSignal.any() then abort - no listener", () => {
|
||||
const a = new AbortController();
|
||||
const b = new AbortController();
|
||||
const signal = AbortSignal.any([a.signal, b.signal]);
|
||||
a.abort();
|
||||
});
|
||||
|
||||
bench("AbortSignal.any() then abort - with listener", () => {
|
||||
const a = new AbortController();
|
||||
const b = new AbortController();
|
||||
const signal = AbortSignal.any([a.signal, b.signal]);
|
||||
signal.addEventListener("abort", () => {});
|
||||
a.abort();
|
||||
});
|
||||
});
|
||||
|
||||
group("AbortController creation only", () => {
|
||||
bench("new AbortController()", () => {
|
||||
const controller = new AbortController();
|
||||
});
|
||||
|
||||
bench("new AbortController() + access signal", () => {
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
});
|
||||
});
|
||||
|
||||
group("AbortSignal.timeout()", () => {
|
||||
// Note: These don't actually wait for timeout, just measure creation overhead
|
||||
bench("AbortSignal.timeout(1000) creation", () => {
|
||||
const signal = AbortSignal.timeout(1000);
|
||||
});
|
||||
|
||||
bench("AbortSignal.timeout(0) creation", () => {
|
||||
const signal = AbortSignal.timeout(0);
|
||||
});
|
||||
});
|
||||
|
||||
group("abort with reason", () => {
|
||||
bench("abort() with no reason", () => {
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
bench("abort() with string reason", () => {
|
||||
const controller = new AbortController();
|
||||
controller.abort("cancelled");
|
||||
});
|
||||
|
||||
bench("abort() with Error reason", () => {
|
||||
const controller = new AbortController();
|
||||
controller.abort(new Error("cancelled"));
|
||||
});
|
||||
});
|
||||
|
||||
await run();
|
||||
@@ -105,10 +105,6 @@ summary(() => {
|
||||
bench(`small (${small.length} chars) - Bun.markdown.render`, () => {
|
||||
return Bun.markdown.render(small, renderCallbacks);
|
||||
});
|
||||
|
||||
bench(`small (${small.length} chars) - Bun.markdown.react`, () => {
|
||||
return Bun.markdown.react(small);
|
||||
});
|
||||
}
|
||||
|
||||
bench(`small (${small.length} chars) - marked`, () => {
|
||||
@@ -129,10 +125,6 @@ summary(() => {
|
||||
bench(`medium (${medium.length} chars) - Bun.markdown.render`, () => {
|
||||
return Bun.markdown.render(medium, renderCallbacks);
|
||||
});
|
||||
|
||||
bench(`medium (${medium.length} chars) - Bun.markdown.react`, () => {
|
||||
return Bun.markdown.react(medium);
|
||||
});
|
||||
}
|
||||
|
||||
bench(`medium (${medium.length} chars) - marked`, () => {
|
||||
@@ -153,10 +145,6 @@ summary(() => {
|
||||
bench(`large (${large.length} chars) - Bun.markdown.render`, () => {
|
||||
return Bun.markdown.render(large, renderCallbacks);
|
||||
});
|
||||
|
||||
bench(`large (${large.length} chars) - Bun.markdown.react`, () => {
|
||||
return Bun.markdown.react(large);
|
||||
});
|
||||
}
|
||||
|
||||
bench(`large (${large.length} chars) - marked`, () => {
|
||||
|
||||
@@ -7,13 +7,6 @@ register_repository(
|
||||
4f4f5ef8ebc6e23cbf393428f0ab1b526773f7ac
|
||||
)
|
||||
|
||||
set(BORINGSSL_CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF)
|
||||
|
||||
# Disable ASM on Windows ARM64 to avoid mixing non-ARM object files into ARM64 libs
|
||||
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64")
|
||||
list(APPEND BORINGSSL_CMAKE_ARGS -DOPENSSL_NO_ASM=1)
|
||||
endif()
|
||||
|
||||
register_cmake_command(
|
||||
TARGET
|
||||
boringssl
|
||||
@@ -22,7 +15,7 @@ register_cmake_command(
|
||||
ssl
|
||||
decrepit
|
||||
ARGS
|
||||
${BORINGSSL_CMAKE_ARGS}
|
||||
-DBUILD_SHARED_LIBS=OFF
|
||||
INCLUDES
|
||||
include
|
||||
)
|
||||
|
||||
@@ -1016,7 +1016,6 @@ if(NOT WIN32)
|
||||
-Wno-unused-function
|
||||
-Wno-c++23-lambda-attributes
|
||||
-Wno-nullability-completeness
|
||||
-Wno-character-conversion
|
||||
-Werror
|
||||
)
|
||||
else()
|
||||
@@ -1034,7 +1033,6 @@ if(NOT WIN32)
|
||||
-Werror=sometimes-uninitialized
|
||||
-Wno-c++23-lambda-attributes
|
||||
-Wno-nullability-completeness
|
||||
-Wno-character-conversion
|
||||
-Werror
|
||||
)
|
||||
|
||||
@@ -1063,7 +1061,6 @@ else()
|
||||
-Wno-inconsistent-dllimport
|
||||
-Wno-incompatible-pointer-types
|
||||
-Wno-deprecated-declarations
|
||||
-Wno-character-conversion
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -1139,15 +1136,6 @@ if(LINUX)
|
||||
-Wl,--wrap=pow
|
||||
-Wl,--wrap=powf
|
||||
)
|
||||
|
||||
# Disable LTO for workaround-missing-symbols.cpp to prevent LLD 21 from emitting
|
||||
# glibc versioned symbol names (e.g. exp@GLIBC_2.17) from .symver directives into
|
||||
# the .lto_discard assembler directive, which fails to parse the '@' character.
|
||||
if(ENABLE_LTO)
|
||||
set_source_files_properties(${CWD}/src/bun.js/bindings/workaround-missing-symbols.cpp
|
||||
PROPERTIES COMPILE_OPTIONS "-fno-lto"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT ABI STREQUAL "musl")
|
||||
@@ -1457,8 +1445,6 @@ if(NOT BUN_CPP_ONLY)
|
||||
# ==856230==See https://github.com/google/sanitizers/issues/856 for possible workarounds.
|
||||
# the linked issue refers to very old kernels but this still happens to us on modern ones.
|
||||
# disabling ASLR to run the binary works around it
|
||||
# Skip post-build test/features when cross-compiling (can't run the target binary on the host)
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
set(TEST_BUN_COMMAND_BASE ${BUILD_PATH}/${bunExe} --revision)
|
||||
set(TEST_BUN_COMMAND_ENV_WRAP
|
||||
${CMAKE_COMMAND} -E env BUN_DEBUG_QUIET_LOGS=1)
|
||||
@@ -1507,7 +1493,6 @@ if(NOT BUN_CPP_ONLY)
|
||||
${BUILD_PATH}/features.json
|
||||
)
|
||||
endif()
|
||||
endif() # NOT CMAKE_CROSSCOMPILING
|
||||
|
||||
if(CMAKE_HOST_APPLE AND bunStrip)
|
||||
register_command(
|
||||
@@ -1554,10 +1539,7 @@ if(NOT BUN_CPP_ONLY)
|
||||
string(REPLACE bun ${bunTriplet} bunPath ${bun})
|
||||
endif()
|
||||
|
||||
set(bunFiles ${bunExe})
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
list(APPEND bunFiles features.json)
|
||||
endif()
|
||||
set(bunFiles ${bunExe} features.json)
|
||||
if(WIN32)
|
||||
list(APPEND bunFiles ${bun}.pdb)
|
||||
elseif(APPLE)
|
||||
|
||||
@@ -26,12 +26,6 @@ if(RELEASE)
|
||||
list(APPEND LOLHTML_BUILD_ARGS --release)
|
||||
endif()
|
||||
|
||||
# Cross-compilation: tell cargo to target ARM64
|
||||
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64")
|
||||
list(APPEND LOLHTML_BUILD_ARGS --target aarch64-pc-windows-msvc)
|
||||
set(LOLHTML_LIBRARY ${LOLHTML_BUILD_PATH}/aarch64-pc-windows-msvc/${LOLHTML_BUILD_TYPE}/${CMAKE_STATIC_LIBRARY_PREFIX}lolhtml${CMAKE_STATIC_LIBRARY_SUFFIX})
|
||||
endif()
|
||||
|
||||
# Windows requires unwind tables, apparently.
|
||||
if (NOT WIN32)
|
||||
# The encoded escape sequences are intentional. They're how you delimit multiple arguments in a single environment variable.
|
||||
@@ -57,18 +51,11 @@ if(WIN32)
|
||||
if(MSVC_VERSIONS)
|
||||
list(GET MSVC_VERSIONS -1 MSVC_LATEST) # Get the latest version
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64")
|
||||
# Use Hostx64/arm64 for cross-compilation from x64, fall back to native
|
||||
if(EXISTS "${MSVC_LATEST}/bin/Hostx64/arm64/link.exe")
|
||||
set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/Hostx64/arm64/link.exe")
|
||||
else()
|
||||
set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/HostARM64/arm64/link.exe")
|
||||
endif()
|
||||
set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/HostARM64/arm64/link.exe")
|
||||
set(CARGO_LINKER_VAR "CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER")
|
||||
set(MSVC_LIB_ARCH "arm64")
|
||||
else()
|
||||
set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/Hostx64/x64/link.exe")
|
||||
set(CARGO_LINKER_VAR "CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER")
|
||||
set(MSVC_LIB_ARCH "x64")
|
||||
endif()
|
||||
if(EXISTS "${MSVC_LINK_PATH}")
|
||||
list(APPEND LOLHTML_ENV "${CARGO_LINKER_VAR}=${MSVC_LINK_PATH}")
|
||||
|
||||
@@ -3,35 +3,18 @@ set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
|
||||
set(CMAKE_C_COMPILER_WORKS ON)
|
||||
set(CMAKE_CXX_COMPILER_WORKS ON)
|
||||
set(CMAKE_CROSSCOMPILING ON)
|
||||
|
||||
# The rest only applies when building on Windows (C++ and link steps).
|
||||
# The Zig step runs on Linux and only needs CMAKE_SYSTEM_NAME/PROCESSOR above.
|
||||
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
|
||||
# Force ARM64 architecture ID - this is what CMake uses to determine /machine: flag
|
||||
set(MSVC_C_ARCHITECTURE_ID ARM64 CACHE INTERNAL "")
|
||||
set(MSVC_CXX_ARCHITECTURE_ID ARM64 CACHE INTERNAL "")
|
||||
|
||||
# Ensure clang/clang-cl targets Windows ARM64 (otherwise ARM64-specific flags like
|
||||
# -march=armv8-a are rejected as x86-only).
|
||||
set(CMAKE_C_COMPILER_TARGET aarch64-pc-windows-msvc CACHE STRING "" FORCE)
|
||||
set(CMAKE_CXX_COMPILER_TARGET aarch64-pc-windows-msvc CACHE STRING "" FORCE)
|
||||
# CMake 4.0+ policy CMP0197 controls how MSVC machine type flags are handled
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0197 NEW CACHE INTERNAL "")
|
||||
|
||||
# ARM64 has lock-free atomics (highway's FindAtomics check can't run ARM64 test binary on x64)
|
||||
set(ATOMICS_LOCK_FREE_INSTRUCTIONS TRUE CACHE BOOL "" FORCE)
|
||||
set(HAVE_CXX_ATOMICS_WITHOUT_LIB TRUE CACHE BOOL "" FORCE)
|
||||
set(HAVE_CXX_ATOMICS64_WITHOUT_LIB TRUE CACHE BOOL "" FORCE)
|
||||
# Clear any inherited static linker flags that might have wrong machine types
|
||||
set(CMAKE_STATIC_LINKER_FLAGS "" CACHE STRING "" FORCE)
|
||||
|
||||
# Force ARM64 architecture ID - this is what CMake uses to determine /machine: flag
|
||||
set(MSVC_C_ARCHITECTURE_ID ARM64 CACHE INTERNAL "")
|
||||
set(MSVC_CXX_ARCHITECTURE_ID ARM64 CACHE INTERNAL "")
|
||||
|
||||
# CMake 4.0+ policy CMP0197 controls how MSVC machine type flags are handled
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0197 NEW CACHE INTERNAL "")
|
||||
|
||||
# Clear any inherited static linker flags that might have wrong machine types
|
||||
set(CMAKE_STATIC_LINKER_FLAGS "" CACHE STRING "" FORCE)
|
||||
|
||||
# Use wrapper script for llvm-lib that strips /machine:x64 flags
|
||||
# This works around CMake 4.1.0 bug where both ARM64 and x64 machine flags are added
|
||||
get_filename_component(_TOOLCHAIN_DIR "${CMAKE_CURRENT_LIST_DIR}" DIRECTORY)
|
||||
set(CMAKE_AR "${_TOOLCHAIN_DIR}/scripts/llvm-lib-wrapper.bat" CACHE FILEPATH "" FORCE)
|
||||
|
||||
endif()
|
||||
# Use wrapper script for llvm-lib that strips /machine:x64 flags
|
||||
# This works around CMake 4.1.0 bug where both ARM64 and x64 machine flags are added
|
||||
get_filename_component(_TOOLCHAIN_DIR "${CMAKE_CURRENT_LIST_DIR}" DIRECTORY)
|
||||
set(CMAKE_AR "${_TOOLCHAIN_DIR}/scripts/llvm-lib-wrapper.bat" CACHE FILEPATH "" FORCE)
|
||||
@@ -12,7 +12,13 @@ if(NOT ENABLE_LLVM)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(DEFAULT_LLVM_VERSION "21.1.8")
|
||||
# LLVM 21 is required for Windows ARM64 (first version with ARM64 Windows builds)
|
||||
# Other platforms use LLVM 19.1.7
|
||||
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64")
|
||||
set(DEFAULT_LLVM_VERSION "21.1.8")
|
||||
else()
|
||||
set(DEFAULT_LLVM_VERSION "19.1.7")
|
||||
endif()
|
||||
|
||||
optionx(LLVM_VERSION STRING "The version of LLVM to use" DEFAULT ${DEFAULT_LLVM_VERSION})
|
||||
|
||||
@@ -21,8 +27,6 @@ if(USE_LLVM_VERSION)
|
||||
set(LLVM_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||
set(LLVM_VERSION_MINOR ${CMAKE_MATCH_2})
|
||||
set(LLVM_VERSION_PATCH ${CMAKE_MATCH_3})
|
||||
# Accept any LLVM version within the same major.minor range (e.g. Alpine 3.23 ships 21.1.2)
|
||||
set(LLVM_VERSION_RANGE ">=${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.0 <${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.99")
|
||||
endif()
|
||||
|
||||
set(LLVM_PATHS)
|
||||
@@ -50,11 +54,6 @@ if(APPLE)
|
||||
list(APPEND LLVM_PATHS ${HOMEBREW_PREFIX}/opt/llvm/bin)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# Prefer standalone LLVM over VS-bundled (standalone supports cross-compilation)
|
||||
list(APPEND LLVM_PATHS "C:/Program Files/LLVM/bin")
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
list(APPEND LLVM_PATHS /usr/lib/llvm/bin)
|
||||
|
||||
@@ -79,12 +78,14 @@ macro(find_llvm_command variable command)
|
||||
)
|
||||
endif()
|
||||
|
||||
math(EXPR LLVM_VERSION_NEXT_MAJOR "${LLVM_VERSION_MAJOR} + 1")
|
||||
|
||||
find_command(
|
||||
VARIABLE ${variable}
|
||||
VERSION_VARIABLE LLVM_VERSION
|
||||
COMMAND ${commands}
|
||||
PATHS ${LLVM_PATHS}
|
||||
VERSION "${LLVM_VERSION_RANGE}"
|
||||
VERSION ">=${LLVM_VERSION_MAJOR}.1.0 <${LLVM_VERSION_NEXT_MAJOR}.0.0"
|
||||
)
|
||||
list(APPEND CMAKE_ARGS -D${variable}=${${variable}})
|
||||
endmacro()
|
||||
|
||||
@@ -6,7 +6,7 @@ option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of down
|
||||
option(WEBKIT_BUILD_TYPE "The build type for local WebKit (defaults to CMAKE_BUILD_TYPE)")
|
||||
|
||||
if(NOT WEBKIT_VERSION)
|
||||
set(WEBKIT_VERSION 8af7958ff0e2a4787569edf64641a1ae7cfe074a)
|
||||
set(WEBKIT_VERSION 515344bc5d65aa2d4f9ff277b5fb944f0e051dcd)
|
||||
endif()
|
||||
|
||||
# Use preview build URL for Windows ARM64 until the fix is merged to main
|
||||
|
||||
@@ -35,7 +35,7 @@ winget install "Visual Studio Community 2022" --override "--add Microsoft.Visual
|
||||
|
||||
After Visual Studio, you need the following:
|
||||
|
||||
- LLVM 21.1.8
|
||||
- LLVM (19.1.7 for x64, 21.1.8 for ARM64)
|
||||
- Go
|
||||
- Rust
|
||||
- NASM
|
||||
@@ -51,7 +51,7 @@ After Visual Studio, you need the following:
|
||||
irm https://get.scoop.sh | iex
|
||||
scoop install nodejs-lts go rust nasm ruby perl ccache
|
||||
# scoop seems to be buggy if you install llvm and the rest at the same time
|
||||
scoop install llvm@21.1.8
|
||||
scoop install llvm@19.1.7
|
||||
```
|
||||
|
||||
For Windows ARM64, download LLVM 21.1.8 directly from GitHub releases (first version with ARM64 Windows builds):
|
||||
|
||||
@@ -40,7 +40,7 @@ sudo pacman -S base-devel cmake git go libiconv libtool make ninja pkg-config py
|
||||
```
|
||||
|
||||
```bash Fedora
|
||||
sudo dnf install cargo clang21 llvm21 lld21 cmake git golang libtool ninja-build pkg-config rustc ruby libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)'
|
||||
sudo dnf install cargo clang19 llvm19 lld19 cmake git golang libtool ninja-build pkg-config rustc ruby libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)'
|
||||
```
|
||||
|
||||
```bash openSUSE Tumbleweed
|
||||
@@ -95,17 +95,17 @@ Our build scripts will automatically detect and use `ccache` if available. You c
|
||||
|
||||
## Install LLVM
|
||||
|
||||
Bun requires LLVM 21.1.8 (`clang` is part of LLVM). This version is enforced by the build system — mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
|
||||
Bun requires LLVM 19 (`clang` is part of LLVM). This version requirement is to match WebKit (precompiled), as mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash macOS (Homebrew)
|
||||
brew install llvm@21
|
||||
brew install llvm@19
|
||||
```
|
||||
|
||||
```bash Ubuntu/Debian
|
||||
# LLVM has an automatic installation script that is compatible with all versions of Ubuntu
|
||||
wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 21 all
|
||||
wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 19 all
|
||||
```
|
||||
|
||||
```bash Arch
|
||||
@@ -117,17 +117,17 @@ sudo dnf install llvm clang lld-devel
|
||||
```
|
||||
|
||||
```bash openSUSE Tumbleweed
|
||||
sudo zypper install clang21 lld21 llvm21
|
||||
sudo zypper install clang19 lld19 llvm19
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
If none of the above solutions apply, you will have to install it [manually](https://github.com/llvm/llvm-project/releases/tag/llvmorg-21.1.8).
|
||||
If none of the above solutions apply, you will have to install it [manually](https://github.com/llvm/llvm-project/releases/tag/llvmorg-19.1.7).
|
||||
|
||||
Make sure Clang/LLVM 21 is in your path:
|
||||
Make sure Clang/LLVM 19 is in your path:
|
||||
|
||||
```bash
|
||||
which clang-21
|
||||
which clang-19
|
||||
```
|
||||
|
||||
If not, run this to manually add it:
|
||||
@@ -136,13 +136,13 @@ If not, run this to manually add it:
|
||||
|
||||
```bash macOS (Homebrew)
|
||||
# use fish_add_path if you're using fish
|
||||
# use path+="$(brew --prefix llvm@21)/bin" if you are using zsh
|
||||
export PATH="$(brew --prefix llvm@21)/bin:$PATH"
|
||||
# use path+="$(brew --prefix llvm@19)/bin" if you are using zsh
|
||||
export PATH="$(brew --prefix llvm@19)/bin:$PATH"
|
||||
```
|
||||
|
||||
```bash Arch
|
||||
# use fish_add_path if you're using fish
|
||||
export PATH="$PATH:/usr/lib/llvm21/bin"
|
||||
export PATH="$PATH:/usr/lib/llvm19/bin"
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -309,7 +309,7 @@ The issue may manifest when initially running `bun setup` as Clang being unable
|
||||
```txt
|
||||
The C++ compiler
|
||||
|
||||
"/usr/bin/clang++-21"
|
||||
"/usr/bin/clang++-19"
|
||||
|
||||
is not able to compile a simple test program.
|
||||
```
|
||||
|
||||
@@ -3,9 +3,9 @@ title: Markdown
|
||||
description: Parse and render Markdown with Bun's built-in Markdown API, supporting GFM extensions and custom rendering callbacks
|
||||
---
|
||||
|
||||
<Callout type="note">
|
||||
**Unstable API** — This API is under active development and may change in future versions of Bun.
|
||||
</Callout>
|
||||
{% callout type="note" %}
|
||||
**Unstable API** — This API is under active development and may change in future versions of Bun.
|
||||
{% /callout %}
|
||||
|
||||
Bun includes a fast, built-in Markdown parser written in Zig. It supports GitHub Flavored Markdown (GFM) extensions and provides three APIs:
|
||||
|
||||
|
||||
10
flake.nix
10
flake.nix
@@ -26,10 +26,10 @@
|
||||
};
|
||||
};
|
||||
|
||||
# LLVM 21 - matching the bootstrap script (targets 21.1.8, actual version from nixpkgs-unstable)
|
||||
llvm = pkgs.llvm_21;
|
||||
clang = pkgs.clang_21;
|
||||
lld = pkgs.lld_21;
|
||||
# LLVM 19 - matching the bootstrap script (targets 19.1.7, actual version from nixpkgs-unstable)
|
||||
llvm = pkgs.llvm_19;
|
||||
clang = pkgs.clang_19;
|
||||
lld = pkgs.lld_19;
|
||||
|
||||
# Node.js 24 - matching the bootstrap script (targets 24.3.0, actual version from nixpkgs-unstable)
|
||||
nodejs = pkgs.nodejs_24;
|
||||
@@ -42,7 +42,7 @@
|
||||
pkgs.pkg-config
|
||||
pkgs.ccache
|
||||
|
||||
# Compilers and toolchain - version pinned to LLVM 21
|
||||
# Compilers and toolchain - version pinned to LLVM 19
|
||||
clang
|
||||
llvm
|
||||
lld
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# Bun
|
||||
|
||||
This is the Windows ARM64 binary for Bun, a fast all-in-one JavaScript runtime. https://bun.com
|
||||
@@ -95,12 +95,6 @@ export const platforms: Platform[] = [
|
||||
bin: "bun-windows-x64-baseline",
|
||||
exe: "bin/bun.exe",
|
||||
},
|
||||
{
|
||||
os: "win32",
|
||||
arch: "arm64",
|
||||
bin: "bun-windows-aarch64",
|
||||
exe: "bin/bun.exe",
|
||||
},
|
||||
];
|
||||
|
||||
export const supportedPlatforms: Platform[] = platforms
|
||||
|
||||
4
packages/bun-types/bun.d.ts
vendored
4
packages/bun-types/bun.d.ts
vendored
@@ -2154,7 +2154,7 @@ declare module "bun" {
|
||||
interface Hash {
|
||||
wyhash: (data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: bigint) => bigint;
|
||||
adler32: (data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer) => number;
|
||||
crc32: (data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: number) => number;
|
||||
crc32: (data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer) => number;
|
||||
cityHash32: (data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer) => number;
|
||||
cityHash64: (data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: bigint) => bigint;
|
||||
xxHash32: (data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: number) => number;
|
||||
@@ -2438,7 +2438,7 @@ declare module "bun" {
|
||||
| `bun-linux-${Architecture}-${Libc}`
|
||||
| `bun-linux-${Architecture}-${SIMD}`
|
||||
| `bun-linux-${Architecture}-${SIMD}-${Libc}`
|
||||
| `bun-windows-${Architecture}`
|
||||
| "bun-windows-x64"
|
||||
| `bun-windows-x64-${SIMD}`;
|
||||
}
|
||||
|
||||
|
||||
1
packages/bun-types/test.d.ts
vendored
1
packages/bun-types/test.d.ts
vendored
@@ -2179,7 +2179,6 @@ declare module "bun:test" {
|
||||
mockResolvedValueOnce(value: ResolveType<T>): this;
|
||||
mockRejectedValue(value: RejectType<T>): this;
|
||||
mockRejectedValueOnce(value: RejectType<T>): this;
|
||||
[Symbol.dispose](): void;
|
||||
}
|
||||
|
||||
// export type MockMetadata<T, MetadataType = MockMetadataType> = {
|
||||
|
||||
@@ -566,10 +566,8 @@ namespace uWS
|
||||
|
||||
|
||||
bool isHTTPMethod = (__builtin_expect(data[1] == '/', 1));
|
||||
bool isConnect = !isHTTPMethod && ((data - start) == 7 && memcmp(start, "CONNECT", 7) == 0);
|
||||
/* Also accept proxy-style absolute URLs (http://... or https://...) as valid request targets */
|
||||
bool isProxyStyleURL = !isHTTPMethod && !isConnect && data[0] == 32 && isHTTPorHTTPSPrefixForProxies(data + 1, end) == 1;
|
||||
if (isHTTPMethod || isConnect || isProxyStyleURL) [[likely]] {
|
||||
bool isConnect = !isHTTPMethod && (isHTTPorHTTPSPrefixForProxies(data + 1, end) == 1 || ((data - start) == 7 && memcmp(start, "CONNECT", 7) == 0));
|
||||
if (isHTTPMethod || isConnect) [[likely]] {
|
||||
header.key = {start, (size_t) (data - start)};
|
||||
data++;
|
||||
if(!isValidMethod(header.key, useStrictMethodValidation)) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Version: 12
|
||||
# Version: 11
|
||||
# A script that installs the dependencies needed to build and test Bun.
|
||||
# This should work on Windows 10 or newer with PowerShell.
|
||||
|
||||
@@ -387,7 +387,7 @@ function Install-PdbAddr2line {
|
||||
function Install-Llvm {
|
||||
Install-Package llvm `
|
||||
-Command clang-cl `
|
||||
-Version "21.1.8"
|
||||
-Version "19.1.7"
|
||||
Add-To-Path "$env:ProgramFiles\LLVM\bin"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
# Version: 28
|
||||
# Version: 27
|
||||
|
||||
# A script that installs the dependencies needed to build and test Bun.
|
||||
# This should work on macOS and Linux with a POSIX shell.
|
||||
@@ -1096,7 +1096,7 @@ install_build_essentials() {
|
||||
}
|
||||
|
||||
llvm_version_exact() {
|
||||
print "21.1.8"
|
||||
print "19.1.7"
|
||||
}
|
||||
|
||||
llvm_version() {
|
||||
@@ -1106,20 +1106,23 @@ llvm_version() {
|
||||
install_llvm() {
|
||||
case "$pm" in
|
||||
apt)
|
||||
# apt.llvm.org's GPG key uses SHA1, which Debian 13+ (sqv) rejects since 2026-02-01.
|
||||
# Override the sequoia crypto policy to extend the SHA1 deadline.
|
||||
# See: https://github.com/llvm/llvm-project/issues/153385
|
||||
if [ -x /usr/bin/sqv ] && [ -f /usr/share/apt/default-sequoia.config ]; then
|
||||
execute_sudo mkdir -p /etc/crypto-policies/back-ends
|
||||
execute_sudo /usr/bin/sh -c "sed 's/sha1.second_preimage_resistance = 2026-02-01/sha1.second_preimage_resistance = 2028-02-01/' /usr/share/apt/default-sequoia.config > /etc/crypto-policies/back-ends/apt-sequoia.config"
|
||||
# Debian 13 (Trixie) has LLVM 19 natively, and apt.llvm.org doesn't have a trixie repo
|
||||
if [ "$distro" = "debian" ]; then
|
||||
install_packages \
|
||||
"llvm-$(llvm_version)" \
|
||||
"clang-$(llvm_version)" \
|
||||
"lld-$(llvm_version)" \
|
||||
"llvm-$(llvm_version)-dev" \
|
||||
"llvm-$(llvm_version)-tools" \
|
||||
"libclang-rt-$(llvm_version)-dev"
|
||||
else
|
||||
bash="$(require bash)"
|
||||
llvm_script="$(download_file "https://apt.llvm.org/llvm.sh")"
|
||||
execute_sudo "$bash" "$llvm_script" "$(llvm_version)" all
|
||||
|
||||
# Install llvm-symbolizer explicitly to ensure it's available for ASAN
|
||||
install_packages "llvm-$(llvm_version)-tools"
|
||||
fi
|
||||
|
||||
bash="$(require bash)"
|
||||
llvm_script="$(download_file "https://apt.llvm.org/llvm.sh")"
|
||||
execute_sudo "$bash" "$llvm_script" "$(llvm_version)" all
|
||||
|
||||
# Install llvm-symbolizer explicitly to ensure it's available for ASAN
|
||||
install_packages "llvm-$(llvm_version)-tools"
|
||||
;;
|
||||
brew)
|
||||
install_packages "llvm@$(llvm_version)"
|
||||
@@ -1174,7 +1177,7 @@ install_gcc() {
|
||||
;;
|
||||
esac
|
||||
|
||||
llvm_v="21"
|
||||
llvm_v="19"
|
||||
|
||||
append_to_profile "export CC=clang-${llvm_v}"
|
||||
append_to_profile "export CXX=clang++-${llvm_v}"
|
||||
|
||||
@@ -77,10 +77,10 @@ const HAS_CCACHE = CCACHE !== null;
|
||||
// On Windows, use clang-cl for MSVC compatibility
|
||||
const CC_BASE = IS_WINDOWS
|
||||
? findExecutable(["clang-cl.exe", "clang-cl"]) || "clang-cl"
|
||||
: findExecutable(["clang-21", "clang"]) || "clang";
|
||||
: findExecutable(["clang-19", "clang"]) || "clang";
|
||||
const CXX_BASE = IS_WINDOWS
|
||||
? findExecutable(["clang-cl.exe", "clang-cl"]) || "clang-cl"
|
||||
: findExecutable(["clang++-21", "clang++"]) || "clang++";
|
||||
: findExecutable(["clang++-19", "clang++"]) || "clang++";
|
||||
|
||||
const CC = HAS_CCACHE ? CCACHE : CC_BASE;
|
||||
const CXX = HAS_CCACHE ? CCACHE : CXX_BASE;
|
||||
|
||||
@@ -57,11 +57,7 @@ async function build(args) {
|
||||
if (process.platform === "win32" && !process.env["VSINSTALLDIR"]) {
|
||||
const shellPath = join(import.meta.dirname, "vs-shell.ps1");
|
||||
const scriptPath = import.meta.filename;
|
||||
// When cross-compiling to ARM64, tell vs-shell.ps1 to set up the x64_arm64 VS environment
|
||||
const toolchainIdx = args.indexOf("--toolchain");
|
||||
const requestedVsArch = toolchainIdx !== -1 && args[toolchainIdx + 1] === "windows-aarch64" ? "arm64" : undefined;
|
||||
const env = requestedVsArch ? { ...process.env, BUN_VS_ARCH: requestedVsArch } : undefined;
|
||||
return spawn("pwsh", ["-NoProfile", "-NoLogo", "-File", shellPath, process.argv0, scriptPath, ...args], { env });
|
||||
return spawn("pwsh", ["-NoProfile", "-NoLogo", "-File", shellPath, process.argv0, scriptPath, ...args]);
|
||||
}
|
||||
|
||||
if (isCI) {
|
||||
@@ -96,9 +92,21 @@ async function build(args) {
|
||||
generateOptions["--toolchain"] = toolchainPath;
|
||||
}
|
||||
|
||||
// Windows ARM64: log detection (compiler is selected by CMake/toolchain)
|
||||
// Windows ARM64: automatically set required options
|
||||
if (isWindowsARM64) {
|
||||
console.log("Windows ARM64 detected");
|
||||
// Use clang-cl instead of MSVC cl.exe for proper ARM64 flag support
|
||||
if (!generateOptions["-DCMAKE_C_COMPILER"]) {
|
||||
generateOptions["-DCMAKE_C_COMPILER"] = "clang-cl";
|
||||
}
|
||||
if (!generateOptions["-DCMAKE_CXX_COMPILER"]) {
|
||||
generateOptions["-DCMAKE_CXX_COMPILER"] = "clang-cl";
|
||||
}
|
||||
// Skip codegen by default since x64 bun crashes under WoW64 emulation
|
||||
// Can be overridden with -DSKIP_CODEGEN=OFF once ARM64 bun is available
|
||||
if (!generateOptions["-DSKIP_CODEGEN"]) {
|
||||
generateOptions["-DSKIP_CODEGEN"] = "ON";
|
||||
}
|
||||
console.log("Windows ARM64 detected: using clang-cl and SKIP_CODEGEN=ON");
|
||||
}
|
||||
|
||||
const generateArgs = Object.entries(generateOptions).flatMap(([flag, value]) =>
|
||||
|
||||
@@ -12,7 +12,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MODE="${1:-format}"
|
||||
|
||||
# Use LLVM_VERSION_MAJOR from environment or default to 19
|
||||
LLVM_VERSION="${LLVM_VERSION_MAJOR:-21}"
|
||||
LLVM_VERSION="${LLVM_VERSION_MAJOR:-19}"
|
||||
|
||||
# Ensure we have the specific clang-format version
|
||||
CLANG_FORMAT="clang-format-${LLVM_VERSION}"
|
||||
|
||||
@@ -5,22 +5,7 @@ $ErrorActionPreference = "Stop"
|
||||
|
||||
# Detect system architecture
|
||||
$script:IsARM64 = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64
|
||||
|
||||
# Allow overriding the target arch (useful for cross-compiling on x64 -> ARM64)
|
||||
$script:VsArch = $null
|
||||
if ($env:BUN_VS_ARCH) {
|
||||
switch ($env:BUN_VS_ARCH.ToLowerInvariant()) {
|
||||
"arm64" { $script:VsArch = "arm64" }
|
||||
"aarch64" { $script:VsArch = "arm64" }
|
||||
"amd64" { $script:VsArch = "amd64" }
|
||||
"x64" { $script:VsArch = "amd64" }
|
||||
default { throw "Invalid BUN_VS_ARCH: $env:BUN_VS_ARCH (expected arm64|amd64)" }
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $script:VsArch) {
|
||||
$script:VsArch = if ($script:IsARM64) { "arm64" } else { "amd64" }
|
||||
}
|
||||
$script:VsArch = if ($script:IsARM64) { "arm64" } else { "amd64" }
|
||||
|
||||
if($env:VSINSTALLDIR -eq $null) {
|
||||
Write-Host "Loading Visual Studio environment, this may take a second..."
|
||||
@@ -32,29 +17,17 @@ if($env:VSINSTALLDIR -eq $null) {
|
||||
|
||||
$vsDir = (& $vswhere -prerelease -latest -property installationPath)
|
||||
if ($vsDir -eq $null) {
|
||||
# Check common VS installation paths
|
||||
$searchPaths = @(
|
||||
"C:\Program Files\Microsoft Visual Studio\2022",
|
||||
"C:\Program Files (x86)\Microsoft Visual Studio\2022"
|
||||
)
|
||||
foreach ($searchPath in $searchPaths) {
|
||||
if (Test-Path $searchPath) {
|
||||
$vsDir = (Get-ChildItem -Path $searchPath -Directory | Select-Object -First 1).FullName
|
||||
if ($vsDir -ne $null) { break }
|
||||
}
|
||||
}
|
||||
$vsDir = Get-ChildItem -Path "C:\Program Files\Microsoft Visual Studio\2022" -Directory
|
||||
if ($vsDir -eq $null) {
|
||||
throw "Visual Studio directory not found."
|
||||
}
|
||||
$vsDir = $vsDir.FullName
|
||||
}
|
||||
|
||||
Push-Location $vsDir
|
||||
try {
|
||||
$vsShell = (Join-Path -Path $vsDir -ChildPath "Common7\Tools\Launch-VsDevShell.ps1")
|
||||
# Visual Studio's Launch-VsDevShell.ps1 only supports x86/amd64 for HostArch
|
||||
# For ARM64 builds, use amd64 as HostArch since it can cross-compile to ARM64
|
||||
$hostArch = if ($script:VsArch -eq "arm64") { "amd64" } else { $script:VsArch }
|
||||
. $vsShell -Arch $script:VsArch -HostArch $hostArch
|
||||
. $vsShell -Arch $script:VsArch -HostArch $script:VsArch
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -88,7 +61,7 @@ if ($args.Count -gt 0) {
|
||||
$displayArgs += $arg
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Write-Host "$ $command $displayArgs"
|
||||
& $command $commandArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
16
shell.nix
16
shell.nix
@@ -8,9 +8,9 @@ pkgs.mkShell rec {
|
||||
# Core build tools (matching bootstrap.sh)
|
||||
cmake
|
||||
ninja
|
||||
clang_21
|
||||
llvm_21
|
||||
lld_21
|
||||
clang_19
|
||||
llvm_19
|
||||
lld_19
|
||||
nodejs_24
|
||||
bun
|
||||
rustc
|
||||
@@ -77,10 +77,10 @@ pkgs.mkShell rec {
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
export CC="${pkgs.lib.getExe pkgs.clang_21}"
|
||||
export CXX="${pkgs.lib.getExe' pkgs.clang_21 "clang++"}"
|
||||
export AR="${pkgs.llvm_21}/bin/llvm-ar"
|
||||
export RANLIB="${pkgs.llvm_21}/bin/llvm-ranlib"
|
||||
export CC="${pkgs.lib.getExe pkgs.clang_19}"
|
||||
export CXX="${pkgs.lib.getExe' pkgs.clang_19 "clang++"}"
|
||||
export AR="${pkgs.llvm_19}/bin/llvm-ar"
|
||||
export RANLIB="${pkgs.llvm_19}/bin/llvm-ranlib"
|
||||
export CMAKE_C_COMPILER="$CC"
|
||||
export CMAKE_CXX_COMPILER="$CXX"
|
||||
export CMAKE_AR="$AR"
|
||||
@@ -88,7 +88,7 @@ pkgs.mkShell rec {
|
||||
export CMAKE_SYSTEM_PROCESSOR=$(uname -m)
|
||||
export TMPDIR=''${TMPDIR:-/tmp}
|
||||
'' + pkgs.lib.optionalString pkgs.stdenv.isLinux ''
|
||||
export LD="${pkgs.lib.getExe' pkgs.lld_21 "ld.lld"}"
|
||||
export LD="${pkgs.lib.getExe' pkgs.lld_19 "ld.lld"}"
|
||||
export NIX_CFLAGS_LINK="''${NIX_CFLAGS_LINK:+$NIX_CFLAGS_LINK }-fuse-ld=lld"
|
||||
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath packages}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
'' + ''
|
||||
|
||||
@@ -1409,6 +1409,15 @@ pub const StandaloneModuleGraph = struct {
|
||||
|
||||
switch (Environment.os) {
|
||||
.linux => {
|
||||
// Check for override first (for Termux/explicit ld.so scenarios)
|
||||
// When invoked via explicit dynamic linker (e.g., /lib64/ld-linux-x86-64.so.2 ./app),
|
||||
// /proc/self/exe points to the linker, not the actual binary.
|
||||
if (bun.env_var.BUN_SELF_EXE.get()) |path| {
|
||||
if (std.fs.cwd().openFile(path, .{})) |file| {
|
||||
return .fromStdFile(file);
|
||||
} else |_| {}
|
||||
}
|
||||
|
||||
if (std.fs.openFileAbsoluteZ("/proc/self/exe", .{})) |easymode| {
|
||||
return .fromStdFile(easymode);
|
||||
} else |_| {
|
||||
|
||||
@@ -7,7 +7,6 @@ const VirtualMachine = @This();
|
||||
|
||||
export var has_bun_garbage_collector_flag_enabled = false;
|
||||
pub export var isBunTest: bool = false;
|
||||
pub export var Bun__defaultRemainingRunsUntilSkipReleaseAccess: c_int = 10;
|
||||
|
||||
// TODO: evaluate if this has any measurable performance impact.
|
||||
pub var synthetic_allocation_limit: usize = std.math.maxInt(u32);
|
||||
|
||||
@@ -464,8 +464,8 @@ const ParseRenderer = struct {
|
||||
const entry = self.#stack.pop().?;
|
||||
const g = self.#globalObject;
|
||||
|
||||
// Determine HTML tag index for cached string
|
||||
const tag_index = getBlockTypeTag(block_type, entry.data);
|
||||
// Determine HTML tag name
|
||||
const type_str: []const u8 = blockTypeName(block_type, entry.data);
|
||||
|
||||
// For headings, compute slug before counting props
|
||||
const slug: ?[]const u8 = if (block_type == .h) self.#heading_tracker.leaveHeading(bun.default_allocator) else null;
|
||||
@@ -496,7 +496,7 @@ const ParseRenderer = struct {
|
||||
|
||||
// Build React element — use component override as type if set
|
||||
const component = self.getBlockComponent(block_type, entry.data);
|
||||
const type_val: JSValue = if (component != .zero) component else getCachedTagString(g, tag_index);
|
||||
const type_val: JSValue = if (component != .zero) component else try bun.String.createUTF8ForJS(g, type_str);
|
||||
|
||||
const props = JSValue.createEmptyObject(g, props_count);
|
||||
self.#marked_args.append(props);
|
||||
@@ -572,7 +572,7 @@ const ParseRenderer = struct {
|
||||
const entry = self.#stack.pop().?;
|
||||
const g = self.#globalObject;
|
||||
|
||||
const tag_index = getSpanTypeTag(span_type);
|
||||
const type_str: []const u8 = spanTypeName(span_type);
|
||||
|
||||
// Count props fields: always children (or alt for img) + metadata
|
||||
var props_count: usize = 1; // children (or alt for img)
|
||||
@@ -592,7 +592,7 @@ const ParseRenderer = struct {
|
||||
|
||||
// Build React element: { $$typeof, type, key, ref, props }
|
||||
const component = self.getSpanComponent(span_type);
|
||||
const type_val: JSValue = if (component != .zero) component else getCachedTagString(g, tag_index);
|
||||
const type_val: JSValue = if (component != .zero) component else try bun.String.createUTF8ForJS(g, type_str);
|
||||
|
||||
const props = JSValue.createEmptyObject(g, props_count);
|
||||
self.#marked_args.append(props);
|
||||
@@ -675,7 +675,7 @@ const ParseRenderer = struct {
|
||||
switch (text_type) {
|
||||
.br => {
|
||||
const br_component = self.#components.br;
|
||||
const br_type: JSValue = if (br_component != .zero) br_component else getCachedTagString(g, .br);
|
||||
const br_type: JSValue = if (br_component != .zero) br_component else try bun.String.createUTF8ForJS(g, "br");
|
||||
const empty_props = JSValue.createEmptyObject(g, 0);
|
||||
self.#marked_args.append(empty_props);
|
||||
const obj = self.createElement(br_type, empty_props);
|
||||
@@ -705,6 +705,53 @@ const ParseRenderer = struct {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Type name mappings
|
||||
// ========================================
|
||||
|
||||
fn blockTypeName(block_type: md.BlockType, data: u32) []const u8 {
|
||||
return switch (block_type) {
|
||||
.h => switch (data) {
|
||||
1 => "h1",
|
||||
2 => "h2",
|
||||
3 => "h3",
|
||||
4 => "h4",
|
||||
5 => "h5",
|
||||
else => "h6",
|
||||
},
|
||||
.p => "p",
|
||||
.quote => "blockquote",
|
||||
.ul => "ul",
|
||||
.ol => "ol",
|
||||
.li => "li",
|
||||
.code => "pre",
|
||||
.hr => "hr",
|
||||
.html => "html",
|
||||
.table => "table",
|
||||
.thead => "thead",
|
||||
.tbody => "tbody",
|
||||
.tr => "tr",
|
||||
.th => "th",
|
||||
.td => "td",
|
||||
.doc => "div",
|
||||
};
|
||||
}
|
||||
|
||||
fn spanTypeName(span_type: md.SpanType) []const u8 {
|
||||
return switch (span_type) {
|
||||
.em => "em",
|
||||
.strong => "strong",
|
||||
.a => "a",
|
||||
.img => "img",
|
||||
.code => "code",
|
||||
.del => "del",
|
||||
.latexmath => "math",
|
||||
.latexmath_display => "math",
|
||||
.wikilink => "a",
|
||||
.u => "u",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Renderer that calls JavaScript callbacks for each markdown element.
|
||||
@@ -1078,89 +1125,6 @@ fn extractLanguage(src_text: []const u8, info_beg: u32) []const u8 {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Cached tag string indices - must match BunMarkdownTagStrings.h
|
||||
const TagIndex = enum(u8) {
|
||||
h1 = 0,
|
||||
h2 = 1,
|
||||
h3 = 2,
|
||||
h4 = 3,
|
||||
h5 = 4,
|
||||
h6 = 5,
|
||||
p = 6,
|
||||
blockquote = 7,
|
||||
ul = 8,
|
||||
ol = 9,
|
||||
li = 10,
|
||||
pre = 11,
|
||||
hr = 12,
|
||||
html = 13,
|
||||
table = 14,
|
||||
thead = 15,
|
||||
tbody = 16,
|
||||
tr = 17,
|
||||
th = 18,
|
||||
td = 19,
|
||||
div = 20,
|
||||
em = 21,
|
||||
strong = 22,
|
||||
a = 23,
|
||||
img = 24,
|
||||
code = 25,
|
||||
del = 26,
|
||||
math = 27,
|
||||
u = 28,
|
||||
br = 29,
|
||||
};
|
||||
|
||||
extern fn BunMarkdownTagStrings__getTagString(*jsc.JSGlobalObject, u8) JSValue;
|
||||
|
||||
fn getCachedTagString(globalObject: *jsc.JSGlobalObject, tag: TagIndex) JSValue {
|
||||
return BunMarkdownTagStrings__getTagString(globalObject, @intFromEnum(tag));
|
||||
}
|
||||
|
||||
fn getBlockTypeTag(block_type: md.BlockType, data: u32) TagIndex {
|
||||
return switch (block_type) {
|
||||
.h => switch (data) {
|
||||
1 => .h1,
|
||||
2 => .h2,
|
||||
3 => .h3,
|
||||
4 => .h4,
|
||||
5 => .h5,
|
||||
else => .h6,
|
||||
},
|
||||
.p => .p,
|
||||
.quote => .blockquote,
|
||||
.ul => .ul,
|
||||
.ol => .ol,
|
||||
.li => .li,
|
||||
.code => .pre,
|
||||
.hr => .hr,
|
||||
.html => .html,
|
||||
.table => .table,
|
||||
.thead => .thead,
|
||||
.tbody => .tbody,
|
||||
.tr => .tr,
|
||||
.th => .th,
|
||||
.td => .td,
|
||||
.doc => .div,
|
||||
};
|
||||
}
|
||||
|
||||
fn getSpanTypeTag(span_type: md.SpanType) TagIndex {
|
||||
return switch (span_type) {
|
||||
.em => .em,
|
||||
.strong => .strong,
|
||||
.a => .a,
|
||||
.img => .img,
|
||||
.code => .code,
|
||||
.del => .del,
|
||||
.latexmath => .math,
|
||||
.latexmath_display => .math,
|
||||
.wikilink => .a,
|
||||
.u => .u,
|
||||
};
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
@@ -256,7 +256,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
jsc.markBinding(@src());
|
||||
if (this.socket.isDetached()) return;
|
||||
const handlers = this.getHandlers();
|
||||
log("onTimeout {s}", .{if (handlers.mode == .server) "S" else "C"});
|
||||
log("onTimeout {s}", .{if (handlers.is_server) "S" else "C"});
|
||||
const callback = handlers.onTimeout;
|
||||
if (callback == .zero or this.flags.finalizing) return;
|
||||
if (handlers.vm.isShuttingDown()) {
|
||||
@@ -281,7 +281,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
|
||||
pub fn handleConnectError(this: *This, errno: c_int) bun.JSError!void {
|
||||
const handlers = this.getHandlers();
|
||||
log("onConnectError {s} ({d}, {d})", .{ if (handlers.mode == .server) "S" else "C", errno, this.ref_count.get() });
|
||||
log("onConnectError {s} ({d}, {d})", .{ if (handlers.is_server) "S" else "C", errno, this.ref_count.get() });
|
||||
// Ensure the socket is still alive for any defer's we have
|
||||
this.ref();
|
||||
defer this.deref();
|
||||
@@ -397,8 +397,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
}
|
||||
|
||||
pub fn isServer(this: *const This) bool {
|
||||
const handlers = this.getHandlers();
|
||||
return handlers.mode.isServer();
|
||||
return this.getHandlers().is_server;
|
||||
}
|
||||
|
||||
pub fn onOpen(this: *This, socket: Socket) void {
|
||||
@@ -503,7 +502,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
jsc.markBinding(@src());
|
||||
if (this.socket.isDetached()) return;
|
||||
const handlers = this.getHandlers();
|
||||
log("onEnd {s}", .{if (handlers.mode == .server) "S" else "C"});
|
||||
log("onEnd {s}", .{if (handlers.is_server) "S" else "C"});
|
||||
// Ensure the socket remains alive until this is finished
|
||||
this.ref();
|
||||
defer this.deref();
|
||||
@@ -535,7 +534,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
this.socket = s;
|
||||
if (this.socket.isDetached()) return;
|
||||
const handlers = this.getHandlers();
|
||||
log("onHandshake {s} ({d})", .{ if (handlers.mode == .server) "S" else "C", success });
|
||||
log("onHandshake {s} ({d})", .{ if (handlers.is_server) "S" else "C", success });
|
||||
|
||||
const authorized = if (success == 1) true else false;
|
||||
|
||||
@@ -572,7 +571,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
result = callback.call(globalObject, this_value, &[_]JSValue{this_value}) catch |err| globalObject.takeException(err);
|
||||
|
||||
// only call onOpen once for clients
|
||||
if (handlers.mode != .server) {
|
||||
if (!handlers.is_server) {
|
||||
// clean onOpen callback so only called in the first handshake and not in every renegotiation
|
||||
// on servers this would require a different approach but it's not needed because our servers will not call handshake multiple times
|
||||
// servers don't support renegotiation
|
||||
@@ -601,7 +600,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
pub fn onClose(this: *This, _: Socket, err: c_int, _: ?*anyopaque) bun.JSError!void {
|
||||
jsc.markBinding(@src());
|
||||
const handlers = this.getHandlers();
|
||||
log("onClose {s}", .{if (handlers.mode == .server) "S" else "C"});
|
||||
log("onClose {s}", .{if (handlers.is_server) "S" else "C"});
|
||||
this.detachNativeCallback();
|
||||
this.socket.detach();
|
||||
defer this.deref();
|
||||
@@ -649,7 +648,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
this.socket = s;
|
||||
if (this.socket.isDetached()) return;
|
||||
const handlers = this.getHandlers();
|
||||
log("onData {s} ({d})", .{ if (handlers.mode == .server) "S" else "C", data.len });
|
||||
log("onData {s} ({d})", .{ if (handlers.is_server) "S" else "C", data.len });
|
||||
if (this.native_callback.onData(data)) return;
|
||||
|
||||
const callback = handlers.onData;
|
||||
@@ -692,7 +691,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
pub fn getListener(this: *This, _: *jsc.JSGlobalObject) JSValue {
|
||||
const handlers = this.handlers orelse return .js_undefined;
|
||||
|
||||
if (handlers.mode != .server or this.socket.isDetached()) {
|
||||
if (!handlers.is_server or this.socket.isDetached()) {
|
||||
return .js_undefined;
|
||||
}
|
||||
|
||||
@@ -1353,7 +1352,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
};
|
||||
|
||||
const this_handlers = this.getHandlers();
|
||||
const handlers = try Handlers.fromJS(globalObject, socket_obj, this_handlers.mode == .server);
|
||||
const handlers = try Handlers.fromJS(globalObject, socket_obj, this_handlers.is_server);
|
||||
this_handlers.deinit();
|
||||
this_handlers.* = handlers;
|
||||
|
||||
@@ -1381,9 +1380,6 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
if (this.socket.isDetached() or this.socket.isNamedPipe()) {
|
||||
return .js_undefined;
|
||||
}
|
||||
if (this.isServer()) {
|
||||
return globalObject.throw("Server-side upgradeTLS is not supported. Use upgradeDuplexToTLS with isServer: true instead.", .{});
|
||||
}
|
||||
const args = callframe.arguments_old(1);
|
||||
|
||||
if (args.len < 1) {
|
||||
@@ -1575,7 +1571,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
this.socket.detach();
|
||||
|
||||
// start TLS handshake after we set extension on the socket
|
||||
new_socket.startTLS(handlers_ptr.mode != .server);
|
||||
new_socket.startTLS(!handlers_ptr.is_server);
|
||||
|
||||
success = true;
|
||||
return array;
|
||||
@@ -1758,23 +1754,6 @@ pub fn NewWrappedHandler(comptime tls: bool) type {
|
||||
};
|
||||
}
|
||||
|
||||
/// Unified socket mode replacing the old is_server bool + TLSMode pair.
|
||||
pub const SocketMode = enum {
|
||||
/// Default — TLS client or non-TLS socket
|
||||
client,
|
||||
/// Listener-owned server. TLS (if any) configured at the listener level.
|
||||
server,
|
||||
/// Duplex upgraded to TLS server role. Not listener-owned —
|
||||
/// markInactive uses client lifecycle path.
|
||||
duplex_server,
|
||||
|
||||
/// Returns true for any mode that acts as a TLS server (ALPN, handshake direction).
|
||||
/// Both .server and .duplex_server present as server to peers.
|
||||
pub fn isServer(this: SocketMode) bool {
|
||||
return this == .server or this == .duplex_server;
|
||||
}
|
||||
};
|
||||
|
||||
pub const DuplexUpgradeContext = struct {
|
||||
upgrade: uws.UpgradedDuplex,
|
||||
// We only us a tls and not a raw socket when upgrading a Duplex, Duplex dont support socketpairs
|
||||
@@ -1785,7 +1764,6 @@ pub const DuplexUpgradeContext = struct {
|
||||
task_event: EventState = .StartTLS,
|
||||
ssl_config: ?jsc.API.ServerConfig.SSLConfig,
|
||||
is_open: bool = false,
|
||||
#mode: SocketMode = .client,
|
||||
|
||||
pub const EventState = enum(u8) {
|
||||
StartTLS,
|
||||
@@ -1868,8 +1846,7 @@ pub const DuplexUpgradeContext = struct {
|
||||
switch (this.task_event) {
|
||||
.StartTLS => {
|
||||
if (this.ssl_config) |config| {
|
||||
log("DuplexUpgradeContext.startTLS mode={s}", .{@tagName(this.#mode)});
|
||||
this.upgrade.startTLS(config, this.#mode == .client) catch |err| {
|
||||
this.upgrade.startTLS(config, true) catch |err| {
|
||||
switch (err) {
|
||||
error.OutOfMemory => {
|
||||
bun.outOfMemory();
|
||||
@@ -1937,15 +1914,8 @@ pub fn jsUpgradeDuplexToTLS(globalObject: *jsc.JSGlobalObject, callframe: *jsc.C
|
||||
return globalObject.throw("Expected \"socket\" option", .{});
|
||||
};
|
||||
|
||||
var is_server = false;
|
||||
if (try opts.getTruthy(globalObject, "isServer")) |is_server_val| {
|
||||
is_server = is_server_val.toBoolean();
|
||||
}
|
||||
// Note: Handlers.fromJS is_server=false because these handlers are standalone
|
||||
// allocations (not embedded in a Listener). The mode field on Handlers
|
||||
// controls lifecycle (markInactive expects a Listener parent when .server).
|
||||
// The TLS direction (client vs server) is controlled by DuplexUpgradeContext.mode.
|
||||
const handlers = try Handlers.fromJS(globalObject, socket_obj, false);
|
||||
const is_server = false; // A duplex socket is always handled as a client
|
||||
const handlers = try Handlers.fromJS(globalObject, socket_obj, is_server);
|
||||
|
||||
var ssl_opts: ?jsc.API.ServerConfig.SSLConfig = null;
|
||||
if (try opts.getTruthy(globalObject, "tls")) |tls| {
|
||||
@@ -1967,9 +1937,6 @@ pub fn jsUpgradeDuplexToTLS(globalObject: *jsc.JSGlobalObject, callframe: *jsc.C
|
||||
|
||||
const handlers_ptr = bun.handleOom(handlers.vm.allocator.create(Handlers));
|
||||
handlers_ptr.* = handlers;
|
||||
// Set mode to duplex_server so TLSSocket.isServer() returns true for ALPN server mode
|
||||
// without affecting markInactive lifecycle (which requires a Listener parent).
|
||||
handlers_ptr.mode = if (is_server) .duplex_server else .client;
|
||||
var tls = bun.new(TLSSocket, .{
|
||||
.ref_count = .init(),
|
||||
.handlers = handlers_ptr,
|
||||
@@ -1996,7 +1963,6 @@ pub fn jsUpgradeDuplexToTLS(globalObject: *jsc.JSGlobalObject, callframe: *jsc.C
|
||||
.vm = globalObject.bunVM(),
|
||||
.task = undefined,
|
||||
.ssl_config = socket_config.*,
|
||||
.#mode = if (is_server) .duplex_server else .client,
|
||||
});
|
||||
tls.ref();
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ binary_type: BinaryType = .Buffer,
|
||||
vm: *jsc.VirtualMachine,
|
||||
globalObject: *jsc.JSGlobalObject,
|
||||
active_connections: u32 = 0,
|
||||
mode: SocketMode = .client,
|
||||
is_server: bool,
|
||||
promise: jsc.Strong.Optional = .empty,
|
||||
|
||||
protection_count: if (Environment.ci_assert) u32 else void = if (Environment.ci_assert) 0,
|
||||
@@ -81,7 +81,7 @@ pub fn markInactive(this: *Handlers) void {
|
||||
Listener.log("markInactive", .{});
|
||||
this.active_connections -= 1;
|
||||
if (this.active_connections == 0) {
|
||||
if (this.mode == .server) {
|
||||
if (this.is_server) {
|
||||
const listen_socket: *Listener = @fieldParentPtr("handlers", this);
|
||||
// allow it to be GC'd once the last connection is closed and it's not listening anymore
|
||||
if (listen_socket.listener == .none) {
|
||||
@@ -133,7 +133,7 @@ pub fn fromGenerated(
|
||||
var result: Handlers = .{
|
||||
.vm = globalObject.bunVM(),
|
||||
.globalObject = globalObject,
|
||||
.mode = if (is_server) .server else .client,
|
||||
.is_server = is_server,
|
||||
.binary_type = switch (generated.binary_type) {
|
||||
.arraybuffer => .ArrayBuffer,
|
||||
.buffer => .Buffer,
|
||||
@@ -217,7 +217,7 @@ pub fn clone(this: *const Handlers) Handlers {
|
||||
.vm = this.vm,
|
||||
.globalObject = this.globalObject,
|
||||
.binary_type = this.binary_type,
|
||||
.mode = this.mode,
|
||||
.is_server = this.is_server,
|
||||
};
|
||||
inline for (callback_fields) |field| {
|
||||
@field(result, field) = @field(this, field);
|
||||
@@ -346,7 +346,6 @@ const strings = bun.strings;
|
||||
const uws = bun.uws;
|
||||
const Listener = bun.api.Listener;
|
||||
const SSLConfig = bun.api.ServerConfig.SSLConfig;
|
||||
const SocketMode = bun.api.socket.SocketMode;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const JSValue = jsc.JSValue;
|
||||
|
||||
@@ -91,7 +91,7 @@ pub fn reload(this: *Listener, globalObject: *jsc.JSGlobalObject, callframe: *js
|
||||
return globalObject.throw("Expected \"socket\" object", .{});
|
||||
};
|
||||
|
||||
const handlers = try Handlers.fromJS(globalObject, socket_obj, this.handlers.mode == .server);
|
||||
const handlers = try Handlers.fromJS(globalObject, socket_obj, this.handlers.is_server);
|
||||
this.handlers.deinit();
|
||||
this.handlers = handlers;
|
||||
|
||||
@@ -773,7 +773,7 @@ pub fn connectInner(globalObject: *jsc.JSGlobalObject, prev_maybe_tcp: ?*TCPSock
|
||||
|
||||
const handlers_ptr = bun.handleOom(handlers.vm.allocator.create(Handlers));
|
||||
handlers_ptr.* = handlers.*;
|
||||
handlers_ptr.mode = .client;
|
||||
handlers_ptr.is_server = false;
|
||||
|
||||
var promise = jsc.JSPromise.create(globalObject);
|
||||
const promise_value = promise.toJS();
|
||||
|
||||
@@ -173,10 +173,8 @@ pub fn SSLWrapper(comptime T: type) type {
|
||||
|
||||
// flush buffered data and returns amount of pending data to write
|
||||
pub fn flush(this: *This) usize {
|
||||
// handleTraffic may trigger a close callback which frees ssl,
|
||||
// so we must not capture the ssl pointer before calling it.
|
||||
this.handleTraffic();
|
||||
const ssl = this.ssl orelse return 0;
|
||||
this.handleTraffic();
|
||||
const pending = BoringSSL.BIO_ctrl_pending(BoringSSL.SSL_get_wbio(ssl));
|
||||
if (pending > 0) return @intCast(pending);
|
||||
return 0;
|
||||
@@ -430,8 +428,6 @@ pub fn SSLWrapper(comptime T: type) type {
|
||||
if (read > 0) {
|
||||
log("triggering data callback (read {d})", .{read});
|
||||
this.triggerDataCallback(buffer[0..read]);
|
||||
// The data callback may have closed the connection
|
||||
if (this.ssl == null or this.flags.closed_notified) return false;
|
||||
}
|
||||
this.triggerCloseCallback();
|
||||
return false;
|
||||
|
||||
@@ -189,13 +189,9 @@ template<> struct WebCore::Converter<Bun::IDLArrayBufferRef>
|
||||
return jsBuffer;
|
||||
}
|
||||
if (auto* jsView = JSC::jsDynamicCast<JSC::JSArrayBufferView*>(value)) {
|
||||
if (jsView->isShared())
|
||||
return std::nullopt;
|
||||
return jsView->unsharedBuffer();
|
||||
}
|
||||
if (auto* jsDataView = JSC::jsDynamicCast<JSC::JSDataView*>(value)) {
|
||||
if (jsDataView->isShared())
|
||||
return std::nullopt;
|
||||
return jsDataView->unsharedBuffer();
|
||||
}
|
||||
return std::nullopt;
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#include <JavaScriptCore/VM.h>
|
||||
#include <JavaScriptCore/Heap.h>
|
||||
|
||||
extern "C" int Bun__defaultRemainingRunsUntilSkipReleaseAccess;
|
||||
|
||||
extern "C" void Bun__JSC_onBeforeWait(JSC::VM* _Nonnull vm)
|
||||
{
|
||||
ASSERT(vm);
|
||||
@@ -48,7 +46,7 @@ extern "C" void Bun__JSC_onBeforeWait(JSC::VM* _Nonnull vm)
|
||||
// finalizers that might've been waiting to be run is a good idea.
|
||||
// But if you haven't, like if the process is just waiting on I/O
|
||||
// then don't bother.
|
||||
const int defaultRemainingRunsUntilSkipReleaseAccess = Bun__defaultRemainingRunsUntilSkipReleaseAccess;
|
||||
static constexpr int defaultRemainingRunsUntilSkipReleaseAccess = 10;
|
||||
|
||||
static thread_local int remainingRunsUntilSkipReleaseAccess = 0;
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
#include "root.h"
|
||||
#include "BunMarkdownTagStrings.h"
|
||||
#include <JavaScriptCore/JSString.h>
|
||||
#include <JavaScriptCore/JSGlobalObject.h>
|
||||
#include <JavaScriptCore/LazyProperty.h>
|
||||
#include <JavaScriptCore/LazyPropertyInlines.h>
|
||||
#include "ZigGlobalObject.h"
|
||||
#include <JavaScriptCore/SlotVisitorInlines.h>
|
||||
#include <JavaScriptCore/VMTrapsInlines.h>
|
||||
|
||||
namespace Bun {
|
||||
using namespace JSC;
|
||||
|
||||
#define MARKDOWN_TAG_STRINGS_LAZY_PROPERTY_DEFINITION(name, str, idx) \
|
||||
this->m_strings[idx].initLater( \
|
||||
[](const JSC::LazyProperty<JSGlobalObject, JSString>::Initializer& init) { \
|
||||
init.set(jsOwnedString(init.vm, str)); \
|
||||
});
|
||||
|
||||
#define MARKDOWN_TAG_STRINGS_LAZY_PROPERTY_VISITOR(name, str, idx) \
|
||||
this->m_strings[idx].visit(visitor);
|
||||
|
||||
void MarkdownTagStrings::initialize()
|
||||
{
|
||||
MARKDOWN_TAG_STRINGS_EACH_NAME(MARKDOWN_TAG_STRINGS_LAZY_PROPERTY_DEFINITION)
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
void MarkdownTagStrings::visit(Visitor& visitor)
|
||||
{
|
||||
MARKDOWN_TAG_STRINGS_EACH_NAME(MARKDOWN_TAG_STRINGS_LAZY_PROPERTY_VISITOR)
|
||||
}
|
||||
|
||||
template void MarkdownTagStrings::visit(JSC::AbstractSlotVisitor&);
|
||||
template void MarkdownTagStrings::visit(JSC::SlotVisitor&);
|
||||
|
||||
} // namespace Bun
|
||||
|
||||
// C API for Zig bindings
|
||||
extern "C" JSC::EncodedJSValue BunMarkdownTagStrings__getTagString(Zig::GlobalObject* globalObject, uint8_t tagIndex)
|
||||
{
|
||||
if (tagIndex >= MARKDOWN_TAG_STRINGS_COUNT)
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
|
||||
auto& tagStrings = globalObject->markdownTagStrings();
|
||||
|
||||
// Use a switch to call the appropriate accessor
|
||||
switch (tagIndex) {
|
||||
#define MARKDOWN_TAG_STRINGS_CASE(name, str, idx) \
|
||||
case idx: \
|
||||
return JSC::JSValue::encode(tagStrings.name##String(globalObject));
|
||||
|
||||
MARKDOWN_TAG_STRINGS_EACH_NAME(MARKDOWN_TAG_STRINGS_CASE)
|
||||
|
||||
#undef MARKDOWN_TAG_STRINGS_CASE
|
||||
default:
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <JavaScriptCore/JSString.h>
|
||||
#include <JavaScriptCore/LazyProperty.h>
|
||||
|
||||
// Markdown HTML tag names cached as JSStrings
|
||||
// These are commonly reused when rendering markdown to React elements
|
||||
|
||||
// clang-format off
|
||||
#define MARKDOWN_TAG_STRINGS_EACH_NAME(macro) \
|
||||
macro(h1, "h1"_s, 0) \
|
||||
macro(h2, "h2"_s, 1) \
|
||||
macro(h3, "h3"_s, 2) \
|
||||
macro(h4, "h4"_s, 3) \
|
||||
macro(h5, "h5"_s, 4) \
|
||||
macro(h6, "h6"_s, 5) \
|
||||
macro(p, "p"_s, 6) \
|
||||
macro(blockquote, "blockquote"_s, 7) \
|
||||
macro(ul, "ul"_s, 8) \
|
||||
macro(ol, "ol"_s, 9) \
|
||||
macro(li, "li"_s, 10) \
|
||||
macro(pre, "pre"_s, 11) \
|
||||
macro(hr, "hr"_s, 12) \
|
||||
macro(html, "html"_s, 13) \
|
||||
macro(table, "table"_s, 14) \
|
||||
macro(thead, "thead"_s, 15) \
|
||||
macro(tbody, "tbody"_s, 16) \
|
||||
macro(tr, "tr"_s, 17) \
|
||||
macro(th, "th"_s, 18) \
|
||||
macro(td, "td"_s, 19) \
|
||||
macro(div, "div"_s, 20) \
|
||||
macro(em, "em"_s, 21) \
|
||||
macro(strong, "strong"_s, 22) \
|
||||
macro(a, "a"_s, 23) \
|
||||
macro(img, "img"_s, 24) \
|
||||
macro(code, "code"_s, 25) \
|
||||
macro(del, "del"_s, 26) \
|
||||
macro(math, "math"_s, 27) \
|
||||
macro(u, "u"_s, 28) \
|
||||
macro(br, "br"_s, 29)
|
||||
// clang-format on
|
||||
|
||||
#define MARKDOWN_TAG_STRINGS_COUNT 30
|
||||
|
||||
namespace Bun {
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
class MarkdownTagStrings {
|
||||
public:
|
||||
#define MARKDOWN_TAG_STRINGS_ACCESSOR_DEFINITION(name, str, idx) \
|
||||
JSC::JSString* name##String(JSC::JSGlobalObject* globalObject) \
|
||||
{ \
|
||||
return m_strings[idx].getInitializedOnMainThread(globalObject); \
|
||||
}
|
||||
|
||||
MARKDOWN_TAG_STRINGS_EACH_NAME(MARKDOWN_TAG_STRINGS_ACCESSOR_DEFINITION)
|
||||
|
||||
#undef MARKDOWN_TAG_STRINGS_ACCESSOR_DEFINITION
|
||||
|
||||
void initialize();
|
||||
|
||||
template<typename Visitor>
|
||||
void visit(Visitor& visitor);
|
||||
|
||||
private:
|
||||
JSC::LazyProperty<JSC::JSGlobalObject, JSC::JSString> m_strings[MARKDOWN_TAG_STRINGS_COUNT];
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
@@ -69,7 +69,6 @@ static uint8_t x86_cpu_features()
|
||||
#if CPU(ARM64)
|
||||
|
||||
#if OS(WINDOWS)
|
||||
#include <windows.h>
|
||||
#elif OS(MACOS)
|
||||
#include <sys/sysctl.h>
|
||||
#elif OS(LINUX)
|
||||
@@ -82,18 +81,7 @@ static uint8_t aarch64_cpu_features()
|
||||
uint8_t features = 0;
|
||||
|
||||
#if OS(WINDOWS)
|
||||
// FP is mandatory on AArch64 — no separate PF_ constant exists for it
|
||||
features |= 1 << static_cast<uint8_t>(AArch64CPUFeature::fp);
|
||||
if (IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE))
|
||||
features |= 1 << static_cast<uint8_t>(AArch64CPUFeature::neon);
|
||||
if (IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE))
|
||||
features |= 1 << static_cast<uint8_t>(AArch64CPUFeature::aes);
|
||||
if (IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE))
|
||||
features |= 1 << static_cast<uint8_t>(AArch64CPUFeature::crc32);
|
||||
if (IsProcessorFeaturePresent(PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE))
|
||||
features |= 1 << static_cast<uint8_t>(AArch64CPUFeature::atomics);
|
||||
if (IsProcessorFeaturePresent(PF_ARM_SVE_INSTRUCTIONS_AVAILABLE))
|
||||
features |= 1 << static_cast<uint8_t>(AArch64CPUFeature::sve);
|
||||
#pragma error "TODO: Implement AArch64 CPU features for Windows"
|
||||
#elif OS(MACOS)
|
||||
int value = 0;
|
||||
size_t size = sizeof(value);
|
||||
|
||||
@@ -39,7 +39,7 @@ static WebCore::ExceptionOr<void> encode(VM& vm, const WTF::BitSet<256>& doNotEs
|
||||
// 4-d-ii-1. Let V be the code unit value of C.
|
||||
char32_t codePoint;
|
||||
if (!U16_IS_LEAD(character))
|
||||
codePoint = static_cast<char32_t>(character);
|
||||
codePoint = character;
|
||||
else {
|
||||
// 4-d-iii. Else,
|
||||
// 4-d-iii-1. Increase k by 1.
|
||||
|
||||
@@ -55,10 +55,6 @@ template<typename CollectionType, typename KeyType> static auto findInSortedPair
|
||||
inline void checkEncodingTableInvariants() {}
|
||||
#endif
|
||||
|
||||
// LLVM 21+ -Wcharacter-conversion flags intentional char32_t/char16_t comparisons
|
||||
// used for Unicode code point range checks in findFirstInSortedPairs.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wcharacter-conversion"
|
||||
struct CompareFirst {
|
||||
template<typename TypeA, typename TypeB> bool operator()(const TypeA& a, const TypeB& b)
|
||||
{
|
||||
@@ -136,6 +132,5 @@ template<typename CollectionType, typename KeyType> static auto findInSortedPair
|
||||
}
|
||||
return std::ranges::equal_range(collection, makeFirstAdapter(key), CompareFirst {});
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
}
|
||||
|
||||
@@ -988,10 +988,6 @@ void JSMockFunctionPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* g
|
||||
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
|
||||
|
||||
this->putDirect(vm, Identifier::fromString(vm, "_isMockFunction"_s), jsBoolean(true), 0);
|
||||
|
||||
// Support `using spy = spyOn(...)` — auto-restores when leaving scope.
|
||||
JSValue restoreFn = this->getDirect(vm, Identifier::fromString(vm, "mockRestore"_s));
|
||||
this->putDirect(vm, vm.propertyNames->disposeSymbol, restoreFn, static_cast<unsigned>(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsMockFunctionGetMockImplementation, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
|
||||
|
||||
@@ -136,23 +136,23 @@ private:
|
||||
bool load_functions()
|
||||
{
|
||||
CFRelease = (void (*)(CFTypeRef))dlsym(cf_handle, "CFRelease");
|
||||
CFStringCreateWithCString = (CFStringRef (*)(CFAllocatorRef, const char*, CFStringEncoding))dlsym(cf_handle, "CFStringCreateWithCString");
|
||||
CFDataCreate = (CFDataRef (*)(CFAllocatorRef, const UInt8*, CFIndex))dlsym(cf_handle, "CFDataCreate");
|
||||
CFStringCreateWithCString = (CFStringRef(*)(CFAllocatorRef, const char*, CFStringEncoding))dlsym(cf_handle, "CFStringCreateWithCString");
|
||||
CFDataCreate = (CFDataRef(*)(CFAllocatorRef, const UInt8*, CFIndex))dlsym(cf_handle, "CFDataCreate");
|
||||
CFDataGetBytePtr = (const UInt8* (*)(CFDataRef))dlsym(cf_handle, "CFDataGetBytePtr");
|
||||
CFDataGetLength = (CFIndex (*)(CFDataRef))dlsym(cf_handle, "CFDataGetLength");
|
||||
CFDictionaryCreateMutable = (CFMutableDictionaryRef (*)(CFAllocatorRef, CFIndex, const CFDictionaryKeyCallBacks*, const CFDictionaryValueCallBacks*))dlsym(cf_handle, "CFDictionaryCreateMutable");
|
||||
CFDataGetLength = (CFIndex(*)(CFDataRef))dlsym(cf_handle, "CFDataGetLength");
|
||||
CFDictionaryCreateMutable = (CFMutableDictionaryRef(*)(CFAllocatorRef, CFIndex, const CFDictionaryKeyCallBacks*, const CFDictionaryValueCallBacks*))dlsym(cf_handle, "CFDictionaryCreateMutable");
|
||||
CFDictionaryAddValue = (void (*)(CFMutableDictionaryRef, const void*, const void*))dlsym(cf_handle, "CFDictionaryAddValue");
|
||||
CFStringGetCString = (Boolean (*)(CFStringRef, char*, CFIndex, CFStringEncoding))dlsym(cf_handle, "CFStringGetCString");
|
||||
CFStringGetCString = (Boolean(*)(CFStringRef, char*, CFIndex, CFStringEncoding))dlsym(cf_handle, "CFStringGetCString");
|
||||
CFStringGetCStringPtr = (const char* (*)(CFStringRef, CFStringEncoding))dlsym(cf_handle, "CFStringGetCStringPtr");
|
||||
CFStringGetLength = (CFIndex (*)(CFStringRef))dlsym(cf_handle, "CFStringGetLength");
|
||||
CFStringGetMaximumSizeForEncoding = (CFIndex (*)(CFIndex, CFStringEncoding))dlsym(cf_handle, "CFStringGetMaximumSizeForEncoding");
|
||||
CFStringGetLength = (CFIndex(*)(CFStringRef))dlsym(cf_handle, "CFStringGetLength");
|
||||
CFStringGetMaximumSizeForEncoding = (CFIndex(*)(CFIndex, CFStringEncoding))dlsym(cf_handle, "CFStringGetMaximumSizeForEncoding");
|
||||
|
||||
SecItemAdd = (OSStatus (*)(CFDictionaryRef, CFTypeRef*))dlsym(handle, "SecItemAdd");
|
||||
SecItemCopyMatching = (OSStatus (*)(CFDictionaryRef, CFTypeRef*))dlsym(handle, "SecItemCopyMatching");
|
||||
SecItemUpdate = (OSStatus (*)(CFDictionaryRef, CFDictionaryRef))dlsym(handle, "SecItemUpdate");
|
||||
SecItemDelete = (OSStatus (*)(CFDictionaryRef))dlsym(handle, "SecItemDelete");
|
||||
SecCopyErrorMessageString = (CFStringRef (*)(OSStatus, void*))dlsym(handle, "SecCopyErrorMessageString");
|
||||
SecAccessCreate = (OSStatus (*)(CFStringRef, CFArrayRef, SecAccessRef*))dlsym(handle, "SecAccessCreate");
|
||||
SecItemAdd = (OSStatus(*)(CFDictionaryRef, CFTypeRef*))dlsym(handle, "SecItemAdd");
|
||||
SecItemCopyMatching = (OSStatus(*)(CFDictionaryRef, CFTypeRef*))dlsym(handle, "SecItemCopyMatching");
|
||||
SecItemUpdate = (OSStatus(*)(CFDictionaryRef, CFDictionaryRef))dlsym(handle, "SecItemUpdate");
|
||||
SecItemDelete = (OSStatus(*)(CFDictionaryRef))dlsym(handle, "SecItemDelete");
|
||||
SecCopyErrorMessageString = (CFStringRef(*)(OSStatus, void*))dlsym(handle, "SecCopyErrorMessageString");
|
||||
SecAccessCreate = (OSStatus(*)(CFStringRef, CFArrayRef, SecAccessRef*))dlsym(handle, "SecAccessCreate");
|
||||
|
||||
return CFRelease && CFStringCreateWithCString && CFDataCreate && CFDataGetBytePtr && CFDataGetLength && CFDictionaryCreateMutable && CFDictionaryAddValue && SecItemAdd && SecItemCopyMatching && SecItemUpdate && SecItemDelete && SecCopyErrorMessageString && SecAccessCreate && CFStringGetCString && CFStringGetCStringPtr && CFStringGetLength && CFStringGetMaximumSizeForEncoding;
|
||||
}
|
||||
|
||||
@@ -199,19 +199,19 @@ private:
|
||||
g_free = (void (*)(gpointer))dlsym(glib_handle, "g_free");
|
||||
g_hash_table_new = (GHashTable * (*)(void*, void*)) dlsym(glib_handle, "g_hash_table_new");
|
||||
g_hash_table_destroy = (void (*)(GHashTable*))dlsym(glib_handle, "g_hash_table_destroy");
|
||||
g_hash_table_lookup = (gpointer (*)(GHashTable*, gpointer))dlsym(glib_handle, "g_hash_table_lookup");
|
||||
g_hash_table_lookup = (gpointer(*)(GHashTable*, gpointer))dlsym(glib_handle, "g_hash_table_lookup");
|
||||
g_hash_table_insert = (void (*)(GHashTable*, gpointer, gpointer))dlsym(glib_handle, "g_hash_table_insert");
|
||||
g_list_free = (void (*)(GList*))dlsym(glib_handle, "g_list_free");
|
||||
g_list_free_full = (void (*)(GList*, void (*)(gpointer)))dlsym(glib_handle, "g_list_free_full");
|
||||
g_str_hash = (guint (*)(gpointer))dlsym(glib_handle, "g_str_hash");
|
||||
g_str_equal = (gboolean (*)(gpointer, gpointer))dlsym(glib_handle, "g_str_equal");
|
||||
g_str_hash = (guint(*)(gpointer))dlsym(glib_handle, "g_str_hash");
|
||||
g_str_equal = (gboolean(*)(gpointer, gpointer))dlsym(glib_handle, "g_str_equal");
|
||||
|
||||
// Load libsecret functions
|
||||
secret_password_store_sync = (gboolean (*)(const SecretSchema*, const gchar*, const gchar*, const gchar*, void*, GError**, ...))
|
||||
secret_password_store_sync = (gboolean(*)(const SecretSchema*, const gchar*, const gchar*, const gchar*, void*, GError**, ...))
|
||||
dlsym(secret_handle, "secret_password_store_sync");
|
||||
secret_password_lookup_sync = (gchar * (*)(const SecretSchema*, void*, GError**, ...))
|
||||
dlsym(secret_handle, "secret_password_lookup_sync");
|
||||
secret_password_clear_sync = (gboolean (*)(const SecretSchema*, void*, GError**, ...))
|
||||
secret_password_clear_sync = (gboolean(*)(const SecretSchema*, void*, GError**, ...))
|
||||
dlsym(secret_handle, "secret_password_clear_sync");
|
||||
secret_password_free = (void (*)(gchar*))dlsym(secret_handle, "secret_password_free");
|
||||
secret_service_search_sync = (GList * (*)(SecretService*, const SecretSchema*, GHashTable*, SecretSearchFlags, void*, GError**))
|
||||
@@ -220,7 +220,7 @@ private:
|
||||
secret_value_get_text = (const gchar* (*)(SecretValue*))dlsym(secret_handle, "secret_value_get_text");
|
||||
secret_value_unref = (void (*)(gpointer))dlsym(secret_handle, "secret_value_unref");
|
||||
secret_item_get_attributes = (GHashTable * (*)(SecretItem*)) dlsym(secret_handle, "secret_item_get_attributes");
|
||||
secret_item_load_secret_sync = (gboolean (*)(SecretItem*, void*, GError**))dlsym(secret_handle, "secret_item_load_secret_sync");
|
||||
secret_item_load_secret_sync = (gboolean(*)(SecretItem*, void*, GError**))dlsym(secret_handle, "secret_item_load_secret_sync");
|
||||
|
||||
return g_error_free && g_free && g_hash_table_new && g_hash_table_destroy && g_hash_table_lookup && g_hash_table_insert && g_list_free && secret_password_store_sync && secret_password_lookup_sync && secret_password_clear_sync && secret_password_free;
|
||||
}
|
||||
|
||||
@@ -890,7 +890,7 @@ static const GB18030EncodeIndex& gb18030EncodeIndex()
|
||||
// https://unicode-org.atlassian.net/browse/ICU-22357
|
||||
// The 2-byte values are handled correctly by values from gb18030()
|
||||
// but these need to be exceptions from gb18030Ranges().
|
||||
static std::optional<uint16_t> gb18030AsymmetricEncode(char32_t codePoint)
|
||||
static std::optional<uint16_t> gb18030AsymmetricEncode(char16_t codePoint)
|
||||
{
|
||||
switch (codePoint) {
|
||||
case 0xE81E:
|
||||
|
||||
@@ -302,6 +302,7 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c
|
||||
JSC::Options::useJITCage() = false;
|
||||
JSC::Options::useShadowRealm() = true;
|
||||
JSC::Options::useV8DateParser() = true;
|
||||
JSC::Options::useMathSumPreciseMethod() = true;
|
||||
JSC::Options::evalMode() = evalMode;
|
||||
JSC::Options::heapGrowthSteepnessFactor() = 1.0;
|
||||
JSC::Options::heapGrowthMaxIncrease() = 2.0;
|
||||
@@ -1698,7 +1699,6 @@ void GlobalObject::finishCreation(VM& vm)
|
||||
m_commonStrings.initialize();
|
||||
m_http2CommonStrings.initialize();
|
||||
m_bakeAdditions.initialize();
|
||||
m_markdownTagStrings.initialize();
|
||||
|
||||
Bun::addNodeModuleConstructorProperties(vm, this);
|
||||
m_JSNodeHTTPServerSocketStructure.initLater(
|
||||
|
||||
@@ -58,7 +58,6 @@ struct node_module;
|
||||
#include "headers-handwritten.h"
|
||||
#include "BunCommonStrings.h"
|
||||
#include "BunHttp2CommonStrings.h"
|
||||
#include "BunMarkdownTagStrings.h"
|
||||
#include "BunGlobalScope.h"
|
||||
#include <js_native_api.h>
|
||||
#include <node_api.h>
|
||||
@@ -527,7 +526,6 @@ public:
|
||||
V(private, std::unique_ptr<WebCore::DOMConstructors>, m_constructors) \
|
||||
V(private, Bun::CommonStrings, m_commonStrings) \
|
||||
V(private, Bun::Http2CommonStrings, m_http2CommonStrings) \
|
||||
V(private, Bun::MarkdownTagStrings, m_markdownTagStrings) \
|
||||
\
|
||||
/* JSC's hashtable code-generator tries to access these properties, so we make them public. */ \
|
||||
/* However, we'd like it better if they could be protected. */ \
|
||||
@@ -718,7 +716,6 @@ public:
|
||||
|
||||
Bun::CommonStrings& commonStrings() { return m_commonStrings; }
|
||||
Bun::Http2CommonStrings& http2CommonStrings() { return m_http2CommonStrings; }
|
||||
Bun::MarkdownTagStrings& markdownTagStrings() { return m_markdownTagStrings; }
|
||||
#include "ZigGeneratedClasses+lazyStructureHeader.h"
|
||||
|
||||
void finishCreation(JSC::VM&);
|
||||
|
||||
@@ -80,20 +80,16 @@ size_t IndexOfAnyCharImpl(const uint8_t* HWY_RESTRICT text, size_t text_len, con
|
||||
return text_len;
|
||||
} else {
|
||||
ASSERT(chars_len <= 16);
|
||||
|
||||
const size_t simd_text_len = text_len - (text_len % N);
|
||||
size_t i = 0;
|
||||
|
||||
#if !HWY_HAVE_SCALABLE && !HWY_TARGET_IS_SVE
|
||||
// Preload search characters into native-width vectors.
|
||||
// On non-SVE targets, Vec has a known size and can be stored in arrays.
|
||||
static constexpr size_t kMaxPreloadedChars = 16;
|
||||
constexpr size_t kMaxPreloadedChars = 16;
|
||||
hn::Vec<D8> char_vecs[kMaxPreloadedChars];
|
||||
const size_t num_chars_to_preload = std::min(chars_len, kMaxPreloadedChars);
|
||||
for (size_t c = 0; c < num_chars_to_preload; ++c) {
|
||||
char_vecs[c] = hn::Set(d, chars[c]);
|
||||
}
|
||||
|
||||
const size_t simd_text_len = text_len - (text_len % N);
|
||||
size_t i = 0;
|
||||
|
||||
for (; i < simd_text_len; i += N) {
|
||||
const auto text_vec = hn::LoadN(d, text + i, N);
|
||||
auto found_mask = hn::MaskFalse(d);
|
||||
@@ -101,18 +97,11 @@ size_t IndexOfAnyCharImpl(const uint8_t* HWY_RESTRICT text, size_t text_len, con
|
||||
for (size_t c = 0; c < num_chars_to_preload; ++c) {
|
||||
found_mask = hn::Or(found_mask, hn::Eq(text_vec, char_vecs[c]));
|
||||
}
|
||||
#else
|
||||
// SVE types are sizeless and cannot be stored in arrays.
|
||||
// hn::Set is a single broadcast instruction; the compiler will
|
||||
// hoist these loop-invariant broadcasts out of the outer loop.
|
||||
for (; i < simd_text_len; i += N) {
|
||||
const auto text_vec = hn::LoadN(d, text + i, N);
|
||||
auto found_mask = hn::MaskFalse(d);
|
||||
|
||||
for (size_t c = 0; c < chars_len; ++c) {
|
||||
found_mask = hn::Or(found_mask, hn::Eq(text_vec, hn::Set(d, chars[c])));
|
||||
if (chars_len > num_chars_to_preload) {
|
||||
for (size_t c = num_chars_to_preload; c < chars_len; ++c) {
|
||||
found_mask = hn::Or(found_mask, hn::Eq(text_vec, hn::Set(d, chars[c])));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
const intptr_t pos = hn::FindFirstTrue(d, found_mask);
|
||||
if (pos >= 0) {
|
||||
|
||||
@@ -171,8 +171,8 @@ void AbortSignal::runAbortSteps()
|
||||
algorithm.second(reason);
|
||||
|
||||
// 3. Fire an event named abort at signal.
|
||||
if (hasEventListeners(eventNames().abortEvent))
|
||||
dispatchEvent(Event::create(eventNames().abortEvent, Event::CanBubble::No, Event::IsCancelable::No));
|
||||
dispatchEvent(Event::create(eventNames().abortEvent, Event::CanBubble::No, Event::IsCancelable::No));
|
||||
|
||||
setIsFiringEventListeners(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ bool EventTarget::addEventListener(const AtomString& eventType, Ref<EventListene
|
||||
if (options.signal) {
|
||||
options.signal->addAlgorithm([weakThis = WeakPtr { *this }, eventType, listener = WeakPtr { listener }, capture = options.capture](JSC::JSValue) {
|
||||
if (weakThis && listener)
|
||||
Ref { *weakThis }->removeEventListener(eventType, *listener, capture);
|
||||
Ref { *weakThis } -> removeEventListener(eventType, *listener, capture);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -90,11 +90,6 @@ JSC_DEFINE_HOST_FUNCTION(structuredCloneForStream, (JSGlobalObject * globalObjec
|
||||
auto* bufferView = jsCast<JSArrayBufferView*>(value);
|
||||
ASSERT(bufferView);
|
||||
|
||||
if (bufferView->isShared()) {
|
||||
throwDataCloneError(*globalObject, scope);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* buffer = bufferView->unsharedBuffer();
|
||||
if (!buffer) {
|
||||
throwDataCloneError(*globalObject, scope);
|
||||
|
||||
@@ -470,7 +470,7 @@ String generatePatternString(const Vector<Part>& partList, const URLPatternStrin
|
||||
|
||||
if (!needsGrouping && part.prefix.isEmpty() && previousPart && previousPart->type == PartType::FixedText && !previousPart->value.isEmpty()) {
|
||||
if (options.prefixCodepoint.length() == 1
|
||||
&& options.prefixCodepoint.startsWith(static_cast<char16_t>(*StringView(previousPart->value).codePoints().codePointAt(previousPart->value.length() - 1))))
|
||||
&& options.prefixCodepoint.startsWith(*StringView(previousPart->value).codePoints().codePointAt(previousPart->value.length() - 1)))
|
||||
needsGrouping = true;
|
||||
}
|
||||
|
||||
@@ -541,7 +541,7 @@ String escapePatternString(StringView input)
|
||||
}
|
||||
|
||||
// https://urlpattern.spec.whatwg.org/#is-a-valid-name-code-point
|
||||
bool isValidNameCodepoint(char32_t codepoint, URLPatternUtilities::IsFirst first)
|
||||
bool isValidNameCodepoint(char16_t codepoint, URLPatternUtilities::IsFirst first)
|
||||
{
|
||||
if (first == URLPatternUtilities::IsFirst::Yes)
|
||||
return u_hasBinaryProperty(codepoint, UCHAR_ID_START) || codepoint == '_' || codepoint == '$';
|
||||
|
||||
@@ -104,7 +104,7 @@ ASCIILiteral convertModifierToString(Modifier);
|
||||
std::pair<String, Vector<String>> generateRegexAndNameList(const Vector<Part>& partList, const URLPatternStringOptions&);
|
||||
String generatePatternString(const Vector<Part>& partList, const URLPatternStringOptions&);
|
||||
String escapePatternString(StringView input);
|
||||
bool isValidNameCodepoint(char32_t codepoint, URLPatternUtilities::IsFirst);
|
||||
bool isValidNameCodepoint(char16_t codepoint, URLPatternUtilities::IsFirst);
|
||||
|
||||
} // namespace URLPatternUtilities
|
||||
} // namespace WebCore
|
||||
|
||||
@@ -49,14 +49,14 @@ WebCoreTypedArrayController::WebCoreTypedArrayController(bool allowAtomicsWait)
|
||||
|
||||
WebCoreTypedArrayController::~WebCoreTypedArrayController() = default;
|
||||
|
||||
JSC::JSArrayBuffer* WebCoreTypedArrayController::toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSGlobalObject* globalObject, JSC::ArrayBuffer& buffer)
|
||||
JSC::JSArrayBuffer* WebCoreTypedArrayController::toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSGlobalObject* globalObject, JSC::ArrayBuffer* buffer)
|
||||
{
|
||||
return JSC::jsCast<JSC::JSArrayBuffer*>(WebCore::toJS(lexicalGlobalObject, getDefaultGlobal(globalObject), buffer));
|
||||
}
|
||||
|
||||
void WebCoreTypedArrayController::registerWrapper(JSC::JSGlobalObject* globalObject, JSC::ArrayBuffer& native, JSC::JSArrayBuffer& wrapper)
|
||||
void WebCoreTypedArrayController::registerWrapper(JSC::JSGlobalObject* globalObject, JSC::ArrayBuffer* native, JSC::JSArrayBuffer* wrapper)
|
||||
{
|
||||
cacheWrapper(static_cast<JSVMClientData*>(JSC::getVM(globalObject).clientData)->normalWorld(), &native, &wrapper);
|
||||
cacheWrapper(static_cast<JSVMClientData*>(JSC::getVM(globalObject).clientData)->normalWorld(), native, wrapper);
|
||||
}
|
||||
|
||||
bool WebCoreTypedArrayController::isAtomicsWaitAllowedOnCurrentThread()
|
||||
|
||||
@@ -35,8 +35,8 @@ public:
|
||||
WebCoreTypedArrayController(bool allowAtomicsWait);
|
||||
virtual ~WebCoreTypedArrayController();
|
||||
|
||||
JSC::JSArrayBuffer* toJS(JSC::JSGlobalObject*, JSC::JSGlobalObject*, JSC::ArrayBuffer&) override;
|
||||
void registerWrapper(JSC::JSGlobalObject*, ArrayBuffer&, JSC::JSArrayBuffer&) override;
|
||||
JSC::JSArrayBuffer* toJS(JSC::JSGlobalObject*, JSC::JSGlobalObject*, JSC::ArrayBuffer*) override;
|
||||
void registerWrapper(JSC::JSGlobalObject*, ArrayBuffer*, JSC::JSArrayBuffer*) override;
|
||||
bool isAtomicsWaitAllowedOnCurrentThread() override;
|
||||
|
||||
JSC::WeakHandleOwner* wrapperOwner() { return &m_owner; }
|
||||
|
||||
@@ -1323,7 +1323,6 @@ void WebSocket::didReceiveBinaryData(const AtomString& eventName, const std::spa
|
||||
|
||||
if (auto* context = scriptExecutionContext()) {
|
||||
RefPtr<Blob> blob = Blob::create(binaryData, context->jsGlobalObject());
|
||||
this->incPendingActivityCount();
|
||||
context->postTask([this, name = eventName, blob = blob.releaseNonNull(), protectedThis = Ref { *this }](ScriptExecutionContext& context) {
|
||||
ASSERT(scriptExecutionContext());
|
||||
protectedThis->dispatchEvent(MessageEvent::create(name, blob, protectedThis->m_url.string()));
|
||||
|
||||
@@ -249,9 +249,32 @@ extern "C" __attribute__((used)) char __libc_single_threaded = 0;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _LIBCPP_VERBOSE_ABORT_NOEXCEPT
|
||||
// Workaround for this error:
|
||||
// workaround-missing-symbols.cpp:245:11: error: '__libcpp_verbose_abort' is missing exception specification 'noexcept'
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// 245 | void std::__libcpp_verbose_abort(char const* format, ...)
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// | ^
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// | noexcept
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// /opt/homebrew/Cellar/llvm/20.1.7/bin/../include/c++/v1/__verbose_abort:30:28: note: previous declaration is here
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// 30 | __printf__, 1, 2) void __libcpp_verbose_abort(const char* __format, ...) _LIBCPP_VERBOSE_ABORT_NOEXCEPT;
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// | ^
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// 1 error generated.
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// [515/540] Building CXX
|
||||
#define BUN_VERBOSE_ABORT_NOEXCEPT _LIBCPP_VERBOSE_ABORT_NOEXCEPT
|
||||
#else
|
||||
#define BUN_VERBOSE_ABORT_NOEXCEPT
|
||||
#endif
|
||||
|
||||
// Provide our implementation
|
||||
// LLVM 20 used _LIBCPP_VERBOSE_ABORT_NOEXCEPT, LLVM 21+ uses _NOEXCEPT (always noexcept).
|
||||
void std::__libcpp_verbose_abort(char const* format, ...) noexcept
|
||||
void std::__libcpp_verbose_abort(char const* format, ...) BUN_VERBOSE_ABORT_NOEXCEPT
|
||||
{
|
||||
va_list list;
|
||||
va_start(list, format);
|
||||
|
||||
@@ -33,7 +33,7 @@ static char32_t decodeUTF16(const UChar* ptr, size_t available, size_t& outLen)
|
||||
}
|
||||
|
||||
outLen = 1;
|
||||
return static_cast<char32_t>(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline uint8_t getVisibleWidth(char32_t cp, bool ambiguousIsWide)
|
||||
|
||||
@@ -52,14 +52,6 @@ pub fn init(this: *GarbageCollectionController, vm: *VirtualMachine) void {
|
||||
}
|
||||
this.gc_timer_interval = gc_timer_interval;
|
||||
|
||||
if (vm.transpiler.env.get("BUN_GC_RUNS_UNTIL_SKIP_RELEASE_ACCESS")) |val| {
|
||||
if (std.fmt.parseInt(c_int, val, 10)) |parsed| {
|
||||
if (parsed >= 0) {
|
||||
VirtualMachine.Bun__defaultRemainingRunsUntilSkipReleaseAccess = parsed;
|
||||
}
|
||||
} else |_| {}
|
||||
}
|
||||
|
||||
this.disabled = vm.transpiler.env.has("BUN_GC_TIMER_DISABLE");
|
||||
|
||||
if (!this.disabled)
|
||||
|
||||
@@ -79,13 +79,11 @@ JSC_DEFINE_HOST_FUNCTION(functionStartRemoteDebugger,
|
||||
JSC::JSValue hostValue = callFrame->argument(0);
|
||||
JSC::JSValue portValue = callFrame->argument(1);
|
||||
const char* host = defaultHost;
|
||||
WTF::CString hostCString;
|
||||
if (hostValue.isString()) {
|
||||
|
||||
auto str = hostValue.toWTFString(globalObject);
|
||||
hostCString = toCString(str);
|
||||
if (!str.isEmpty())
|
||||
host = hostCString.span().data();
|
||||
host = toCString(str).span().data();
|
||||
} else if (!hostValue.isUndefined()) {
|
||||
throwVMError(globalObject, scope,
|
||||
createTypeError(globalObject, "host must be a string"_s));
|
||||
|
||||
@@ -1700,8 +1700,8 @@ pub fn dumpStackTrace(trace: std.builtin.StackTrace, limits: WriteStackTraceLimi
|
||||
|
||||
const programs: []const [:0]const u8 = switch (bun.Environment.os) {
|
||||
.windows => &.{"pdb-addr2line"},
|
||||
// if `llvm-symbolizer` doesn't work, also try `llvm-symbolizer-21`
|
||||
else => &.{ "llvm-symbolizer", "llvm-symbolizer-21" },
|
||||
// if `llvm-symbolizer` doesn't work, also try `llvm-symbolizer-19`
|
||||
else => &.{ "llvm-symbolizer", "llvm-symbolizer-19" },
|
||||
};
|
||||
for (programs) |program| {
|
||||
var arena = bun.ArenaAllocator.init(bun.default_allocator);
|
||||
|
||||
@@ -58,6 +58,10 @@ pub const BUN_INSTALL = New(kind.string, "BUN_INSTALL", .{});
|
||||
pub const BUN_INSTALL_BIN = New(kind.string, "BUN_INSTALL_BIN", .{});
|
||||
pub const BUN_INSTALL_GLOBAL_DIR = New(kind.string, "BUN_INSTALL_GLOBAL_DIR", .{});
|
||||
pub const BUN_NEEDS_PROC_SELF_WORKAROUND = New(kind.boolean, "BUN_NEEDS_PROC_SELF_WORKAROUND", .{ .default = false });
|
||||
/// Override path used for self-executable detection. Useful when running via explicit
|
||||
/// dynamic linker (e.g., /lib64/ld-linux-x86-64.so.2 ./my-app) where /proc/self/exe
|
||||
/// points to the linker instead of the actual binary.
|
||||
pub const BUN_SELF_EXE = PlatformSpecificNew(kind.string, "BUN_SELF_EXE", null, .{});
|
||||
pub const BUN_OPTIONS = New(kind.string, "BUN_OPTIONS", .{});
|
||||
pub const BUN_POSTGRES_SOCKET_MONITOR = New(kind.string, "BUN_POSTGRES_SOCKET_MONITOR", .{});
|
||||
pub const BUN_POSTGRES_SOCKET_MONITOR_READER = New(kind.string, "BUN_POSTGRES_SOCKET_MONITOR_READER", .{});
|
||||
|
||||
@@ -1,395 +0,0 @@
|
||||
const { Duplex } = require("node:stream");
|
||||
const upgradeDuplexToTLS = $newZigFunction("socket.zig", "jsUpgradeDuplexToTLS", 2);
|
||||
|
||||
interface NativeHandle {
|
||||
resume(): void;
|
||||
close(): void;
|
||||
end(): void;
|
||||
$write(chunk: Buffer, encoding: string): boolean;
|
||||
alpnProtocol?: string;
|
||||
}
|
||||
|
||||
interface UpgradeContextType {
|
||||
connectionListener: (...args: any[]) => any;
|
||||
server: Http2SecureServer;
|
||||
rawSocket: import("node:net").Socket;
|
||||
nativeHandle: NativeHandle | null;
|
||||
events: [(...args: any[]) => void, ...Function[]] | null;
|
||||
}
|
||||
|
||||
interface Http2SecureServer {
|
||||
key?: Buffer;
|
||||
cert?: Buffer;
|
||||
ca?: Buffer;
|
||||
passphrase?: string;
|
||||
ALPNProtocols?: Buffer;
|
||||
_requestCert?: boolean;
|
||||
_rejectUnauthorized?: boolean;
|
||||
emit(event: string, ...args: any[]): boolean;
|
||||
}
|
||||
|
||||
interface TLSProxySocket {
|
||||
_ctx: UpgradeContextType;
|
||||
_writeCallback: ((err?: Error | null) => void) | null;
|
||||
alpnProtocol: string | null;
|
||||
authorized: boolean;
|
||||
encrypted: boolean;
|
||||
server: Http2SecureServer;
|
||||
_requestCert: boolean;
|
||||
_rejectUnauthorized: boolean;
|
||||
_securePending: boolean;
|
||||
secureConnecting: boolean;
|
||||
_secureEstablished: boolean;
|
||||
authorizationError?: string;
|
||||
push(chunk: Buffer | null): boolean;
|
||||
destroy(err?: Error): this;
|
||||
emit(event: string, ...args: any[]): boolean;
|
||||
resume(): void;
|
||||
readonly destroyed: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Context object holding upgrade-time state for the TLS proxy socket.
|
||||
* Attached as `tlsSocket._ctx` so named functions can reach it via `this._ctx`
|
||||
* (Duplex methods) or via a bound `this` (socket callbacks).
|
||||
*/
|
||||
function UpgradeContext(
|
||||
connectionListener: (...args: any[]) => any,
|
||||
server: Http2SecureServer,
|
||||
rawSocket: import("node:net").Socket,
|
||||
) {
|
||||
this.connectionListener = connectionListener;
|
||||
this.server = server;
|
||||
this.rawSocket = rawSocket;
|
||||
this.nativeHandle = null;
|
||||
this.events = null;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Duplex stream methods — called with `this` = tlsSocket (standard stream API)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// _read: called by stream machinery when the H2 session wants data.
|
||||
// Resume the native TLS handle so it feeds decrypted data via the data callback.
|
||||
// Mirrors net.ts Socket.prototype._read which calls socket.resume().
|
||||
function tlsSocketRead(this: TLSProxySocket) {
|
||||
const h = this._ctx.nativeHandle;
|
||||
if (h) {
|
||||
h.resume();
|
||||
}
|
||||
this._ctx.rawSocket.resume();
|
||||
}
|
||||
|
||||
// _write: called when the H2 session writes outbound frames.
|
||||
// Forward to the native TLS handle for encryption, then back to rawSocket.
|
||||
// Mirrors net.ts Socket.prototype._write which calls socket.$write().
|
||||
function tlsSocketWrite(this: TLSProxySocket, chunk: Buffer, encoding: string, callback: (err?: Error) => void) {
|
||||
const h = this._ctx.nativeHandle;
|
||||
if (!h) {
|
||||
callback(new Error("Socket is closed"));
|
||||
return;
|
||||
}
|
||||
// $write returns true if fully flushed, false if buffered
|
||||
if (h.$write(chunk, encoding)) {
|
||||
callback();
|
||||
} else {
|
||||
// Store callback so drain event can invoke it (backpressure)
|
||||
this._writeCallback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
// _destroy: called when the stream is destroyed (e.g. tlsSocket.destroy(err)).
|
||||
// Cleans up the native TLS handle.
|
||||
// Mirrors net.ts Socket.prototype._destroy.
|
||||
function tlsSocketDestroy(this: TLSProxySocket, err: Error | null, callback: (err?: Error | null) => void) {
|
||||
const h = this._ctx.nativeHandle;
|
||||
if (h) {
|
||||
h.close();
|
||||
this._ctx.nativeHandle = null;
|
||||
}
|
||||
// Must invoke pending write callback with error per Writable stream contract
|
||||
const writeCb = this._writeCallback;
|
||||
if (writeCb) {
|
||||
this._writeCallback = null;
|
||||
writeCb(err ?? new Error("Socket destroyed"));
|
||||
}
|
||||
callback(err);
|
||||
}
|
||||
|
||||
// _final: called when the writable side is ending (all data flushed).
|
||||
// Shuts down the TLS write side gracefully.
|
||||
// Mirrors net.ts Socket.prototype._final.
|
||||
function tlsSocketFinal(this: TLSProxySocket, callback: () => void) {
|
||||
const h = this._ctx.nativeHandle;
|
||||
if (!h) return callback();
|
||||
// Signal end-of-stream to the TLS layer
|
||||
h.end();
|
||||
callback();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Socket callbacks — called by Zig with `this` = native handle (not useful).
|
||||
// All are bound to tlsSocket so `this` inside each = tlsSocket.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// open: called when the TLS layer is initialized (before handshake).
|
||||
// No action needed; we wait for the handshake callback.
|
||||
function socketOpen() {}
|
||||
|
||||
// data: called with decrypted plaintext after the TLS layer decrypts incoming data.
|
||||
// Push into tlsSocket so the H2 session's _read() receives these frames.
|
||||
function socketData(this: TLSProxySocket, _socket: NativeHandle, chunk: Buffer) {
|
||||
if (!this.push(chunk)) {
|
||||
this._ctx.rawSocket.pause();
|
||||
}
|
||||
}
|
||||
|
||||
// end: TLS peer signaled end-of-stream; signal EOF to the H2 session.
|
||||
function socketEnd(this: TLSProxySocket) {
|
||||
this.push(null);
|
||||
}
|
||||
|
||||
// drain: raw socket is writable again after being full; propagate backpressure signal.
|
||||
// If _write stored a callback waiting for drain, invoke it now.
|
||||
function socketDrain(this: TLSProxySocket) {
|
||||
const cb = this._writeCallback;
|
||||
if (cb) {
|
||||
this._writeCallback = null;
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
// close: TLS connection closed; tear down the tlsSocket Duplex.
|
||||
function socketClose(this: TLSProxySocket) {
|
||||
if (!this.destroyed) {
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// error: TLS-level error (e.g. certificate verification failure).
|
||||
// In server mode without _requestCert, the server doesn't request a client cert,
|
||||
// so issuer verification errors on the server's own cert are non-fatal.
|
||||
function socketError(this: TLSProxySocket, _socket: NativeHandle, err: NodeJS.ErrnoException) {
|
||||
const ctx = this._ctx;
|
||||
if (!ctx.server._requestCert && err?.code === "UNABLE_TO_GET_ISSUER_CERT") {
|
||||
return;
|
||||
}
|
||||
this.destroy(err);
|
||||
}
|
||||
|
||||
// timeout: socket idle timeout; forward to the Duplex so H2 session can handle it.
|
||||
function socketTimeout(this: TLSProxySocket) {
|
||||
this.emit("timeout");
|
||||
}
|
||||
|
||||
// handshake: TLS handshake completed. This is the critical callback that triggers
|
||||
// H2 session creation.
|
||||
//
|
||||
// Mirrors the handshake logic in net.ts ServerHandlers.handshake:
|
||||
// - Set secure-connection state flags on tlsSocket
|
||||
// - Read alpnProtocol from the native handle (set by ALPN negotiation)
|
||||
// - Handle _requestCert / _rejectUnauthorized for mutual TLS
|
||||
// - Call connectionListener to create the ServerHttp2Session
|
||||
function socketHandshake(
|
||||
this: TLSProxySocket,
|
||||
nativeHandle: NativeHandle,
|
||||
success: boolean,
|
||||
verifyError: NodeJS.ErrnoException | null,
|
||||
) {
|
||||
const tlsSocket = this; // bound
|
||||
const ctx = tlsSocket._ctx;
|
||||
|
||||
if (!success) {
|
||||
const err = verifyError || new Error("TLS handshake failed");
|
||||
ctx.server.emit("tlsClientError", err, tlsSocket);
|
||||
tlsSocket.destroy(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark TLS handshake as complete on the proxy socket
|
||||
tlsSocket._securePending = false;
|
||||
tlsSocket.secureConnecting = false;
|
||||
tlsSocket._secureEstablished = true;
|
||||
|
||||
// Copy the negotiated ALPN protocol (e.g. "h2") from the native TLS handle.
|
||||
// The H2 session checks this to confirm HTTP/2 was negotiated.
|
||||
tlsSocket.alpnProtocol = nativeHandle?.alpnProtocol ?? null;
|
||||
|
||||
// Handle mutual TLS: if the server requested a client cert, check for errors
|
||||
if (tlsSocket._requestCert || tlsSocket._rejectUnauthorized) {
|
||||
if (verifyError) {
|
||||
tlsSocket.authorized = false;
|
||||
tlsSocket.authorizationError = verifyError.code || verifyError.message;
|
||||
ctx.server.emit("tlsClientError", verifyError, tlsSocket);
|
||||
if (tlsSocket._rejectUnauthorized) {
|
||||
tlsSocket.emit("secure", tlsSocket);
|
||||
tlsSocket.destroy(verifyError);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
tlsSocket.authorized = true;
|
||||
}
|
||||
} else {
|
||||
tlsSocket.authorized = true;
|
||||
}
|
||||
|
||||
// Invoke the H2 connectionListener which creates a ServerHttp2Session.
|
||||
// This is the same function passed to Http2SecureServer's constructor
|
||||
// and is what normally fires on the 'secureConnection' event.
|
||||
ctx.connectionListener.$call(ctx.server, tlsSocket);
|
||||
|
||||
// Resume the Duplex so the H2 session can read frames from it.
|
||||
// Mirrors net.ts ServerHandlers.handshake line 438: `self.resume()`.
|
||||
tlsSocket.resume();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Close-cleanup handler
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// onTlsClose: when the TLS socket closes (e.g. H2 session destroyed), clean up
|
||||
// the raw socket listeners to prevent memory leaks and stale callback references.
|
||||
// EventEmitter calls 'close' handlers with `this` = emitter (tlsSocket).
|
||||
function onTlsClose(this: TLSProxySocket) {
|
||||
const ctx = this._ctx;
|
||||
const raw = ctx.rawSocket;
|
||||
const ev = ctx.events;
|
||||
if (!ev) return;
|
||||
raw.removeListener("data", ev[0]);
|
||||
raw.removeListener("end", ev[1]);
|
||||
raw.removeListener("drain", ev[2]);
|
||||
raw.removeListener("close", ev[3]);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Module-scope noop (replaces anonymous () => {} for the error suppression)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// no-op handler used to suppress unhandled error events until
|
||||
// the H2 session attaches its own error handler.
|
||||
function noop() {}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main upgrade function
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Upgrades a raw TCP socket to TLS and initiates an H2 session on it.
|
||||
//
|
||||
// When a net.Server forwards an accepted TCP connection to an Http2SecureServer
|
||||
// via `h2Server.emit('connection', socket)`, the socket has not been TLS-upgraded.
|
||||
// Node.js Http2SecureServer expects to receive this and perform the upgrade itself.
|
||||
//
|
||||
// This mirrors the TLS server handshake pattern from net.ts ServerHandlers, but
|
||||
// targets the H2 connectionListener instead of a generic secureConnection event.
|
||||
//
|
||||
// Data flow after upgrade:
|
||||
// rawSocket (TCP) → upgradeDuplexToTLS (Zig TLS layer) → socket callbacks
|
||||
// → tlsSocket.push() → H2 session reads
|
||||
// H2 session writes → tlsSocket._write() → handle.$write() → Zig TLS layer → rawSocket
|
||||
//
|
||||
// CRITICAL: We do NOT set tlsSocket._handle to the native TLS handle.
|
||||
// If we did, the H2FrameParser constructor would detect it as a JSTLSSocket
|
||||
// and call attachNativeCallback(), which intercepts all decrypted data at the
|
||||
// Zig level, completely bypassing our JS data callback and Duplex.push() path.
|
||||
// Instead, we store the handle in _ctx.nativeHandle so _read/_write/_destroy
|
||||
// can use it, while the H2 session sees _handle as null and uses the JS-level
|
||||
// socket.on("data") → Duplex → parser.read() path for incoming frames.
|
||||
function upgradeRawSocketToH2(
|
||||
connectionListener: (...args: any[]) => any,
|
||||
server: Http2SecureServer,
|
||||
rawSocket: import("node:net").Socket,
|
||||
): boolean {
|
||||
// Create a Duplex stream that acts as the TLS "socket" from the H2 session's perspective.
|
||||
const tlsSocket = new Duplex() as unknown as TLSProxySocket;
|
||||
tlsSocket._ctx = new UpgradeContext(connectionListener, server, rawSocket);
|
||||
|
||||
// Duplex stream methods — `this` is tlsSocket, no bind needed
|
||||
tlsSocket._read = tlsSocketRead;
|
||||
tlsSocket._write = tlsSocketWrite;
|
||||
tlsSocket._destroy = tlsSocketDestroy;
|
||||
tlsSocket._final = tlsSocketFinal;
|
||||
|
||||
// Suppress unhandled error events until the H2 session attaches its own error handler
|
||||
tlsSocket.on("error", noop);
|
||||
|
||||
// Set TLS-like properties that connectionListener and the H2 session expect.
|
||||
// These are set on the Duplex because we cannot use a real TLSSocket here —
|
||||
// its internal state machine would conflict with upgradeDuplexToTLS.
|
||||
tlsSocket.alpnProtocol = null;
|
||||
tlsSocket.authorized = false;
|
||||
tlsSocket.encrypted = true;
|
||||
tlsSocket.server = server;
|
||||
|
||||
// Only enforce client cert verification if the server explicitly requests it.
|
||||
// tls.Server defaults _rejectUnauthorized to true, but without _requestCert
|
||||
// the server doesn't actually ask for a client cert, so verification errors
|
||||
// (e.g. UNABLE_TO_GET_ISSUER_CERT for the server's own self-signed cert) are
|
||||
// spurious and must be ignored.
|
||||
tlsSocket._requestCert = server._requestCert || false;
|
||||
tlsSocket._rejectUnauthorized = server._requestCert ? server._rejectUnauthorized : false;
|
||||
|
||||
// socket: callbacks — bind to tlsSocket since Zig calls them with native handle as `this`
|
||||
let handle: NativeHandle, events: UpgradeContextType["events"];
|
||||
try {
|
||||
// upgradeDuplexToTLS wraps rawSocket with a TLS layer in server mode (isServer: true).
|
||||
// The Zig side will:
|
||||
// 1. Read encrypted data from rawSocket via events[0..3]
|
||||
// 2. Decrypt it through the TLS engine (with ALPN negotiation for "h2")
|
||||
// 3. Call our socket callbacks below with the decrypted plaintext
|
||||
//
|
||||
// ALPNProtocols: server.ALPNProtocols is a Buffer in wire format (e.g. <Buffer 02 68 32>
|
||||
// for ["h2"]). The Zig SSLConfig expects an ArrayBuffer, so we slice the underlying buffer.
|
||||
[handle, events] = upgradeDuplexToTLS(rawSocket, {
|
||||
isServer: true,
|
||||
tls: {
|
||||
key: server.key,
|
||||
cert: server.cert,
|
||||
ca: server.ca,
|
||||
passphrase: server.passphrase,
|
||||
ALPNProtocols: server.ALPNProtocols
|
||||
? server.ALPNProtocols.buffer.slice(
|
||||
server.ALPNProtocols.byteOffset,
|
||||
server.ALPNProtocols.byteOffset + server.ALPNProtocols.byteLength,
|
||||
)
|
||||
: null,
|
||||
},
|
||||
socket: {
|
||||
open: socketOpen,
|
||||
data: socketData.bind(tlsSocket),
|
||||
end: socketEnd.bind(tlsSocket),
|
||||
drain: socketDrain.bind(tlsSocket),
|
||||
close: socketClose.bind(tlsSocket),
|
||||
error: socketError.bind(tlsSocket),
|
||||
timeout: socketTimeout.bind(tlsSocket),
|
||||
handshake: socketHandshake.bind(tlsSocket),
|
||||
},
|
||||
data: {},
|
||||
});
|
||||
} catch (e) {
|
||||
rawSocket.destroy(e as Error);
|
||||
tlsSocket.destroy(e as Error);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Store handle in _ctx (NOT on tlsSocket._handle).
|
||||
// This prevents H2FrameParser from attaching as native callback which would
|
||||
// intercept data at the Zig level and bypass our Duplex push path.
|
||||
tlsSocket._ctx.nativeHandle = handle;
|
||||
tlsSocket._ctx.events = events;
|
||||
|
||||
// Wire up the raw TCP socket to feed encrypted data into the TLS layer.
|
||||
// events[0..3] are native event handlers returned by upgradeDuplexToTLS that
|
||||
// the Zig TLS engine expects to receive data/end/drain/close through.
|
||||
rawSocket.on("data", events[0]);
|
||||
rawSocket.on("end", events[1]);
|
||||
rawSocket.on("drain", events[2]);
|
||||
rawSocket.on("close", events[3]);
|
||||
|
||||
// When the TLS socket closes (e.g. H2 session destroyed), clean up the raw socket
|
||||
// listeners to prevent memory leaks and stale callback references.
|
||||
// EventEmitter calls 'close' handlers with `this` = emitter (tlsSocket).
|
||||
tlsSocket.once("close", onTlsClose);
|
||||
return true;
|
||||
}
|
||||
|
||||
export default { upgradeRawSocketToH2 };
|
||||
@@ -73,7 +73,6 @@ const H2FrameParser = $zig("h2_frame_parser.zig", "H2FrameParserConstructor");
|
||||
const assertSettings = $newZigFunction("h2_frame_parser.zig", "jsAssertSettings", 1);
|
||||
const getPackedSettings = $newZigFunction("h2_frame_parser.zig", "jsGetPackedSettings", 1);
|
||||
const getUnpackedSettings = $newZigFunction("h2_frame_parser.zig", "jsGetUnpackedSettings", 1);
|
||||
const { upgradeRawSocketToH2 } = require("node:_http2_upgrade");
|
||||
|
||||
const sensitiveHeaders = Symbol.for("nodejs.http2.sensitiveHeaders");
|
||||
const bunHTTP2Native = Symbol.for("::bunhttp2native::");
|
||||
@@ -3882,7 +3881,6 @@ Http2Server.prototype[EventEmitter.captureRejectionSymbol] = function (err, even
|
||||
function onErrorSecureServerSession(err, socket) {
|
||||
if (!this.emit("clientError", err, socket)) socket.destroy(err);
|
||||
}
|
||||
|
||||
function emitFrameErrorEventNT(stream, frameType, errorCode) {
|
||||
stream.emit("frameError", frameType, errorCode);
|
||||
}
|
||||
@@ -3920,15 +3918,6 @@ class Http2SecureServer extends tls.Server {
|
||||
}
|
||||
this.on("tlsClientError", onErrorSecureServerSession);
|
||||
}
|
||||
emit(event: string, ...args: any[]) {
|
||||
if (event === "connection") {
|
||||
const socket = args[0];
|
||||
if (socket && !(socket instanceof TLSSocket)) {
|
||||
return upgradeRawSocketToH2(connectionListener, this, socket);
|
||||
}
|
||||
}
|
||||
return super.emit(event, ...args);
|
||||
}
|
||||
setTimeout(ms, callback) {
|
||||
this.timeout = ms;
|
||||
if (typeof callback === "function") {
|
||||
|
||||
@@ -486,27 +486,22 @@ pub const HtmlRenderer = struct {
|
||||
}
|
||||
|
||||
pub fn writeHtmlEscaped(self: *HtmlRenderer, txt: []const u8) void {
|
||||
var i: usize = 0;
|
||||
const needle = "&<>\"";
|
||||
|
||||
while (true) {
|
||||
const next = bun.strings.indexOfAny(txt[i..], needle) orelse {
|
||||
self.write(txt[i..]);
|
||||
return;
|
||||
var start: usize = 0;
|
||||
for (txt, 0..) |c, i| {
|
||||
const replacement: ?[]const u8 = switch (c) {
|
||||
'&' => "&",
|
||||
'<' => "<",
|
||||
'>' => ">",
|
||||
'"' => """,
|
||||
else => null,
|
||||
};
|
||||
const pos = i + next;
|
||||
if (pos > i)
|
||||
self.write(txt[i..pos]);
|
||||
const c = txt[pos];
|
||||
switch (c) {
|
||||
'&' => self.write("&"),
|
||||
'<' => self.write("<"),
|
||||
'>' => self.write(">"),
|
||||
'"' => self.write("""),
|
||||
else => unreachable,
|
||||
if (replacement) |r| {
|
||||
if (i > start) self.write(txt[start..i]);
|
||||
self.write(r);
|
||||
start = i + 1;
|
||||
}
|
||||
i = pos + 1;
|
||||
}
|
||||
if (start < txt.len) self.write(txt[start..]);
|
||||
}
|
||||
|
||||
fn writeUrlEscaped(self: *HtmlRenderer, txt: []const u8) void {
|
||||
|
||||
@@ -70,13 +70,11 @@ pub fn isZeroWidthCodepointType(comptime T: type, cp: T) bool {
|
||||
}
|
||||
|
||||
// Thai combining marks
|
||||
// Note: U+0E32 (SARA AA) and U+0E33 (SARA AM) are Grapheme_Base (spacing vowels), not combining
|
||||
if (cp == 0xe31 or (cp >= 0xe34 and cp <= 0xe3a) or (cp >= 0xe47 and cp <= 0xe4e))
|
||||
if ((cp >= 0xe31 and cp <= 0xe3a) or (cp >= 0xe47 and cp <= 0xe4e))
|
||||
return true;
|
||||
|
||||
// Lao combining marks
|
||||
// Note: U+0EB2 and U+0EB3 are spacing vowels like Thai, not combining
|
||||
if (cp == 0xeb1 or (cp >= 0xeb4 and cp <= 0xebc) or (cp >= 0xec8 and cp <= 0xecd))
|
||||
if ((cp >= 0xeb1 and cp <= 0xebc) or (cp >= 0xec8 and cp <= 0xecd))
|
||||
return true;
|
||||
|
||||
// Combining Diacritical Marks Extended
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
Bun.hash.wyhash("asdf", 1234n);
|
||||
|
||||
// https://github.com/oven-sh/bun/issues/26043
|
||||
// Bun.hash.crc32 accepts optional seed parameter for incremental CRC32 computation
|
||||
let crc = 0;
|
||||
crc = Bun.hash.crc32(new Uint8Array([1, 2, 3]), crc);
|
||||
crc = Bun.hash.crc32(new Uint8Array([4, 5, 6]), crc);
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { expect, mock, spyOn, test } from "bun:test";
|
||||
|
||||
test("spyOn returns a disposable that calls mockRestore", () => {
|
||||
const obj = { method: () => "original" };
|
||||
|
||||
{
|
||||
using spy = spyOn(obj, "method").mockReturnValue("mocked");
|
||||
expect(obj.method()).toBe("mocked");
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
}
|
||||
|
||||
expect(obj.method()).toBe("original");
|
||||
});
|
||||
|
||||
test("mock() returns a disposable that calls mockRestore", () => {
|
||||
const fn = mock(() => "original");
|
||||
|
||||
fn();
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
expect(fn[Symbol.dispose]).toBeFunction();
|
||||
fn[Symbol.dispose]();
|
||||
expect(fn).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("using with spyOn auto-restores prototype methods", () => {
|
||||
class Greeter {
|
||||
greet() {
|
||||
return "hello";
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
using spy = spyOn(Greeter.prototype, "greet").mockReturnValue("hola");
|
||||
expect(new Greeter().greet()).toBe("hola");
|
||||
}
|
||||
|
||||
expect(new Greeter().greet()).toBe("hello");
|
||||
});
|
||||
@@ -485,28 +485,6 @@ describe("stringWidth extended", () => {
|
||||
expect(Bun.stringWidth("ก็")).toBe(1); // With maitaikhu
|
||||
expect(Bun.stringWidth("ปฏัก")).toBe(3); // ป + ฏ + ั (combining) + ก = 3 visible
|
||||
});
|
||||
|
||||
test("Thai spacing vowels (SARA AA and SARA AM)", () => {
|
||||
// U+0E32 (SARA AA) and U+0E33 (SARA AM) are spacing vowels, not combining marks
|
||||
expect(Bun.stringWidth("\u0E32")).toBe(1); // SARA AA alone
|
||||
expect(Bun.stringWidth("\u0E33")).toBe(1); // SARA AM alone
|
||||
expect(Bun.stringWidth("ก\u0E32")).toBe(2); // ก + SARA AA
|
||||
expect(Bun.stringWidth("ก\u0E33")).toBe(2); // กำ (KO KAI + SARA AM)
|
||||
expect(Bun.stringWidth("คำ")).toBe(2); // Common Thai word
|
||||
expect(Bun.stringWidth("ทำ")).toBe(2); // Common Thai word
|
||||
// True combining marks should still be zero-width
|
||||
expect(Bun.stringWidth("\u0E31")).toBe(0); // MAI HAN-AKAT (combining)
|
||||
expect(Bun.stringWidth("ก\u0E31")).toBe(1); // กั
|
||||
});
|
||||
|
||||
test("Lao spacing vowels", () => {
|
||||
// U+0EB2 and U+0EB3 are spacing vowels in Lao, similar to Thai
|
||||
expect(Bun.stringWidth("\u0EB2")).toBe(1); // LAO VOWEL SIGN AA
|
||||
expect(Bun.stringWidth("\u0EB3")).toBe(1); // LAO VOWEL SIGN AM
|
||||
expect(Bun.stringWidth("ກ\u0EB2")).toBe(2); // KO + AA
|
||||
// True combining marks should still be zero-width
|
||||
expect(Bun.stringWidth("\u0EB1")).toBe(0); // MAI KAN (combining)
|
||||
});
|
||||
});
|
||||
|
||||
describe("non-ASCII in escape sequences and Indic script handling", () => {
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
/**
|
||||
* All tests in this file should also run in Node.js.
|
||||
*
|
||||
* Do not add any tests that only run in Bun.
|
||||
*/
|
||||
|
||||
import { describe, test } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { Agent, createServer, request as httpRequest } from "node:http";
|
||||
import type { AddressInfo } from "node:net";
|
||||
|
||||
// Helper to make a request and get the response.
|
||||
// Uses a shared agent so that all requests go through the same TCP connection,
|
||||
// which is critical for actually testing the keep-alive / proxy-URL bug.
|
||||
function makeRequest(
|
||||
port: number,
|
||||
path: string,
|
||||
agent: Agent,
|
||||
): Promise<{ statusCode: number; body: string; url: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = httpRequest({ host: "127.0.0.1", port, path, method: "GET", agent }, res => {
|
||||
let body = "";
|
||||
res.on("data", chunk => {
|
||||
body += chunk;
|
||||
});
|
||||
res.on("end", () => {
|
||||
resolve({ statusCode: res.statusCode!, body, url: path });
|
||||
});
|
||||
});
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function listenOnRandomPort(server: ReturnType<typeof createServer>): Promise<number> {
|
||||
return new Promise((resolve) => {
|
||||
server.listen(0, "127.0.0.1", () => {
|
||||
const addr = server.address() as AddressInfo;
|
||||
resolve(addr.port);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("HTTP server with proxy-style absolute URLs", () => {
|
||||
test("sequential GET requests with absolute URL paths don't hang", async () => {
|
||||
const agent = new Agent({ keepAlive: true, maxSockets: 1 });
|
||||
const server = createServer((req, res) => {
|
||||
res.writeHead(200, { "Content-Type": "text/plain" });
|
||||
res.end(req.url);
|
||||
});
|
||||
|
||||
const port = await listenOnRandomPort(server);
|
||||
|
||||
try {
|
||||
// Make 3 sequential requests with proxy-style absolute URLs
|
||||
// Before the fix, request 2 would hang because the parser entered tunnel mode
|
||||
const r1 = await makeRequest(port, "http://example.com/test1", agent);
|
||||
assert.strictEqual(r1.statusCode, 200);
|
||||
assert.ok(r1.body.includes("example.com"), `Expected body to contain "example.com", got: ${r1.body}`);
|
||||
assert.ok(r1.body.includes("/test1"), `Expected body to contain "/test1", got: ${r1.body}`);
|
||||
|
||||
const r2 = await makeRequest(port, "http://example.com/test2", agent);
|
||||
assert.strictEqual(r2.statusCode, 200);
|
||||
assert.ok(r2.body.includes("example.com"), `Expected body to contain "example.com", got: ${r2.body}`);
|
||||
assert.ok(r2.body.includes("/test2"), `Expected body to contain "/test2", got: ${r2.body}`);
|
||||
|
||||
const r3 = await makeRequest(port, "http://other.com/test3", agent);
|
||||
assert.strictEqual(r3.statusCode, 200);
|
||||
assert.ok(r3.body.includes("other.com"), `Expected body to contain "other.com", got: ${r3.body}`);
|
||||
assert.ok(r3.body.includes("/test3"), `Expected body to contain "/test3", got: ${r3.body}`);
|
||||
} finally {
|
||||
agent.destroy();
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("sequential POST requests with absolute URL paths don't hang", async () => {
|
||||
const agent = new Agent({ keepAlive: true, maxSockets: 1 });
|
||||
const server = createServer((req, res) => {
|
||||
let body = "";
|
||||
req.on("data", chunk => {
|
||||
body += chunk;
|
||||
});
|
||||
req.on("end", () => {
|
||||
res.writeHead(200, { "Content-Type": "text/plain" });
|
||||
res.end(`${req.method} ${req.url} body=${body}`);
|
||||
});
|
||||
});
|
||||
|
||||
const port = await listenOnRandomPort(server);
|
||||
|
||||
try {
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
const result = await new Promise<{ statusCode: number; body: string }>((resolve, reject) => {
|
||||
const req = httpRequest(
|
||||
{
|
||||
host: "127.0.0.1",
|
||||
port,
|
||||
path: `http://example.com/post${i}`,
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "text/plain" },
|
||||
agent,
|
||||
},
|
||||
res => {
|
||||
let body = "";
|
||||
res.on("data", chunk => {
|
||||
body += chunk;
|
||||
});
|
||||
res.on("end", () => {
|
||||
resolve({ statusCode: res.statusCode!, body });
|
||||
});
|
||||
},
|
||||
);
|
||||
req.on("error", reject);
|
||||
req.write(`data${i}`);
|
||||
req.end();
|
||||
});
|
||||
assert.strictEqual(result.statusCode, 200);
|
||||
assert.ok(result.body.includes(`/post${i}`), `Expected body to contain "/post${i}", got: ${result.body}`);
|
||||
assert.ok(result.body.includes(`body=data${i}`), `Expected body to contain "body=data${i}", got: ${result.body}`);
|
||||
}
|
||||
} finally {
|
||||
agent.destroy();
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("mixed normal and proxy-style URLs work sequentially", async () => {
|
||||
const agent = new Agent({ keepAlive: true, maxSockets: 1 });
|
||||
const server = createServer((req, res) => {
|
||||
res.writeHead(200, { "Content-Type": "text/plain" });
|
||||
res.end(req.url);
|
||||
});
|
||||
|
||||
const port = await listenOnRandomPort(server);
|
||||
|
||||
try {
|
||||
// Mix of normal and proxy-style URLs
|
||||
const r1 = await makeRequest(port, "/normal1", agent);
|
||||
assert.strictEqual(r1.statusCode, 200);
|
||||
assert.ok(r1.body.includes("/normal1"), `Expected body to contain "/normal1", got: ${r1.body}`);
|
||||
|
||||
const r2 = await makeRequest(port, "http://example.com/proxy1", agent);
|
||||
assert.strictEqual(r2.statusCode, 200);
|
||||
assert.ok(r2.body.includes("example.com"), `Expected body to contain "example.com", got: ${r2.body}`);
|
||||
assert.ok(r2.body.includes("/proxy1"), `Expected body to contain "/proxy1", got: ${r2.body}`);
|
||||
|
||||
const r3 = await makeRequest(port, "/normal2", agent);
|
||||
assert.strictEqual(r3.statusCode, 200);
|
||||
assert.ok(r3.body.includes("/normal2"), `Expected body to contain "/normal2", got: ${r3.body}`);
|
||||
|
||||
const r4 = await makeRequest(port, "http://other.com/proxy2", agent);
|
||||
assert.strictEqual(r4.statusCode, 200);
|
||||
assert.ok(r4.body.includes("other.com"), `Expected body to contain "other.com", got: ${r4.body}`);
|
||||
assert.ok(r4.body.includes("/proxy2"), `Expected body to contain "/proxy2", got: ${r4.body}`);
|
||||
} finally {
|
||||
agent.destroy();
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, nodeExe } from "harness";
|
||||
import { join } from "node:path";
|
||||
|
||||
describe("HTTP server with proxy-style absolute URLs", () => {
|
||||
test("tests should run on node.js", async () => {
|
||||
await using process = Bun.spawn({
|
||||
cmd: [nodeExe(), "--test", join(import.meta.dir, "node-http-proxy-url.node.mts")],
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
stdin: "ignore",
|
||||
env: bunEnv,
|
||||
});
|
||||
expect(await process.exited).toBe(0);
|
||||
});
|
||||
test("tests should run on bun", async () => {
|
||||
await using process = Bun.spawn({
|
||||
cmd: [bunExe(), "test", join(import.meta.dir, "node-http-proxy-url.node.mts")],
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
stdin: "ignore",
|
||||
env: bunEnv,
|
||||
});
|
||||
expect(await process.exited).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -1,428 +0,0 @@
|
||||
/**
|
||||
* Tests for the net.Server → Http2SecureServer upgrade path
|
||||
* (upgradeRawSocketToH2 in _http2_upgrade.ts).
|
||||
*
|
||||
* This pattern is used by http2-wrapper, crawlee, and other libraries that
|
||||
* accept raw TCP connections and upgrade them to HTTP/2 via
|
||||
* `h2Server.emit('connection', rawSocket)`.
|
||||
*
|
||||
* Works with both:
|
||||
* bun bd test test/js/node/http2/node-http2-upgrade.test.ts
|
||||
* node --experimental-strip-types --test test/js/node/http2/node-http2-upgrade.test.ts
|
||||
*/
|
||||
import assert from "node:assert";
|
||||
import fs from "node:fs";
|
||||
import http2 from "node:http2";
|
||||
import net from "node:net";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, test } from "node:test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const FIXTURES_PATH = path.join(__dirname, "..", "test", "fixtures", "keys");
|
||||
|
||||
const TLS = {
|
||||
key: fs.readFileSync(path.join(FIXTURES_PATH, "agent1-key.pem")),
|
||||
cert: fs.readFileSync(path.join(FIXTURES_PATH, "agent1-cert.pem")),
|
||||
ALPNProtocols: ["h2"],
|
||||
};
|
||||
|
||||
function createUpgradeServer(
|
||||
handler: (req: http2.Http2ServerRequest, res: http2.Http2ServerResponse) => void,
|
||||
opts: { onSession?: (session: http2.Http2Session) => void } = {},
|
||||
): Promise<{ netServer: net.Server; h2Server: http2.Http2SecureServer; port: number }> {
|
||||
return new Promise(resolve => {
|
||||
const h2Server = http2.createSecureServer(TLS, handler);
|
||||
h2Server.on("error", () => {});
|
||||
if (opts.onSession) h2Server.on("session", opts.onSession);
|
||||
|
||||
const netServer = net.createServer(socket => {
|
||||
h2Server.emit("connection", socket);
|
||||
});
|
||||
|
||||
netServer.listen(0, "127.0.0.1", () => {
|
||||
resolve({ netServer, h2Server, port: (netServer.address() as net.AddressInfo).port });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function connectClient(port: number): http2.ClientHttp2Session {
|
||||
const client = http2.connect(`https://127.0.0.1:${port}`, { rejectUnauthorized: false });
|
||||
client.on("error", () => {});
|
||||
return client;
|
||||
}
|
||||
|
||||
function request(
|
||||
client: http2.ClientHttp2Session,
|
||||
method: string,
|
||||
reqPath: string,
|
||||
body?: string,
|
||||
): Promise<{ status: number; headers: http2.IncomingHttpHeaders; body: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = client.request({ ":method": method, ":path": reqPath });
|
||||
let responseBody = "";
|
||||
let responseHeaders: http2.IncomingHttpHeaders = {};
|
||||
req.on("response", hdrs => {
|
||||
responseHeaders = hdrs;
|
||||
});
|
||||
req.setEncoding("utf8");
|
||||
req.on("data", (chunk: string) => {
|
||||
responseBody += chunk;
|
||||
});
|
||||
req.on("end", () => {
|
||||
resolve({
|
||||
status: responseHeaders[":status"] as unknown as number,
|
||||
headers: responseHeaders,
|
||||
body: responseBody,
|
||||
});
|
||||
});
|
||||
req.on("error", reject);
|
||||
if (body !== undefined) {
|
||||
req.end(body);
|
||||
} else {
|
||||
req.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe("HTTP/2 upgrade via net.Server", () => {
|
||||
let servers: { netServer: net.Server }[] = [];
|
||||
let clients: http2.ClientHttp2Session[] = [];
|
||||
|
||||
afterEach(() => {
|
||||
for (const c of clients) c.close();
|
||||
for (const s of servers) s.netServer.close();
|
||||
clients = [];
|
||||
servers = [];
|
||||
});
|
||||
|
||||
test("GET request succeeds with 200 and custom headers", async () => {
|
||||
const srv = await createUpgradeServer((_req, res) => {
|
||||
res.writeHead(200, { "x-upgrade-test": "yes" });
|
||||
res.end("hello from upgraded server");
|
||||
});
|
||||
servers.push(srv);
|
||||
|
||||
const client = connectClient(srv.port);
|
||||
clients.push(client);
|
||||
|
||||
const result = await request(client, "GET", "/");
|
||||
assert.strictEqual(result.status, 200);
|
||||
assert.strictEqual(result.headers["x-upgrade-test"], "yes");
|
||||
assert.strictEqual(result.body, "hello from upgraded server");
|
||||
});
|
||||
|
||||
test("POST request with body echoed back", async () => {
|
||||
const srv = await createUpgradeServer((_req, res) => {
|
||||
let body = "";
|
||||
_req.on("data", (chunk: string) => {
|
||||
body += chunk;
|
||||
});
|
||||
_req.on("end", () => {
|
||||
res.writeHead(200);
|
||||
res.end("echo:" + body);
|
||||
});
|
||||
});
|
||||
servers.push(srv);
|
||||
|
||||
const client = connectClient(srv.port);
|
||||
clients.push(client);
|
||||
|
||||
const result = await request(client, "POST", "/echo", "test payload");
|
||||
assert.strictEqual(result.status, 200);
|
||||
assert.strictEqual(result.body, "echo:test payload");
|
||||
});
|
||||
});
|
||||
|
||||
describe("HTTP/2 upgrade — multiple requests on one connection", () => {
|
||||
test("three sequential requests share the same session", async () => {
|
||||
let count = 0;
|
||||
const srv = await createUpgradeServer((_req, res) => {
|
||||
count++;
|
||||
res.writeHead(200);
|
||||
res.end(String(count));
|
||||
});
|
||||
|
||||
const client = connectClient(srv.port);
|
||||
|
||||
const r1 = await request(client, "GET", "/");
|
||||
const r2 = await request(client, "GET", "/");
|
||||
const r3 = await request(client, "GET", "/");
|
||||
|
||||
assert.strictEqual(r1.body, "1");
|
||||
assert.strictEqual(r2.body, "2");
|
||||
assert.strictEqual(r3.body, "3");
|
||||
|
||||
client.close();
|
||||
srv.netServer.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe("HTTP/2 upgrade — session event", () => {
|
||||
test("h2Server emits session event", async () => {
|
||||
let sessionFired = false;
|
||||
const srv = await createUpgradeServer(
|
||||
(_req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end("ok");
|
||||
},
|
||||
{
|
||||
onSession: () => {
|
||||
sessionFired = true;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const client = connectClient(srv.port);
|
||||
|
||||
await request(client, "GET", "/");
|
||||
|
||||
assert.strictEqual(sessionFired, true);
|
||||
|
||||
client.close();
|
||||
srv.netServer.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe("HTTP/2 upgrade — concurrent clients", () => {
|
||||
test("two clients get independent sessions", async () => {
|
||||
const srv = await createUpgradeServer((_req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end(_req.url);
|
||||
});
|
||||
|
||||
const c1 = connectClient(srv.port);
|
||||
const c2 = connectClient(srv.port);
|
||||
|
||||
const [r1, r2] = await Promise.all([request(c1, "GET", "/from-client-1"), request(c2, "GET", "/from-client-2")]);
|
||||
|
||||
assert.strictEqual(r1.body, "/from-client-1");
|
||||
assert.strictEqual(r2.body, "/from-client-2");
|
||||
|
||||
c1.close();
|
||||
c2.close();
|
||||
srv.netServer.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe("HTTP/2 upgrade — socket close ordering", () => {
|
||||
test("no crash when rawSocket.destroy() precedes session.close()", async () => {
|
||||
let rawSocket: net.Socket | undefined;
|
||||
let h2Session: http2.Http2Session | undefined;
|
||||
|
||||
const h2Server = http2.createSecureServer(TLS, (_req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end("done");
|
||||
});
|
||||
h2Server.on("error", () => {});
|
||||
h2Server.on("session", s => {
|
||||
h2Session = s;
|
||||
});
|
||||
|
||||
const netServer = net.createServer(socket => {
|
||||
rawSocket = socket;
|
||||
h2Server.emit("connection", socket);
|
||||
});
|
||||
|
||||
const port = await new Promise<number>(resolve => {
|
||||
netServer.listen(0, "127.0.0.1", () => resolve((netServer.address() as net.AddressInfo).port));
|
||||
});
|
||||
|
||||
const client = connectClient(port);
|
||||
await request(client, "GET", "/");
|
||||
|
||||
const socketClosed = Promise.withResolvers<void>();
|
||||
rawSocket!.once("close", () => socketClosed.resolve());
|
||||
rawSocket!.destroy();
|
||||
await socketClosed.promise;
|
||||
if (h2Session) h2Session.close();
|
||||
|
||||
client.close();
|
||||
netServer.close();
|
||||
});
|
||||
|
||||
test("no crash when session.close() precedes rawSocket.destroy()", async () => {
|
||||
let rawSocket: net.Socket | undefined;
|
||||
let h2Session: http2.Http2Session | undefined;
|
||||
|
||||
const h2Server = http2.createSecureServer(TLS, (_req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end("done");
|
||||
});
|
||||
h2Server.on("error", () => {});
|
||||
h2Server.on("session", s => {
|
||||
h2Session = s;
|
||||
});
|
||||
|
||||
const netServer = net.createServer(socket => {
|
||||
rawSocket = socket;
|
||||
h2Server.emit("connection", socket);
|
||||
});
|
||||
|
||||
const port = await new Promise<number>(resolve => {
|
||||
netServer.listen(0, "127.0.0.1", () => resolve((netServer.address() as net.AddressInfo).port));
|
||||
});
|
||||
|
||||
const client = connectClient(port);
|
||||
await request(client, "GET", "/");
|
||||
|
||||
if (h2Session) h2Session.close();
|
||||
const socketClosed = Promise.withResolvers<void>();
|
||||
rawSocket!.once("close", () => socketClosed.resolve());
|
||||
rawSocket!.destroy();
|
||||
await socketClosed.promise;
|
||||
|
||||
client.close();
|
||||
netServer.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe("HTTP/2 upgrade — ALPN negotiation", () => {
|
||||
test("alpnProtocol is h2 after upgrade", async () => {
|
||||
let observedAlpn: string | undefined;
|
||||
const srv = await createUpgradeServer((_req, res) => {
|
||||
const session = _req.stream.session;
|
||||
if (session && session.socket) {
|
||||
observedAlpn = (session.socket as any).alpnProtocol;
|
||||
}
|
||||
res.writeHead(200);
|
||||
res.end("alpn-ok");
|
||||
});
|
||||
|
||||
const client = connectClient(srv.port);
|
||||
await request(client, "GET", "/");
|
||||
|
||||
assert.strictEqual(observedAlpn, "h2");
|
||||
|
||||
client.close();
|
||||
srv.netServer.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe("HTTP/2 upgrade — varied status codes", () => {
|
||||
test("404 response with custom header", async () => {
|
||||
const srv = await createUpgradeServer((_req, res) => {
|
||||
res.writeHead(404, { "x-reason": "not-found" });
|
||||
res.end("not found");
|
||||
});
|
||||
|
||||
const client = connectClient(srv.port);
|
||||
const result = await request(client, "GET", "/missing");
|
||||
|
||||
assert.strictEqual(result.status, 404);
|
||||
assert.strictEqual(result.headers["x-reason"], "not-found");
|
||||
assert.strictEqual(result.body, "not found");
|
||||
|
||||
client.close();
|
||||
srv.netServer.close();
|
||||
});
|
||||
|
||||
test("302 redirect response", async () => {
|
||||
const srv = await createUpgradeServer((_req, res) => {
|
||||
res.writeHead(302, { location: "/" });
|
||||
res.end();
|
||||
});
|
||||
|
||||
const client = connectClient(srv.port);
|
||||
const result = await request(client, "GET", "/redirect");
|
||||
|
||||
assert.strictEqual(result.status, 302);
|
||||
assert.strictEqual(result.headers["location"], "/");
|
||||
|
||||
client.close();
|
||||
srv.netServer.close();
|
||||
});
|
||||
|
||||
test("large response body (8KB) through upgraded socket", async () => {
|
||||
const srv = await createUpgradeServer((_req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end("x".repeat(8192));
|
||||
});
|
||||
|
||||
const client = connectClient(srv.port);
|
||||
const result = await request(client, "GET", "/large");
|
||||
|
||||
assert.strictEqual(result.body.length, 8192);
|
||||
|
||||
client.close();
|
||||
srv.netServer.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe("HTTP/2 upgrade — client disconnect mid-response", () => {
|
||||
test("server does not crash when client destroys stream early", async () => {
|
||||
const streamClosed = Promise.withResolvers<void>();
|
||||
|
||||
const srv = await createUpgradeServer((_req, res) => {
|
||||
res.writeHead(200);
|
||||
const interval = setInterval(() => {
|
||||
if (res.destroyed || res.writableEnded) {
|
||||
clearInterval(interval);
|
||||
return;
|
||||
}
|
||||
res.write("chunk\n");
|
||||
}, 5);
|
||||
_req.stream.on("close", () => {
|
||||
clearInterval(interval);
|
||||
streamClosed.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
const client = connectClient(srv.port);
|
||||
|
||||
const streamReady = Promise.withResolvers<http2.ClientHttp2Stream>();
|
||||
const req = client.request({ ":method": "GET", ":path": "/" });
|
||||
req.on("response", () => streamReady.resolve(req));
|
||||
req.on("error", () => {});
|
||||
|
||||
const stream = await streamReady.promise;
|
||||
stream.destroy();
|
||||
|
||||
await streamClosed.promise;
|
||||
|
||||
client.close();
|
||||
srv.netServer.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe("HTTP/2 upgrade — independent upgrade per connection", () => {
|
||||
test("three clients produce three distinct sessions", async () => {
|
||||
const sessions: http2.Http2Session[] = [];
|
||||
|
||||
const srv = await createUpgradeServer(
|
||||
(_req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end("ok");
|
||||
},
|
||||
{ onSession: s => sessions.push(s) },
|
||||
);
|
||||
|
||||
const c1 = connectClient(srv.port);
|
||||
const c2 = connectClient(srv.port);
|
||||
const c3 = connectClient(srv.port);
|
||||
|
||||
await Promise.all([request(c1, "GET", "/"), request(c2, "GET", "/"), request(c3, "GET", "/")]);
|
||||
|
||||
assert.strictEqual(sessions.length, 3);
|
||||
assert.notStrictEqual(sessions[0], sessions[1]);
|
||||
assert.notStrictEqual(sessions[1], sessions[2]);
|
||||
|
||||
c1.close();
|
||||
c2.close();
|
||||
c3.close();
|
||||
srv.netServer.close();
|
||||
});
|
||||
});
|
||||
if (typeof Bun !== "undefined") {
|
||||
describe("Node.js compatibility", () => {
|
||||
test("tests should run on node.js", async () => {
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [Bun.which("node") || "node", "--test", import.meta.filename],
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
stdin: "ignore",
|
||||
});
|
||||
assert.strictEqual(await proc.exited, 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const fixtures = require('../common/fixtures');
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
const assert = require('assert');
|
||||
const net = require('net');
|
||||
const h2 = require('http2');
|
||||
|
||||
const tlsOptions = {
|
||||
key: fixtures.readKey('agent1-key.pem'),
|
||||
cert: fixtures.readKey('agent1-cert.pem'),
|
||||
ALPNProtocols: ['h2']
|
||||
};
|
||||
|
||||
// Create a net server that upgrades sockets to HTTP/2 manually, handles the
|
||||
// request, and then shuts down via a short socket timeout and a longer H2 session
|
||||
// timeout. This is an unconventional way to shut down a session (the underlying
|
||||
// socket closing first) but it should work - critically, it shouldn't segfault
|
||||
// (as it did until Node v20.5.1).
|
||||
|
||||
let serverRawSocket;
|
||||
let serverH2Session;
|
||||
|
||||
const netServer = net.createServer((socket) => {
|
||||
serverRawSocket = socket;
|
||||
h2Server.emit('connection', socket);
|
||||
});
|
||||
|
||||
const h2Server = h2.createSecureServer(tlsOptions, (req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
});
|
||||
|
||||
h2Server.on('session', (session) => {
|
||||
serverH2Session = session;
|
||||
});
|
||||
|
||||
netServer.listen(0, common.mustCall(() => {
|
||||
const proxyClient = h2.connect(`https://localhost:${netServer.address().port}`, {
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
|
||||
proxyClient.on('error', () => {});
|
||||
proxyClient.on('close', common.mustCall(() => {
|
||||
netServer.close();
|
||||
}));
|
||||
|
||||
const req = proxyClient.request({
|
||||
':method': 'GET',
|
||||
':path': '/'
|
||||
});
|
||||
|
||||
req.on('error', () => {});
|
||||
req.on('response', common.mustCall((response) => {
|
||||
assert.strictEqual(response[':status'], 200);
|
||||
|
||||
// Asynchronously shut down the server's connections after the response,
|
||||
// but not in the order it typically expects:
|
||||
setTimeout(() => {
|
||||
serverRawSocket.destroy();
|
||||
|
||||
setTimeout(() => {
|
||||
serverH2Session.close();
|
||||
}, 10);
|
||||
}, 10);
|
||||
}));
|
||||
}));
|
||||
@@ -1,21 +0,0 @@
|
||||
import { expect, test } from "bun:test";
|
||||
|
||||
test("Response.clone() does not crash when body stream contains SharedArrayBuffer-backed typed array", async () => {
|
||||
const sab = new SharedArrayBuffer(8);
|
||||
const view = new Uint8Array(sab);
|
||||
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(view);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
|
||||
const resp = new Response(stream);
|
||||
const clone = resp.clone();
|
||||
// Reading the cloned body triggers structuredCloneForStream on the chunk.
|
||||
// Before the fix, this would crash with:
|
||||
// ASSERTION FAILED: !result || !result->isShared()
|
||||
// Now it should throw a DataCloneError instead of crashing.
|
||||
expect(async () => await clone.arrayBuffer()).toThrow("cloned");
|
||||
});
|
||||
@@ -20,8 +20,8 @@ export async function build(dir: string) {
|
||||
// so we make it use clang instead
|
||||
...(process.platform == "linux" && isCI
|
||||
? {
|
||||
CC: !isMusl ? "/usr/lib/llvm-21/bin/clang" : "/usr/lib/llvm21/bin/clang",
|
||||
CXX: !isMusl ? "/usr/lib/llvm-21/bin/clang++" : "/usr/lib/llvm21/bin/clang++",
|
||||
CC: !isMusl ? "/usr/lib/llvm-19/bin/clang" : "/usr/lib/llvm19/bin/clang",
|
||||
CXX: !isMusl ? "/usr/lib/llvm-19/bin/clang++" : "/usr/lib/llvm19/bin/clang++",
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe } from "harness";
|
||||
|
||||
// https://github.com/oven-sh/bun/issues/26669
|
||||
// WebSocket client crashes ("Pure virtual function called!") when binaryType = "blob"
|
||||
// and no event listener is attached. The missing incPendingActivityCount() allows the
|
||||
// WebSocket to be GC'd before the postTask callback runs.
|
||||
test("WebSocket with binaryType blob should not crash when GC'd before postTask", async () => {
|
||||
await using server = Bun.serve({
|
||||
port: 0,
|
||||
fetch(req, server) {
|
||||
if (server.upgrade(req)) return undefined;
|
||||
return new Response("Not a websocket");
|
||||
},
|
||||
websocket: {
|
||||
open(ws) {
|
||||
// Send binary data immediately - this triggers didReceiveBinaryData
|
||||
// with the Blob path when client has binaryType = "blob"
|
||||
ws.sendBinary(new Uint8Array(64));
|
||||
ws.sendBinary(new Uint8Array(64));
|
||||
ws.sendBinary(new Uint8Array(64));
|
||||
},
|
||||
message() {},
|
||||
},
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [
|
||||
bunExe(),
|
||||
"-e",
|
||||
`
|
||||
const url = process.argv[1];
|
||||
// Create many short-lived WebSocket objects with blob binaryType and no listeners.
|
||||
// Without the fix, the missing incPendingActivityCount() lets the WebSocket get GC'd
|
||||
// before the postTask callback fires, causing "Pure virtual function called!".
|
||||
async function run() {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const ws = new WebSocket(url);
|
||||
ws.binaryType = "blob";
|
||||
// Intentionally: NO event listeners attached.
|
||||
// This forces the postTask path in didReceiveBinaryData's Blob case.
|
||||
}
|
||||
// Force GC to collect the unreferenced WebSocket objects while postTask
|
||||
// callbacks are still pending.
|
||||
Bun.gc(true);
|
||||
await Bun.sleep(50);
|
||||
Bun.gc(true);
|
||||
await Bun.sleep(50);
|
||||
Bun.gc(true);
|
||||
await Bun.sleep(100);
|
||||
}
|
||||
await run();
|
||||
Bun.gc(true);
|
||||
await Bun.sleep(200);
|
||||
console.log("OK");
|
||||
process.exit(0);
|
||||
`,
|
||||
`ws://localhost:${server.port}`,
|
||||
],
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stdout).toContain("OK");
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
90
test/regression/issue/26752.test.ts
Normal file
90
test/regression/issue/26752.test.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDir } from "harness";
|
||||
import { existsSync } from "node:fs";
|
||||
|
||||
// This test is only applicable on Linux where explicit ld.so invocation is possible
|
||||
const isLinux = process.platform === "linux";
|
||||
|
||||
// Skip the entire test file on non-Linux platforms
|
||||
test.skipIf(!isLinux)("compiled executable works with BUN_SELF_EXE override", async () => {
|
||||
using dir = tempDir("issue-26752", {
|
||||
"hello.js": `console.log("Hello from compiled Bun!");`,
|
||||
});
|
||||
|
||||
// Compile the script into an executable
|
||||
await using compileProc = Bun.spawn({
|
||||
cmd: [bunExe(), "build", "--compile", "hello.js", "--outfile", "hello"],
|
||||
cwd: String(dir),
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [, compileStderr, compileExitCode] = await Promise.all([
|
||||
compileProc.stdout.text(),
|
||||
compileProc.stderr.text(),
|
||||
compileProc.exited,
|
||||
]);
|
||||
|
||||
expect(compileStderr).toBe("");
|
||||
expect(compileExitCode).toBe(0);
|
||||
|
||||
const executablePath = `${dir}/hello`;
|
||||
expect(existsSync(executablePath)).toBe(true);
|
||||
|
||||
// First, verify the executable works directly
|
||||
await using directProc = Bun.spawn({
|
||||
cmd: [executablePath],
|
||||
cwd: String(dir),
|
||||
env: bunEnv,
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [directStdout, directStderr, directExitCode] = await Promise.all([
|
||||
directProc.stdout.text(),
|
||||
directProc.stderr.text(),
|
||||
directProc.exited,
|
||||
]);
|
||||
|
||||
expect(directStdout.trim()).toBe("Hello from compiled Bun!");
|
||||
expect(directStderr).toBe("");
|
||||
expect(directExitCode).toBe(0);
|
||||
|
||||
// Find the dynamic linker (supports both x86_64 and aarch64)
|
||||
const ldPaths = [
|
||||
"/lib64/ld-linux-x86-64.so.2",
|
||||
"/lib/ld-linux-x86-64.so.2",
|
||||
"/lib/ld-linux-aarch64.so.1",
|
||||
"/lib64/ld-linux-aarch64.so.1",
|
||||
];
|
||||
const ldPath = ldPaths.find(p => existsSync(p)) ?? null;
|
||||
|
||||
// Skip the ld.so test if we can't find the linker
|
||||
if (!ldPath) {
|
||||
console.log("Skipping ld.so test: dynamic linker not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Now test with explicit ld.so invocation and BUN_SELF_EXE override
|
||||
await using ldProc = Bun.spawn({
|
||||
cmd: [ldPath, executablePath],
|
||||
cwd: String(dir),
|
||||
env: {
|
||||
...bunEnv,
|
||||
BUN_SELF_EXE: executablePath,
|
||||
},
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
|
||||
const [ldStdout, ldStderr, ldExitCode] = await Promise.all([
|
||||
ldProc.stdout.text(),
|
||||
ldProc.stderr.text(),
|
||||
ldProc.exited,
|
||||
]);
|
||||
|
||||
expect(ldStdout.trim()).toBe("Hello from compiled Bun!");
|
||||
expect(ldStderr).toBe("");
|
||||
expect(ldExitCode).toBe(0);
|
||||
});
|
||||
Reference in New Issue
Block a user