Skip to content

Stop debug tslang --emit=exe/dll ending in a breakpoint at exit - #326

Merged
ASDAlexander77 merged 1 commit into
mainfrom
crt-report-hook-no-break
Sep 14, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
crt-report-hook-no-break

Conversation

@ASDAlexander77

Copy link
Copy Markdown
Owner

Problem

In a Debug build, every tslang --emit=exe / --emit=dll run ended in a breakpoint exception (0x80000003) at process exit. This failed test-compile-gc-shared-auto and test-jit-gc-defaultlib-collector, and was half of test-compile-gc-defaultlib-collector. CI builds Release only, so it never showed there.

Root cause

  1. tslang.cpp enables _CRTDBG_LEAK_CHECK_DF, so the debug CRT emits a _CRT_WARN leak report at exit.
  2. buildExe constructs llvm::InitLLVM, which (via DisableSystemDialogsOnCrash) installs LLVM's AvoidMessageBoxHook with _CrtSetReportHook. That hook answers every report with "retry".
  3. "Retry" makes the CRT call _CrtDbgBreak, so the harmless leak report became an int3.

JIT exits via TerminateProcess and never reports; --emit=obj never installs LLVM's hook.

Fix

Install a _CrtSetReportHook2 hook in main (Debug MSVC only) that handles _CRT_WARN itself: it forwards the text to OutputDebugStringA and returns without asking for a break. v2 hooks run before the v1 hook (ucrt dbgrptt.cpp), so LLVM's hook never sees warnings. Errors and asserts are untouched.

Verification (windows-msbuild-2026-debug)

  • --emit=dll / --emit=exe exit 0.
  • test-compile-gc-shared-auto and test-jit-gc-defaultlib-collector pass.
  • test-compile-gc-defaultlib-collector passes together with TypeScriptCompilerDefaultLib#7 (debug wrappers built with /MTd) and a rebuilt debug default lib.
  • Full suite (ctest -R "^test-", 2727 tests): only test-jit-rc-owned-array-ops and test-compile-rc-corpus-00owned-array-ops fail, both fixed by Fix memmove sizes in array shift, unshift and splice #325 which is not in this branch.

🤖 Generated with Claude Code

A debug tslang turns on the CRT leak report at exit (_CRTDBG_LEAK_CHECK_DF),
which is emitted as a _CRT_WARN report. Building an exe or dll constructs
llvm::InitLLVM (exe.cpp), whose PrintStackTraceOnErrorSignal installs LLVM's
AvoidMessageBoxHook with _CrtSetReportHook. That hook answers every CRT report
with "retry", and the report macro turns a retry into _CrtDbgBreak - so every
debug --emit=exe and --emit=dll ended with an unhandled breakpoint
(0x80000003, exit code 3) after the output had been written. The heap was not
corrupted: periodic _CrtCheckMemory during the build stayed clean. The JIT
installs the same hook but exits through TerminateProcess, and --emit=obj never
installs it.

Install a _CrtSetReportHook2 hook for _CRT_WARN that writes the message to the
debugger output (where it went before) and does not ask to break. Report hooks
from _CrtSetReportHook2 run before the _CrtSetReportHook one (ucrt
dbgrptt.cpp), so asserts and errors still reach LLVM's hook.

Fixes test-compile-gc-shared-auto and test-jit-gc-defaultlib-collector on debug
builds (CI builds Release, where the leak check does not exist).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit b2d90c3 into main Sep 14, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the crt-report-hook-no-break branch September 14, 2026 21:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant