Skip to content

Conversation

@delaneyb
Copy link

@delaneyb delaneyb commented Oct 9, 2025

Summary

Fixes intermittent crashes with assertion: "not enough elements in the stack" when calling hs.window:application() on windows from recently-terminated applications.

Root Cause

  • The binding used the LuaSkin shared singleton to push the return value. That singleton holds a mutable lua_State pointer (.L).
  • At the crash point, the singleton's .L differed from the binding's L, so the return value was pushed onto a different lua_State than the caller was returning to.
  • Evidence from runtime logs:
    • Before push: WINAPP before: L=0x600002ae2158, skin.L=0x15b00a208, app=nil
    • pushNSObject ran on L=0x15b00a208
    • After: WINAPP after: L=0x600002ae2158 top=0, pushed=1 → Lua asserted "not enough elements in the stack".
  • The mutable .L can be changed by any code path using the shared singleton (e.g. logging or other sharedWithState calls). The logs show a concrete L mismatch at the moment app==nil; this change removes sensitivity to that for this binding.

The Fix

  • When app == nil (process terminated), push nil directly to the caller's L using lua_pushnil(L) instead of pushNSObject (which pushes to the singleton's .L).
  • Clear the caller's stack first with lua_settop(L, 0) to avoid leaking the receiver (consistent with other window methods).

Precedent in Codebase

  • extensions/axuielement/common.m uses the same pattern:
    • if init returns nil → lua_pushnil(L); else → [skin pushNSObject:obj].

Impact

  • Minimal change; only affects the nil-return path.
  • No behavior change for valid applications.
  • Eliminates the observed state-mismatch crash during window iteration when the owning process has just terminated.

When hs.window:application() is called on a window whose process has
just terminated (initWithPid returns nil), the code must ensure the
return value is pushed onto the correct lua_State.

The bug occurred because:
1. The shared LuaSkin instance's .L field can be temporarily swapped
   by class-level logging (+[LuaSkin logError:...])
2. When app==nil, logError is called for the missing NSRunningApplication
3. If pushNSObject(nil) executes while .L points to a different state,
   the nil gets pushed onto the wrong Lua stack
4. The C function returns '1 result' to the caller's L, but that stack
   is empty, triggering: 'not enough elements in the stack' assertion

Fix: When app is nil, push nil directly to the caller's L using
lua_pushnil(L) instead of going through pushNSObject which uses the
mutable shared instance's .L.

Also add lua_settop(L, 0) to prevent leaking the receiver in coroutines
(matching the pattern used in other window methods).

Fixes intermittent crashes during window enumeration when iterating
over windows from recently-terminated applications.
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pull request does not contain a valid label. Please add one of the following labels: ['pr-fix', 'pr-change', 'pr-feature', 'pr-maintenance']

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