From ff4d08efa6385260c5231ce43bcd4730bcd3c0d2 Mon Sep 17 00:00:00 2001 From: Gary Lysenko Date: Mon, 25 May 2026 18:14:12 +0300 Subject: [PATCH] fix(build): gate V8 nullptr_t compiler workaround --- flake.nix | 1 + scripts/lib/native-modules.sh | 45 +++++++++++++++++ tests/scripts_smoke.sh | 92 +++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) diff --git a/flake.nix b/flake.nix index 5f5735a1..484f8a3b 100644 --- a/flake.nix +++ b/flake.nix @@ -153,6 +153,7 @@ error() { echo "[ERROR] $*" >&2; exit 1; } source ${sourceRoot}/scripts/lib/native-modules.sh patch_better_sqlite3_for_v8_external_pointer_api "$PWD/node_modules/better-sqlite3" + apply_v8_nullptr_t_workaround_if_needed "$TMPDIR/native-nullptr-workaround" node "$PWD/node_modules/@electron/rebuild/lib/cli.js" \ -v ${electronVersion} \ diff --git a/scripts/lib/native-modules.sh b/scripts/lib/native-modules.sh index 675759ab..a2f93002 100644 --- a/scripts/lib/native-modules.sh +++ b/scripts/lib/native-modules.sh @@ -126,6 +126,50 @@ prune_native_module_build_artifacts() { find "$module_dir" -type f -name "*.target.mk" -delete 2>/dev/null || true } +apply_v8_nullptr_t_workaround_if_needed() { + local build_dir="$1" + local probe_source="$build_dir/.v8-nullptr-probe.cc" + local nullptr_fix="$build_dir/.v8-nullptr-fix.h" + local cxx_wrapper="$build_dir/.cxx-v8-nullptr" + local -a cxx_command + + mkdir -p "$build_dir" + + # CXX is conventionally a command plus optional leading arguments, e.g. + # "ccache g++". Preserve that common form when wrapping the compiler. + # shellcheck disable=SC2206 + cxx_command=( ${CXX:-c++} ) + if [ "${#cxx_command[@]}" -eq 0 ]; then + cxx_command=(c++) + fi + + command -v "${cxx_command[0]}" >/dev/null 2>&1 || error "C++ compiler not found: ${cxx_command[0]}" + + cat > "$probe_source" <<'CPP' +#include +nullptr_t x = nullptr; +CPP + + if "${cxx_command[@]}" -x c++ -std=c++20 -fsyntax-only "$probe_source" >/dev/null 2>&1; then + return 0 + fi + + printf '#include \nusing std::nullptr_t;\n' > "$nullptr_fix" + { + printf '#!/bin/bash\n' + printf 'exec' + local arg + for arg in "${cxx_command[@]}"; do + printf ' %q' "$arg" + done + printf ' -include %q "$@"\n' "$nullptr_fix" + } > "$cxx_wrapper" + chmod +x "$cxx_wrapper" + + export CXX="$cxx_wrapper" + info "Applied GCC 16+ nullptr_t compatibility workaround" +} + build_native_modules() { local app_extracted="$1" @@ -168,6 +212,7 @@ build_native_modules() { info "Compiling for Electron v$ELECTRON_VERSION (this takes ~1 min)..." info "Using Electron headers: $ELECTRON_HEADERS_URL" [ -f "$build_dir/node_modules/@electron/rebuild/lib/cli.js" ] || error "electron-rebuild CLI not found in native build toolchain" + apply_v8_nullptr_t_workaround_if_needed "$build_dir" npm_config_disturl="$ELECTRON_HEADERS_URL" \ NPM_CONFIG_DISTURL="$ELECTRON_HEADERS_URL" \ node "$build_dir/node_modules/@electron/rebuild/lib/cli.js" -v "$ELECTRON_VERSION" --force --dist-url "$ELECTRON_HEADERS_URL" 2>&1 >&2 diff --git a/tests/scripts_smoke.sh b/tests/scripts_smoke.sh index da3158de..a829dbce 100755 --- a/tests/scripts_smoke.sh +++ b/tests/scripts_smoke.sh @@ -1652,6 +1652,96 @@ CPP assert_contains "$output_log" "already applied" } +test_v8_nullptr_workaround_skips_when_included_probe_succeeds() { + info "Checking V8 nullptr_t workaround probe stays inactive when not needed" + local workspace="$TMP_DIR/v8-nullptr-workaround-skip" + local fake_bin="$workspace/bin" + local cxx_log="$workspace/cxx.log" + local cxx_state="$workspace/cxx-state.log" + local output_log="$workspace/output.log" + + mkdir -p "$fake_bin" "$workspace/work" + cat > "$fake_bin/c++" <<'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail +printf 'argv:%s\n' "$*" >> "$NATIVE_CXX_LOG" +for arg in "$@"; do + if [ -f "$arg" ]; then + cat "$arg" >> "$NATIVE_CXX_LOG" + fi +done +exit 0 +SCRIPT + chmod +x "$fake_bin/c++" + + ( + CXX="$fake_bin/c++" + NATIVE_CXX_LOG="$cxx_log" + export CXX NATIVE_CXX_LOG + info() { echo "[INFO] $*" >&2; } + warn() { echo "[WARN] $*" >&2; } + error() { echo "[ERROR] $*" >&2; exit 1; } + # shellcheck disable=SC1091 + source "$REPO_DIR/scripts/lib/native-modules.sh" + apply_v8_nullptr_t_workaround_if_needed "$workspace/work" + printf 'CXX=%s\n' "$CXX" > "$cxx_state" + ) > "$output_log" 2>&1 + + assert_contains "$cxx_log" "#include " + assert_contains "$cxx_log" "nullptr_t x = nullptr;" + assert_contains "$cxx_state" "CXX=$fake_bin/c++" + assert_not_contains "$output_log" "Applied GCC 16+ nullptr_t compatibility workaround" +} + +test_v8_nullptr_workaround_wraps_when_included_probe_fails() { + info "Checking V8 nullptr_t workaround wraps CXX only when needed" + local workspace="$TMP_DIR/v8-nullptr-workaround-wrap" + local fake_bin="$workspace/bin" + local cxx_log="$workspace/cxx.log" + local cxx_state="$workspace/cxx-state.log" + local output_log="$workspace/output.log" + + mkdir -p "$fake_bin" "$workspace/work" + cat > "$fake_bin/c++" <<'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail +printf 'argv:%s\n' "$*" >> "$NATIVE_CXX_LOG" +for arg in "$@"; do + if [ -f "$arg" ]; then + cat "$arg" >> "$NATIVE_CXX_LOG" + fi + case "$arg" in + *.v8-nullptr-probe.cc) exit 1 ;; + esac +done +exit 0 +SCRIPT + chmod +x "$fake_bin/c++" + printf '%s\n' 'int main() { return 0; }' > "$workspace/dummy.cc" + + ( + CXX="$fake_bin/c++" + NATIVE_CXX_LOG="$cxx_log" + export CXX NATIVE_CXX_LOG + info() { echo "[INFO] $*" >&2; } + warn() { echo "[WARN] $*" >&2; } + error() { echo "[ERROR] $*" >&2; exit 1; } + # shellcheck disable=SC1091 + source "$REPO_DIR/scripts/lib/native-modules.sh" + apply_v8_nullptr_t_workaround_if_needed "$workspace/work" + "$CXX" -x c++ -fsyntax-only "$workspace/dummy.cc" + printf 'CXX=%s\n' "$CXX" > "$cxx_state" + ) > "$output_log" 2>&1 + + assert_file_exists "$workspace/work/.v8-nullptr-fix.h" + assert_file_exists "$workspace/work/.cxx-v8-nullptr" + assert_contains "$workspace/work/.v8-nullptr-fix.h" "using std::nullptr_t;" + assert_contains "$cxx_state" "CXX=$workspace/work/.cxx-v8-nullptr" + assert_contains "$cxx_log" "-include" + assert_contains "$cxx_log" ".v8-nullptr-fix.h" + assert_contains "$output_log" "Applied GCC 16+ nullptr_t compatibility workaround" +} + test_native_module_rebuild_uses_local_electron_rebuild_toolchain() { info "Checking native module rebuild uses local Electron rebuild toolchain" local workspace="$TMP_DIR/native-module-rebuild-toolchain" @@ -4700,6 +4790,8 @@ main() { test_port_validation_rejects_oversized_numeric_values test_managed_node_runtime_source_install test_better_sqlite3_electron_42_source_patch + test_v8_nullptr_workaround_skips_when_included_probe_succeeds + test_v8_nullptr_workaround_wraps_when_included_probe_fails test_native_module_rebuild_uses_local_electron_rebuild_toolchain test_native_module_rebuild_accepts_prebuilt_source test_bundled_plugin_builders_accept_prebuilt_binaries