mirror of
https://github.com/NeuralNomadsAI/CodeNomad.git
synced 2026-10-20 19:44:34 +00:00
Backport upstream tao#1215 unchanged onto Tao 0.34.6 to correct the same-thread native input mutex deadlock identified in a symbolized Windows hang dump. Preserve the existing Tauri dependency/API line, all non-Windows sources, licensing and exact published-crate/patch provenance through a workspace Cargo override. Add real isolated Win32 reentry regressions for keydown, keyup, character, system-character and IME input. Original-crate controls reproduce bounded deadlocks; the backport passes all five cases. Independently cover the IME lock boundary with temporary-source instrumentation and require an IME-only lock-order mutation to fail, preventing keyboard pre-peeking from masking an IME regression. Independent gatekeeper pass 1 identified the IME coverage gap; pass 2 on 462f9eac reports zero actionable findings and independently reran all controls. Full local Tauri tests passed 181/181. Administrator squash merge requested by the user; remaining CI jobs were still running at merge.
93 lines
5.3 KiB
JavaScript
93 lines
5.3 KiB
JavaScript
// Real Win32/Tao input reentrancy. No CodeNomad profile, backend or shared daemon.
|
|
import assert from "node:assert/strict"
|
|
import { spawnSync } from "node:child_process"
|
|
import { mkdtemp, mkdir, readFile, writeFile, cp, rm } from "node:fs/promises"
|
|
import os from "node:os"
|
|
import path from "node:path"
|
|
import { fileURLToPath } from "node:url"
|
|
|
|
assert.equal(process.platform, "win32", "Run this native regression on Windows")
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
|
|
const workspace = path.join(root, "packages/tauri-app")
|
|
const fixture = path.join(workspace, "tests/windows-input")
|
|
const target = path.resolve(process.env.CARGO_TARGET_DIR || path.join(workspace, "target"))
|
|
const env = { ...process.env, CARGO_TARGET_DIR: target }
|
|
delete env.NODE_OPTIONS
|
|
delete env.CODENOMAD_NATIVE_PARENT
|
|
function cargo(args, cwd = workspace) {
|
|
const result = spawnSync("cargo", args, { cwd, env, encoding: "utf8", timeout: 300_000, maxBuffer: 4 * 1024 * 1024, windowsHide: true })
|
|
assert.equal(result.error, undefined, String(result.error))
|
|
assert.equal(result.status, 0, result.stdout + result.stderr)
|
|
return result.stdout
|
|
}
|
|
const binary = path.join(target, "debug/codenomad-windows-input-fixture.exe")
|
|
const cases = ["keydown", "keyup", "char", "syschar", "ime"]
|
|
function check(expected, scenarios = cases) {
|
|
for (const scenario of scenarios) {
|
|
const result = spawnSync(binary, [scenario], { cwd: fixture, env, encoding: "utf8", timeout: 15_000, windowsHide: true })
|
|
assert.equal(result.error, undefined, `${scenario}: ${result.error}`)
|
|
assert.match(result.stdout, /nested focus queued before Tao input processing/, `${scenario}: ${result.stdout}${result.stderr}`)
|
|
assert.equal(result.status, expected, `${scenario}: ${result.stdout}${result.stderr}`)
|
|
if (expected === 0) assert.match(result.stdout, /PASS:/)
|
|
else assert.match(result.stderr, /input callback watchdog/)
|
|
console.log(`${expected === 0 ? "fixed PASS" : "negative control deadlock reproduced"}: ${scenario}`)
|
|
}
|
|
}
|
|
|
|
// Optional negative control downloads the original published crate through Cargo.
|
|
// Build it in a separate temporary workspace so the production patch is untouched.
|
|
if (process.argv.includes("--baseline")) {
|
|
const temporaryRoot = path.join(os.tmpdir(), "opencode")
|
|
await mkdir(temporaryRoot, { recursive: true })
|
|
const baseline = await mkdtemp(path.join(temporaryRoot, "tao-input-baseline-"))
|
|
try {
|
|
await cp(path.join(fixture, "src"), path.join(baseline, "src"), { recursive: true })
|
|
await writeFile(path.join(baseline, "Cargo.toml"), (await readFile(path.join(fixture, "Cargo.toml"), "utf8")) + "\n[workspace]\n")
|
|
cargo(["build", "--manifest-path", path.join(baseline, "Cargo.toml")], baseline)
|
|
check(2)
|
|
} finally { await rm(baseline, { recursive: true, force: true }) }
|
|
}
|
|
|
|
const metadata = JSON.parse(cargo(["metadata", "--locked", "--format-version", "1"]))
|
|
const tao = metadata.packages.filter(item => item.name === "tao")
|
|
assert.equal(tao.length, 1, "Tauri and the regression must share one Tao implementation")
|
|
assert.equal(path.resolve(tao[0].manifest_path), path.join(workspace, "vendor/tao-0.34.6/Cargo.toml"))
|
|
cargo(["build", "--locked", "-p", "codenomad-windows-input-fixture"])
|
|
check(0)
|
|
|
|
// Queue at the IME boundary itself: the keyboard pre-peek must not consume the
|
|
// sent focus message first. Instrument ONLY a temporary copy, preserving every
|
|
// production statement and the checked-in upstream source byte-for-byte.
|
|
const temporaryRoot = path.join(os.tmpdir(), "opencode")
|
|
await mkdir(temporaryRoot, { recursive: true })
|
|
const imeRoot = await mkdtemp(path.join(temporaryRoot, "tao-ime-boundary-"))
|
|
function replaceOnce(source, before, after) {
|
|
assert.equal(source.split(before).length, 2, `Expected one instrumentation boundary: ${before}`)
|
|
return source.replace(before, after)
|
|
}
|
|
try {
|
|
await cp(path.join(fixture, "src"), path.join(imeRoot, "src"), { recursive: true })
|
|
await cp(path.join(workspace, "vendor/tao-0.34.6"), path.join(imeRoot, "tao"), { recursive: true })
|
|
await writeFile(path.join(imeRoot, "Cargo.toml"),
|
|
(await readFile(path.join(fixture, "Cargo.toml"), "utf8")) + '\n[workspace]\n[patch.crates-io]\ntao = { path = "tao" }\n')
|
|
const eventLoop = path.join(imeRoot, "tao/src/platform_impl/windows/event_loop.rs")
|
|
const original = (await readFile(eventLoop, "utf8")).replaceAll("\r\n", "\n")
|
|
const instrumented = replaceOnce(original, " let ime_callback = || {", ` let ime_callback = || {
|
|
if matches!(msg, WM_CHAR | WM_SYSCHAR) {
|
|
unsafe { SendMessageW(window, WM_APP + 0x434, None, None); }
|
|
}`)
|
|
await writeFile(eventLoop, instrumented)
|
|
cargo(["build", "--manifest-path", path.join(imeRoot, "Cargo.toml")], imeRoot)
|
|
check(0, ["ime-boundary"])
|
|
|
|
// Mutation control: only move the real IME peek back under window_state.
|
|
// This must hang even though keyboard input still uses the upstream fix.
|
|
const peek = " let more_char_coming = more_ime_char_coming(window, msg);\n"
|
|
const lock = " let text = {\n let mut window_state = subclass_input.window_state.lock();\n"
|
|
const mutated = replaceOnce(replaceOnce(instrumented, peek, ""), lock, lock + peek)
|
|
await writeFile(eventLoop, mutated)
|
|
cargo(["build", "--locked", "--manifest-path", path.join(imeRoot, "Cargo.toml")], imeRoot)
|
|
check(2, ["ime-boundary"])
|
|
} finally {
|
|
await rm(imeRoot, { recursive: true, force: true })
|
|
}
|