Bound native hook stack use and scope hooks to foreground threads

Ignored and reentrant Edit messages previously entered a callback whose
large text buffers increased stack use on every nested call. Menu hook
forwarding also reserved payload buffers for unrelated messages. The
64-bit helper additionally installed desktop-wide hooks, allowing
background UI threads to acquire subclasses and introducing synchronous
traffic through Wine's mixed-architecture global hook chain.

Move Edit processing and menu payload construction into non-inlined
handlers. Keep the forwarding callbacks small, retain the busy-state and
destruction guards, and preserve application subclass chaining and ANSI/
Unicode text handling.

Share foreground hook ownership between the Winelib helper and 32-bit
host. Attach both hooks only to the matching foreground UI thread,
release them when that thread changes, and retry incomplete attachment
as a pair. Preserve existing Edit subclasses across foreground changes
and release host hooks during shutdown.

Add native 32-bit and 64-bit stack-budget fixtures covering ignored and
busy reentrant messages, menu forwarding, nested text queries, application
subclasses before and after attachment, text input, and destruction during
a text change. Add a foreground-scope integration fixture that verifies
background exclusion, thread switching, detachment, and reattachment.
Keep hook debug symbols and frame pointers for native crash diagnosis,
and document installation and live acceptance requirements.

Validation:
- CMake configure and parallel build; git diff --check.
- Hook-safety CTest and foreground-scope fixtures for both architectures.
- Existing menu fixtures and Edit late-attachment/restart checks for both
  architectures, plus the 32-bit list fixture and reader-timeout test.

These checks exercise specific stack and hook-scope defects; they do not
establish the cause of the live MUSHclient stack overflow. Audible speech,
physical braille, and live MUSHclient acceptance remain separate checks.
This commit is contained in:
deedra committed 2026-10-19 18:35:57 -07:00
1 parent b808c6686a
commit 3474bb39c7
12 files changed
+427 -114

No files matched your search

+4 -31
View File
@@ -1,18 +1,9 @@
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "menu_bridge.h"
#include "foreground_hooks.h"
#include <stdlib.h>
#include <stdio.h>
static DWORD foreground_thread32(void) {
DWORD pid = 0;
DWORD thread = GetWindowThreadProcessId(GetForegroundWindow(), &pid);
HANDLE process = pid ? OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid) : NULL;
BOOL wow64 = FALSE;
BOOL known = process && IsWow64Process(process, &wow64);
if (process) CloseHandle(process);
return known && wow64 ? thread : 0;
}
// Hook DLLs must match the application's architecture. This small PE host
// supplies 32-bit menu/editor hooks without requiring 32-bit Linux GLib/Winelib.
int WINAPI WinMain(HINSTANCE instance, HINSTANCE previous, LPSTR args, int show) {
@@ -40,29 +31,12 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE previous, LPSTR args, int show)
HMODULE module = LoadLibraryW(path);
HOOKPROC callback = module ? (HOOKPROC)GetProcAddress(module, "WineAccessMenuHook") : NULL;
HOOKPROC inputCallback = module ? (HOOKPROC)GetProcAddress(module, "WineAccessInputHook") : NULL;
HHOOK hook = NULL;
HHOOK inputHook = NULL;
DWORD hookedThread = 0;
WineAccessForegroundHooks hooks = {module, callback, inputCallback, NULL, NULL, 0};
if (!callback || !inputCallback)
fprintf(stderr, "wine-access: 32-bit menu/editor hooks unavailable (error %lu)\n", GetLastError());
if (callback && inputCallback) {
while (IsWindow(bridge)) {
// Wine's mixed-architecture global hook chain can suppress the 64-bit
// hook. Attach only to the foreground 32-bit UI thread, and release it
// when focus moves elsewhere. The 64-bit helper keeps its global hook.
DWORD thread = foreground_thread32();
if (thread != hookedThread) {
if (hook) UnhookWindowsHookEx(hook);
if (inputHook) UnhookWindowsHookEx(inputHook);
hook = thread ? SetWindowsHookExW(WH_CALLWNDPROC, callback, module, thread) : NULL;
inputHook = thread ? SetWindowsHookExW(WH_GETMESSAGE, inputCallback, module, thread) : NULL;
hookedThread = hook && inputHook ? thread : 0;
if (!hookedThread) {
if (hook) UnhookWindowsHookEx(hook);
if (inputHook) UnhookWindowsHookEx(inputHook);
hook = inputHook = NULL;
}
}
wine_access_update_hooks(&hooks, TRUE);
DWORD result = MsgWaitForMultipleObjects(1, &parent, FALSE, 100, QS_ALLINPUT);
if (result == WAIT_OBJECT_0 || result == WAIT_FAILED) break;
MSG message;
@@ -71,8 +45,7 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE previous, LPSTR args, int show)
DispatchMessageW(&message);
}
}
if (hook) UnhookWindowsHookEx(hook);
if (inputHook) UnhookWindowsHookEx(inputHook);
wine_access_release_hooks(&hooks);
}
if (module) FreeLibrary(module);
CloseHandle(parent);