fix(930): UiThread dispatcher-exit null guard, ILGlobals dead public statics, stale doc-comment line counts - #935
Merged
drmoisan merged 16 commits intoSep 29, 2026
Conversation
…iteria for issue 930 Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01KoweznWqwJTkNCf6756FoF
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01KoweznWqwJTkNCf6756FoF
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm
…an, round 4 Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm
…noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm
…, stale doc-comment line counts Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm
…anthropic.com Claude-Session: https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm
…-By: Claude Opus 5.5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm
….5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm
…7 reopened Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm
…INSTALL-ROOT placeholder and check off AC7 Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm
…Opus 5.5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm
…omments-930 Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com Claude-Session: https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Suggested title
fix(930): UiThread dispatcher-exit null guard, ILGlobals dead public statics, stale doc-comment line counts
Summary
_dispatcher is not nullguard to the dispatcher exit ofUiThread.SynchronizationContextAwaiter.IsCompleted, so a null captured dispatcher can no longer match a thread that owns no dispatcher (null equals null). Regression test recorded failing before the change and passing after it.ILGlobals.CacheandILGlobals.modulesfrom UtilitiesCS (SDIL Reader). Neither had a consumer in the repository; see Backward Compatibility.Cache_IsInitializedtest with two structural tests that pin the rule thatILGlobalsexposes no public mutable static.Why
Issue #930 consolidates three independent low-risk latent defects in production C#, each verified present on main on 2026-09-28:
IsCompletedcomparedDispatcher.FromThread(Thread.CurrentThread)with the captured_dispatcherwithout a null test. The captured-context exit directly above it already carries this guard (added in PR fix(threading): harden the captured-UI-context exit of IsCompleted and settle AC5 #890).ILGlobalsstill declared two public writable static fields after the opcode-table publication was hardened (Bug: ilglobals-loadopcodes-unsynchronised-static-race #824).Cachewas referenced only by one test assertion;moduleshad no reference.What Changed
Production code
UtilitiesCS/Threading/UiThread.cs: one operand&& _dispatcher is not nulladded to the dispatcher-exit return, plus one comment line (2 lines added, 0 removed).UtilitiesCS/NewtonsoftHelpers/SDIL Reader/ILGlobals.cs:public static Dictionary<int, object> Cacheandpublic static Module[]? modulesdeleted (3 lines removed).QuickFiler/Viewers/BreadcrumbBridgeCoordinator.Search.csandQuickFiler/Viewers/BreadcrumbItemViewerLifecycleCoordinator.Search.cs: the parenthesised line-count token removed from one comment line each; the explanation of the 500-line ceiling is retained.Tests
UtilitiesCS.Test/Threading/UiThreadApartmentMeasurement_Tests.cs: new testIsCompleted_WhenTheAwaiterContextIsAForeignDispatcherContextAndNoUiDispatcherWasCaptured_ReturnsFalsein the existingUiThreadPredicateHardening_Testsclass (which already carries[DoNotParallelize]; no attribute added).UtilitiesCS.Test/NewtonsoftHelpers/SDILReader/ILGlobals_Tests.cs:Cache_IsInitializedreplaced byPublicStaticFields_AreAllInitOnlyandPublicStaticFields_AreExactlyTheTwoOpCodeTables.Docs and evidence
docs/features/active/2026-09-28-csharp-latent-hazards-uithread-ilglobals-comments-930/: plan, baseline, regression and QA evidence, reduced-audit artifacts, and one documentation-only remediation cycle (see Verification).Architecture / How It Fits Together
IsCompletedhas independent proof exits that decide whether an awaiter may continue synchronously on the UI thread. The change makes the dispatcher exit apply the same null rule as the captured-context exit, so both exits require a captured dispatcher before comparing it with the executing thread's dispatcher.ILGlobalsnow publishes only its two readonly opcode tables. The QuickFiler changes are comment-only.Verification
Completed (from the evidence under the feature folder):
Total 3, executed 3, passed 2, failed 1) and passed after the guard; all twelve pre-existingIsCompletedtests still pass.passed 13, failed 2) and passed after the deletion (passed 15, failed 0). The solution rebuilt with0 Error(s)after the deletion, and a repository-wide name, string and reflection search found no remaining reference in any.csfile.LOOP: CLEAN PASS:dotnet tool run csharpier format .rewrote nothing in the owned files;dotnet tool run csharpier check .exit 0./t:Rebuild ... /p:EnableNETAnalyzers=true /p:EnforceCodeStyleInBuild=true): exit 0,0 Error(s),0 Warning(s)./t:Rebuild ... /p:TreatWarningsAsErrors=true): exit 0,0 Error(s), no CS86xx errors.First-party coverage: lines 56084/65736 (85.32%), branches 13597/17054 (79.73%)against a baseline of 85.31% and 79.71%.DoNotParallelize, no worker-count change, no retry, no sleep, runsettings hash unchanged.Local run conditions:
ShellUtilities_Tests,ShellUtilitiesStatic_Tests,SysImageListHelperTests,OSBrowser_Tests) hang on the development workstation and were excluded identically from the baseline and final local coverage runs. The mstest-coverage CI workflow runs them unfiltered.TryAddValuesAsync_UpdatesExistingValuedid not fail; the one permitted re-measurement was not used.Recommended:
Backward Compatibility / Migration Notes
SDILReader.ILGlobals.Cache(public static Dictionary<int, object>) andSDILReader.ILGlobals.modules(public static Module[]?) are removed from the public surface of UtilitiesCS. No in-repository consumer exists: the full solution rebuilt with zero errors and the reference search found no use by name, string literal or reflection. An external consumer, if any, would fail to compile and should not have been writing these process-wide fields; there is no replacement member.Risks and Mitigations
IsCompleted: when no UI dispatcher was captured, the dispatcher exit now returns false instead of true on a thread without a dispatcher. This only affects the case where the previous result was the defect; the continuation is then posted to the context rather than run inline. Covered by the new regression test and the unchanged passingIsCompletedsuite. Rollback: revert the one-operand change.ILGlobalsfields: mitigated by the solution-wide rebuild and reference search above.Review Guide
UtilitiesCS/Threading/UiThread.cs(2 lines).UtilitiesCS/NewtonsoftHelpers/SDIL Reader/ILGlobals.cs(3 deletions) andILGlobals_Tests.cs.UiThreadApartmentMeasurement_Tests.cs(one new test).evidence/qa-gates/toolchain-final-pass.md,evidence/qa-gates/coverage-comparison.md,evidence/regression-testing/889-fail-before.md,evidence/regression-testing/863-reference-search.md, and the*.2026-09-29T10-16.mdreaudit artifacts.The branch also contains a merge of main (no conflicts; no overlap with the six code paths).
Follow-ups
UtilitiesCS.csprojandVBFunctions.csprojname Meziantou.Analyzer 3.0.235 andSVGControl.Test.csprojnames MSTest.Analyzers 4.4.0, while package restore installs 3.0.290 and 4.4.1; a fresh worktree's analyzer Rebuild fails with CS0006 until the named versions are installed. Pre-existing on main; not changed here.using System.Collections.Generic;inILGlobals.csis now unused (no analyzer diagnostic).GitHub Auto-close
🤖 Generated with Claude Code
https://claude.ai/code/session_01KNZiXntshsLY8vqqCHUvHm