mirror of https://github.com/icsharpcode/ILSpy.git
Tree:
07d5a01d2b
christophwille/closedhierarchies
christophwille/contributing
christophwille/decompilerdocs
christophwille/membench
claude/ilspy-pr-3839-review-uo8q7r
collection-expression-tests
copilot/add-field-keyword-auto-properties
deconstruction-fixes
fix-3475-anonymous-type-null
fix-3860-outvar-explicit-type
fix/3465
fix/3518
gh-pages
issue1638
master
natural-type-lambdas-methods
null-coalescing-assignment
release/10.1
release/6.2
release/7.1
release/7.2
release/8.1
stash/beautify-enum-member-declarations
stash/dynamic-await
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
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 }
9 Commits (07d5a01d2bb30c50a6daaf80fce059bfaf4ffbf8)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
391bf28f81 |
Drop the per-call predicate from FindFlagInitStore.
After the cluster-1/3/4 fixes converged every caller on the same matching shape (match the slot/kind/type of a reference ILVariable, then check the init value), the `Predicate<StLoc>` parameter was just a hole through which each caller restated that logic verbatim. Fold the slot/kind/type check into the helper and have callers pass just the reference variable and a value matcher. |
2 months ago |
|
|
a9a43f96a9 |
Reverse runtime-async multi-handler try-catch dispatched via an if-chain.
The multi-handler matcher only recognized a switch-instruction dispatch — but
when a try-catch has just two handlers (or a handful with non-consecutive K
values), Roslyn emits an if-chain instead:
if (num == K_1) br case_K_1; br nextBlock
; nextBlock { if (num == K_2) br case_K_2; <leave outer | br end> }
Add a parallel matcher that walks the if-chain and collects (K, case-block)
pairs the same way MatchSwitchDispatch does, plus the terminating leave/branch
as the default exit. Call it as a fallback when the switch matcher rejects.
Also clone the default-exit before re-adding it to the continuation block —
in the if-chain shape it's a child of a *different* block (a later step in
the chain), not the now-cleared switch instruction, so the in-place re-add
relied on the switch's release cascade and didn't generalize.
Closes Cluster 2 from #3745.
|
2 months ago |
|
|
8e2e48f5cc |
Generalise runtime-async flag-based early-return for nested try-finally.
The flag-based early-return rewriter was tied to one specific lowered shape:
the try body's flag-setter had to be exactly `stloc flag(K); leave try`, the
post-try check had to be a `br checkBlock` (not an inline `IfInstruction`), and
the early path had to be a direct Leave or a forward to a one-instruction
leave-block whose target was the function body. None of those hold for
`try { try { return X; } finally { await ... } } finally { await ... }`:
- The inner flag-setter has a leading capture-forwarding store
(`stloc capture(X); stloc innerFlag(K); leave inner-try`).
- The inner check-block's early path branches to a multi-instruction helper
that sets the *outer* flag and leaves the outer try, instead of being a
direct return.
- SplitVariables hands out a separate ILVariable for the pre-init flag store
when the in-handler store is in a disjoint dataflow region.
Rebuild the matcher around the idea of a "template" — the chain of stores
the early path performs before its terminating Leave. Each flag-setter then
becomes its own prefix stores + a clone of the template, which collapses the
inner-then-outer flag chain in two passes (inner first, outer second, because
descendant order visits the inner TryFinally first). Also extend the
flag-setter scan to walk the whole try-block's descendants — after the inner
rewrite, the inner's spliced flag-setter lives inside the inner-try container
but still leaves outwards to the outer try, so it's an outer flag-setter from
the outer's perspective.
Add a `RUNTIMEASYNC` preprocessor symbol (defined when `EnableRuntimeAsync`
is set) and gate the new return-from-try-finally fixtures on it — the
state-machine async pipeline doesn't recover this shape, so it would expand
the same source into the `int result; try { ...; result = X; } finally { ... }
return result;` verbose form and the Async (state-machine) pretty test would
regress.
Closes Cluster 1 (1.1, 1.3) from #3745. Cluster 1.2 (void `return;` at the
end of a try-finally body) and 1.4 (break/continue across a try-finally) are
left for a follow-up: both round-trip semantically equivalently but the AST
emitter drops a trailing void `return;` and the break/continue lowering uses
a switch dispatch that the current single-K matcher can't recognize.
|
2 months ago |
|
|
8a03ee246f |
Reverse runtime-async try-finally where the try body always throws.
`try { throw new ...(); } finally { await ... }` lowers to a try whose only
exit is the throw (handled by the synthetic catch). The existing matcher
required at least one outward Branch to the continuation, which is too strict
— a throw-only try body produces zero outward branches but is still a valid
lowered shape. Two follow-on fixes were also needed:
- The pre-init's ILVariable diverges from the in-handler store after
SplitVariables when the try body has no path that reaches the dispatch's
load without going through the catch; match the flag init by slot/kind/type
instead of identity (same workaround the multi-handler matcher uses).
- With a throw-only try body the new TryFinally has unreachable endpoint,
so appending the no-exception successor after it would put a non-final
unreachable-endpoint instruction in the parent block. Skip the append in
that case — the parent block's endpoint is already correctly unreachable.
Closes Cluster 4 from #3745.
|
2 months ago |
|
|
8059fde539 |
Reverse runtime-async exception lowering for try-catch nested inside try-finally.
The single-handler try-catch matcher was tied to the top-level shape: it required the try-catch be the last instruction in its parent block, that the post-catch "no exception" path be a direct Leave that exits the function, and that the flag-init's ILVariable be identical to the in-handler flag store. None of those hold for an inner try-catch sitting inside an outer try-finally where both await — the inner is followed by a `br continuation`, the no-exception path leaves the outer try-block (not the function), and SplitVariables hands out a separate ILVariable for the pre-init store. Drop the "must be last instruction" gate, accept Leave-to-any-ancestor and cross-container Branch as the no-exception exit (extracted into a new `IsContainerExit` helper), and match the flag-init by slot/kind/type the same way the multi-handler matcher already does. Closes Cluster 3 from #3745. |
2 months ago |
|
|
c107c5a4bc |
Reverse runtime-async flag-based early-return across a try-finally.
When a return crosses an enclosing try-finally with await, runtime-async lowers it as: capture the return value, set an int flag to a unique non-zero value, leave the try block normally so the finally runs, then post-finally check "if (flag == K) return capture;". Detect that pattern after my outer try-finally rewrite (or, in optimized builds, the compiler-emitted TryFinally directly) and replace each capture-flag-and-leave site with a direct "leave outer (capture)" — the leave still passes through the TryFinally, so the user's finally body executes before the function returns, which matches the source-level semantics. Handles both the "if (flag == K)" and "if (flag != K)" check forms (the optimizer emits the latter). Closes the last gap in Issue2436 — RuntimeAsync now passes both Optimize and non-Optimize modes; the full RuntimeAsync* sweep is 12/12 green. Also remap reads of the captured-obj local inside the cleaned filter so optimized builds (where Roslyn inlines the typed-cast directly into the user filter expression instead of stashing it in a local) render against the catch variable rather than against "((T)obj)". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
2 months ago |
|
|
7c39e9ea5e |
Reduce multi-handler runtime-async try-catch back to a multi-handler TryCatch.
When the user writes multiple catch clauses on a single try, runtime-async lowers each catch's body to "[stloc tmp(ex);] [stloc obj(...);] stloc num(K_i); br continuation" with a unique K_i per handler, and the post-catch flow becomes a switch dispatch on `num` that branches to each user-level catch body. Add a TryRewriteMultiHandlerTryCatch driver that mirrors the single-handler match (using NormalizeRuntimeAsyncFilter for filter cleanup), recognizes the post-catch SwitchInstruction, and uses the existing dominator-based block move to relocate each switch case into the corresponding handler body, remapping that handler's per-handler synthesized variables (and the shared filter obj) back to the catch variable. The shared obj local can no longer be remapped function-wide during filter normalization — that would tag every dispatch idiom with whichever handler ran first — so record the obj per handler in a dictionary and let TryRewriteTryCatch / TryRewriteMultiHandlerTryCatch remap it scoped to each moved catch body. The pre-init "stloc num(0)" is matched by slot index rather than ILVariable identity, since SplitVariables splits the dead pre-init off from the in-handler stores. Resolves the LoadsToCatch case. Filter normalization extends to the typeless `catch when (filter)` form (isinst Object in the filter), recovered as `catch when` in the AST output. Remaining failures in RuntimeAsync are now multi-await expressions, async-in-struct, and a couple of unrelated decompilation issues. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
2 months ago |
|
|
58baac0c50 |
Normalize runtime-async catch filters to recover catch-with-when.
Roslyn lowers `catch (T ex) when (filter)` to `catch object when (BlockContainer { isinst T; obj=ex; <filter> })` even when T is `object` (the source-level `catch when (filter)` form). Run a pre-pass over every catch handler that matches the four-block diamond (entry isinst-gate, trueBody with obj-store + user filter, falseBody constant-false, merge leave-with-result), strip the obj-store machinery, retype the handler variable when T is a more specific type than object, and remap reads of the synthesized obj/tmp/typedEx variables back to the handler variable. After that the catch body is the same simple flag-store shape that TryRewriteTryCatch already handles, so the existing match runs unchanged.
Resolves the RethrowDeclaredWithFilter and ComplexCatchBlockWithFilter cases. Multi-handler catches (LoadsToCatch) still fail because they use a multi-valued discriminator that isn't reduced yet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
2 months ago |
|
|
255e73f3a0 |
Reverse runtime-async lowering of try-finally and try-catch with await.
Roslyn's runtime-async lowering flattens these into a TryCatch[object] with a captured-rethrow pattern (try-finally) or a TryCatch[T] with a flag-int discriminator and a guarded post-catch body (try-catch). Add a new transform invoked from AsyncAwaitDecompiler when the state-machine matches fail and the method has the runtime-async impl bit; it pattern-matches both shapes and rewrites them back to TryFinally / TryCatch with the original catch body inlined into the handler. The state-machine and runtime-async lowerings of try-finally use the same catch-handler shape and the same dominator-based finally-body extraction, so promote those to internal static helpers (MatchObjectStoreCatchHandler, MoveDominatedBlocksToContainer) on AwaitInFinallyTransform and call them from the new transform. Filter-bearing catches and multi-handler tries are still left to the standard pipeline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
2 months ago |