mirror of https://github.com/icsharpcode/ILSpy.git
Tree:
ca3e528ce2
christophwille/closedhierarchies
christophwille/membench
compound-assignment-operators
fix/1982-params-attribute-args
fix/2040-invalid-xml-characters
fix/2093-navigateto-reference-assembly
fix/2362-xalz-references
fix/2372-address-taken-by
fix/3282-indexer-optional-arguments
fix/3568-record-member-order
fix/4059-deconstruct-out-slots
fix/lambda-parameter-syntax
fix/scroll-children-on-expand
gh-pages
ldmembertoken
master
natural-type-lambdas-methods
null-coalescing-assignment
release/10.1
release/6.2
release/7.1
release/7.2
release/8.1
substring-optimizations
tests/829-async-method-builder-override
tests/829-collection-expressions
tests/829-compound-assignment-operators
tests/829-coverage-audit
tests/829-expression-tree-named-optional-args
tests/829-expression-variables-in-initializers
tests/829-extended-property-patterns
tests/829-extension-members
tests/829-extension-operators
tests/829-file-local-types
tests/829-improved-definite-assignment
tests/829-improved-overload-candidates
tests/829-inline-arrays
tests/829-interpolated-string-improvements
tests/829-lambda-param-modifiers
tests/829-list-patterns
tests/829-lock-object
tests/829-mixed-deconstruction
tests/829-null-coalescing-assignment
tests/829-null-conditional-assignment
tests/829-object-initializer-indexer
tests/829-overload-resolution-priority
tests/829-params-collections
tests/829-pattern-matching-improvements
tests/829-primary-constructors
tests/829-ref-unsafe-in-iterators-async
tests/829-sealed-record-tostring
tests/829-target-typed-conditional
tests/829-tuple-comparison
win-a11y-textsize
1.0-Beta
1.0-M1
1.0-M2
1.0-M3
1.0.0
2.0.0
2.1
2.2
2.3
2.3.1
3.0-Preview1
3.0-Preview2
3.0.2
v10.0
v10.0-preview1
v10.0-preview2
v10.0-preview3
v10.0.1
v10.1
v10.1.1
v11.0
v11.0-preview1
v11.0-rc
v2.3.2
v2.4
v3.0
v3.0-beta1
v3.0-beta2
v3.0-beta2a
v3.0-beta3
v3.0-beta4
v3.0.1
v3.1-beta1
v3.1-final
v3.1-rc
v3.2-beta
v3.2-rc
v3.2.0
v4.0
v4.0-alpha1
v4.0-beta1
v4.0-beta2
v4.0-beta3
v4.0-rc1
v4.0-rc2
v4.0.1
v5.0
v5.0-preview1
v5.0-preview2
v5.0-preview3
v5.0-preview4
v5.0-rc1
v5.0.1
v5.0.2
v6.0
v6.0-preview1
v6.0-preview2
v6.0-preview3
v6.0-preview4
v6.0-rc1
v6.1
v6.2
v6.2-preview1
v6.2-preview2
v6.2.1
v7.0
v7.0-preview1
v7.0-preview2
v7.0-preview3
v7.0-rc1
v7.0-rc2
v7.1
v7.2
v7.2-preview1
v7.2-preview2
v7.2-preview3
v7.2-preview4
v7.2-rc
v7.2.1
v8.0
v8.0-preview1
v8.0-preview2
v8.0-preview3
v8.0-preview4
v8.0-rc1
v8.1
v8.1.1
v8.2
v9.0
v9.0-preview1
v9.0-preview2
v9.0-preview3
v9.0-rc
v9.1
${ noResults }
1 Commits (ca3e528ce24bf62df6d6bbbb3b34846633ead6a4)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
ca3e528ce2 |
Add a Debug-only thread-affinity check to the shared tree model
Issue #3290 is a NullReferenceException in GetNodeByVisibleIndex that is provably unreachable single-threaded: TreeFlattener.Count and GetNodeByVisibleIndex read the same totalListLength fields back to back, so a stale index yields ArgumentOutOfRangeException, never an NRE. A stress harness with reader threads racing an IsExpanded/Children mutator reproduces exactly that NRE, so the crash requires a mutation from a foreign thread. The rule that a displayed tree is only mutated from the UI thread was pure convention: ICSharpCode.ILSpyX/TreeView contained no VerifyAccess, lock or dispatcher of any kind, and two tree nodes already carry a Dispatcher.UIThread.Invoke workaround for the same hazard, which means it has been hit before and fixed one site at a time. ICSharpCode.ILSpyX is host-agnostic and must not name a dispatcher, so ownership is stated by the host instead of inferred: SetOwner(Thread) marks the thread allowed to mutate a node and its subtree. Unowned means unchecked, which is what makes the analyzer pattern legal - build a subtree on a worker, publish it on the UI thread - without an exception carved into the rule. The owner is resolved by walking up the model-parent chain to the nearest explicit owner rather than stamped onto every node. That buys the propagation rules for free: one call on the root covers the whole displayed tree, children attached later inherit it with no bookkeeping, and a subtree built off-thread is unchecked while it is being built yet inherits the owner the moment it is attached - an attachment which is itself a checked mutation of the owned tree. A subtree that already carries a different owner would otherwise leave one displayed tree demanding two threads, so that case is reported once and the incoming owner dropped, rather than reported on every later mutation. Re-owning is allowed because handing a tree over is the point, but the handoff must come from the current owner: a background thread taking a live tree away from the UI is the race being hunted. The check sits in SharpTreeNodeCollection.OnCollectionChanged, which every Children mutator funnels through, and in the IsExpanded and IsHidden setters - the three entry points that invalidate totalListLength. Checking in OnCollectionChanged also means a violation is reported before OnChildrenChanged rewrites the flat-list tree, so the AVL structure is left intact. Violations are collected rather than fatal by default, with per-call-site deduplication and a count, and the first hit of each site written straight through to a log file so a long exploratory session can be read while it runs. FailFast makes them throw so tests can observe one deterministically. Everything is behind #if DEBUG plus [Conditional("DEBUG")], so the release build has no field on SharpTreeNode and no call at any site; verified by decompiling the release assembly. Assisted-by: Claude:claude-opus-5:Claude Code |
2 weeks ago |