From 46a5afe8ef27bf8dff131ec078f49f5ca49be97d Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Thu, 29 Oct 2026 21:11:16 +0000 Subject: [PATCH 1/3] ci(native): run the tests against a UBSan build of the addon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A normal build of the out-of-range casts fixed in #30 passes every test. Add a ci-ubsan mise task that builds only the addon with -fsanitize=undefined,float-cast-overflow and no recovery, then runs the tests and smoke. CI runs it as a linux-x64-ubsan matrix row, and every row now names its mise task. _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `medium`_ Change-Id: Iad93613b401f2b4c82346d12b851dc9dc779dac5 Signed-off-by: Thomas Kosiewski --- .github/workflows/ci.yml | 8 +++++++- README.md | 1 + mise.toml | 11 +++++++++++ native/README.md | 1 + 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8ee86d..e798069 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,11 +25,17 @@ jobs: include: - target: linux-x64 os: ubuntu-latest + task: ci - target: linux-arm64 os: ubuntu-24.04-arm + task: ci - target: macos-arm64 os: macos-14 + task: ci + - target: linux-x64-ubsan + os: ubuntu-latest + task: ci-ubsan steps: - uses: actions/checkout@v6 - uses: jdx/mise-action@v3 - - run: mise run ci + - run: mise run ${{ matrix.task }} diff --git a/README.md b/README.md index 3dc9fe6..6ec24f8 100644 --- a/README.md +++ b/README.md @@ -184,5 +184,6 @@ The package has four layers: - Run: `mise run ci` - `.github/workflows/ci.yml` runs the same task. Without mise, run the steps in "Native Build" above. +- UBSan build of the addon: `mise run ci-ubsan`. CI runs it as the `linux-x64-ubsan` row. - Packaged-install journeys: [`.agents/skills/verify-libghostty-vt-node/SKILL.md`](https://github.com/coder/libghostty-vt-node/blob/main/.agents/skills/verify-libghostty-vt-node/SKILL.md) - TLA+ model of the native lifecycle: [`.agents/skills/verify-native-lifecycle/SKILL.md`](https://github.com/coder/libghostty-vt-node/blob/main/.agents/skills/verify-native-lifecycle/SKILL.md) diff --git a/mise.toml b/mise.toml index ddfd277..18c3e6d 100644 --- a/mise.toml +++ b/mise.toml @@ -14,6 +14,17 @@ run = [ "npm run smoke", ] +[tasks.ci-ubsan] +description = "Build the native addon with UBSan, then run tests and smoke so undefined behavior aborts the run." +run = [ + "npm ci", + "npm run build:libghostty", + # GCC's "undefined" group leaves out float-cast-overflow. + "CXXFLAGS=\"-fsanitize=undefined,float-cast-overflow -fno-sanitize-recover=all\" LDFLAGS=\"-fsanitize=undefined,float-cast-overflow\" npm run build:native", + "npm test", + "npm run smoke", +] + [tasks.release-prebuilds] description = "Build and verify npm-bundled Node-API prebuild artifacts." run = [ diff --git a/native/README.md b/native/README.md index 37f130c..6b244b5 100644 --- a/native/README.md +++ b/native/README.md @@ -67,5 +67,6 @@ C++ declarations: - Run (after `npm ci`): `npm run build:libghostty && npm run build:native && npm test` - Every native test skips when the addon does not load (`test/native-support.ts:nativeSupport`), so check the test output for skips. +- `mise run ci-ubsan` builds the addon with UBSan (Linux, GCC), then runs the tests and smoke. Undefined behavior aborts the run (`mise.toml:tasks.ci-ubsan`). - `test/workers.test.ts` checks the per-environment state in child processes with `worker_threads`. `test/terminal.test.ts` covers `feed`, `snapshot`, `resize`, `dispose`, and the `RangeError` cases of the `createTerminal` checks in the addon. No vitest test covers `formatPlain`, `formatHtml`, `includeScrollback`, the cell style fields, or the typed-array errors. - For changes to `TerminalWrap` or `InitAll`, also run the TLA+ lifecycle model: [`.agents/skills/verify-native-lifecycle/SKILL.md`](https://github.com/coder/libghostty-vt-node/blob/main/.agents/skills/verify-native-lifecycle/SKILL.md). From dcb68251f79d43c2cb482d40e848204ad0259363 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Thu, 29 Oct 2026 21:31:44 +0000 Subject: [PATCH 2/3] ci(native): fail the UBSan row when the addon lacks the sanitizer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the sanitizer flags stop reaching g++, the ubsan row would build a plain addon and stay green. The new step checks the addon's own object files for undefined UBSan _abort handlers. The final .node cannot show this, because libghostty-vt.a always carries Zig's handlers. _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `low`_ Change-Id: I5e7cd4b708ad8e7f17356cb9e4da8cecf15c75b9 Signed-off-by: Thomas Kosiewski --- mise.toml | 1 + native/README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mise.toml b/mise.toml index 18c3e6d..af8e37f 100644 --- a/mise.toml +++ b/mise.toml @@ -21,6 +21,7 @@ run = [ "npm run build:libghostty", # GCC's "undefined" group leaves out float-cast-overflow. "CXXFLAGS=\"-fsanitize=undefined,float-cast-overflow -fno-sanitize-recover=all\" LDFLAGS=\"-fsanitize=undefined,float-cast-overflow\" npm run build:native", + "nm -u $(find build/Release -name '*.o') | grep -q '__ubsan_handle_.*_abort' || { echo \"error: the addon was built without UBSan\" >&2; exit 1; }", "npm test", "npm run smoke", ] diff --git a/native/README.md b/native/README.md index 6b244b5..cd522dc 100644 --- a/native/README.md +++ b/native/README.md @@ -67,6 +67,6 @@ C++ declarations: - Run (after `npm ci`): `npm run build:libghostty && npm run build:native && npm test` - Every native test skips when the addon does not load (`test/native-support.ts:nativeSupport`), so check the test output for skips. -- `mise run ci-ubsan` builds the addon with UBSan (Linux, GCC), then runs the tests and smoke. Undefined behavior aborts the run (`mise.toml:tasks.ci-ubsan`). +- `mise run ci-ubsan` builds the addon with UBSan (Linux, GCC), then runs the tests and smoke. Undefined behavior aborts the run. The task also fails when the addon's object files contain no UBSan checks, which means the flags did not reach g++ (`mise.toml:tasks.ci-ubsan`). - `test/workers.test.ts` checks the per-environment state in child processes with `worker_threads`. `test/terminal.test.ts` covers `feed`, `snapshot`, `resize`, `dispose`, and the `RangeError` cases of the `createTerminal` checks in the addon. No vitest test covers `formatPlain`, `formatHtml`, `includeScrollback`, the cell style fields, or the typed-array errors. - For changes to `TerminalWrap` or `InitAll`, also run the TLA+ lifecycle model: [`.agents/skills/verify-native-lifecycle/SKILL.md`](https://github.com/coder/libghostty-vt-node/blob/main/.agents/skills/verify-native-lifecycle/SKILL.md). From ffecdc20f7f169d20982139ef67e4c26cbd2b699 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Thu, 29 Oct 2026 21:39:50 +0000 Subject: [PATCH 3/3] ci(native): read all nm output in the UBSan guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mise runs each step with pipefail. grep -q exits on its first match, so nm can die of SIGPIPE and the guard fails a correctly instrumented build with exit 141. grep -c reads to the end and still exits 1 when nothing matches. _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `low`_ Change-Id: I17c4f080629226f6dfb27d9354923d7a31ac382b Signed-off-by: Thomas Kosiewski --- mise.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mise.toml b/mise.toml index af8e37f..7eba29e 100644 --- a/mise.toml +++ b/mise.toml @@ -21,7 +21,7 @@ run = [ "npm run build:libghostty", # GCC's "undefined" group leaves out float-cast-overflow. "CXXFLAGS=\"-fsanitize=undefined,float-cast-overflow -fno-sanitize-recover=all\" LDFLAGS=\"-fsanitize=undefined,float-cast-overflow\" npm run build:native", - "nm -u $(find build/Release -name '*.o') | grep -q '__ubsan_handle_.*_abort' || { echo \"error: the addon was built without UBSan\" >&2; exit 1; }", + "nm -u $(find build/Release -name '*.o') | grep -c '__ubsan_handle_.*_abort' > /dev/null || { echo \"error: the addon was built without UBSan\" >&2; exit 1; }", "npm test", "npm run smoke", ]