Skip to content

App / headless robustness fixes (non-graphics, from #2109)#2118

Merged
jcelerier merged 12 commits into
masterfrom
planb/app-robustness
Jul 19, 2026
Merged

App / headless robustness fixes (non-graphics, from #2109)#2118
jcelerier merged 12 commits into
masterfrom
planb/app-robustness

Conversation

@jcelerier

Copy link
Copy Markdown
Member

Extracts the non-graphics robustness fixes made during the GPU-graphics work (#2109) so they can land on master independently as small, reviewed commits. Each is a self-contained fix to headless/app-lifetime/drop code — no graphics changes.

Stacked on #2117 (the test infrastructure) so the harness that exercises these paths is available. Will retarget to master once #2117 merges.

Commits

  • core: don't store a null panel when a PanelDelegateFactory declines — a factory returning null (missing hardware / headless) stored a null pointer that crashed every later panels() iteration.
  • core: guard the loadPluginData panel-instantiation path against the same null.
  • explorer: null-tolerant panel lookup in findDeviceExplorerWidgetInstance (consumer side of the above; aborted on hardened stdlibs).
  • packagemanager: honor SCORE_SANITIZE_SKIP_CHECKS in firstTimeLibraryDownload so a non-interactive session doesn't block on the first-run modal.
  • avnd: remove a duplicate include.
  • core: drain the event queue before deleting the presenter — both Minimal[GUI]Application destructors dispatched deferred plugin-load slots against the freed presenter context (use-after-free on shutdown).
  • process: don't let a throwing drop handler escape the noexcept drop API — dropCustom overrides can run parsers that throw on malformed input; catch in getCustomDrops.

Notes on the dataflow analysis

The split branch's 5d26b13ba ("don't let settings models outlive the QApplication") was deliberately skipped: master already solves that bug more cleanly by declaring m_app as a unique_ptr first (destroyed last), so settings models are torn down before the QApplication via member order. Only the still-present processEvents-ordering use-after-free was extracted.

Validation

  • ctest: 17/17.
  • Process + device sweeps under ASAN + UBSan: 0 memory errors, 0 undefined-behavior in score's own code.

🤖 Generated with Claude Code

jcelerier added a commit that referenced this pull request Jul 17, 2026
Eight regression tests guarding the panel/presenter/drop/app-lifecycle
robustness fixes on this branch and #2117 below it — each names the commit
it guards in the CMake comment. All enabled: the fixes they exercise are
present in the chain (planb is in-stack under planc..planf2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
jcelerier and others added 12 commits July 18, 2026 09:34
A factory returning nullptr (missing hardware, headless environment) put a
null unique_ptr in ApplicationComponents::panels, crashing every later
panels() iteration (e.g. Explorer::findDeviceExplorerWidgetInstance from
Scenario::SearchWidget). Warn and skip instead.

(cherry picked from commit 4175e6c)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…null

Same defect as registerPanel: a declining PanelDelegateFactory put a null
unique_ptr in ApplicationComponents::panels and setModel dereferenced it.

(cherry picked from commit 874d5ee)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
ctx.panels()'s indirect iteration dereferences every slot unconditionally,
which aborts on hardened stdlibs (_GLIBCXX_ASSERTIONS) when a slot is empty;
findPanel<T>() does the same lookup null-safely.

(cherry picked from commit 844a4ab)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
…ownload

The first-run library-download question is modal; in non-interactive
sessions (tests/CI) it blocks the main thread forever. Same escape hatch
refresh() already uses.

(cherry picked from commit 26f6eb6)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
(cherry picked from commit b940d06)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A8dWKnm8kgkuDaMMjapgFT
Both Minimal[GUI]Application destructors ran QApplication::processEvents()
after delete m_presenter, so deferred slots queued during plugin load (e.g.
Scenario::SearchWidget's deferred init reaching into the device explorer)
were dispatched against the freed presenter-owned application context — a
use-after-free seen as a shutdown crash on headless harnesses. Drain the
queue while the presenter is still alive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Some dropCustom overrides run parsers that can throw on malformed input,
but getCustomDrops is noexcept, so an exception terminated the editor.
Drop noexcept from the dropCustom contract and catch in getCustomDrops.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Drain the event queue before deleting the presenter: deferred slots queued
during plugin load read the presenter-owned application context, so running
them after the presenter is gone is a use-after-free. Also take the settings
models down while the QApplication is still alive, which Qt >= 6.11 requires.

(cherry picked from commit e4dafbc)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
Eight regression tests guarding the panel/presenter/drop/app-lifecycle
robustness fixes on this branch and the test infrastructure below it —
each names the fix it guards in the CMake comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rZgzE8JjWvHDtaVUhxpLE
@jcelerier
jcelerier force-pushed the planb/app-robustness branch from e42885c to a14d79a Compare July 18, 2026 13:37
@jcelerier
jcelerier changed the base branch from tests/infrastructure to master July 18, 2026 13:37
@jcelerier jcelerier closed this Jul 18, 2026
@jcelerier jcelerier reopened this Jul 18, 2026
@jcelerier
jcelerier merged commit b0786c8 into master Jul 19, 2026
57 of 58 checks passed
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