From 4a6dcd8d68590596ba5111ad444e22e44a299dd2 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 15 Sep 2026 19:16:29 +0200 Subject: [PATCH] Match the VB On Error catch filter exactly Only vbc's On Error lowering produces this filter; structured Catch...When filters call SetProjectError and never reach it. Roslyn emits it from a single code path with System.Exception as the type, and legacy vbc output has the same shape, so there is no chain of unknown conjuncts to walk. The transform runs before the expression transforms canonicalize comparisons, so the filter still compares with cgt against ldnull and cgt.un against zero: the ldnull comparison is fixed up first, and MatchCompUnsignedZero accepts both the unsigned and canonical form. Assisted-by: Claude:claude-opus-5:Claude Code --- .../IL/Instructions/PatternMatching.cs | 19 +++++ .../DetectCatchWhenConditionBlocks.cs | 79 +++++++------------ 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs b/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs index 6a0390bad..e3b4418c6 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs @@ -17,6 +17,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using ICSharpCode.Decompiler.TypeSystem; @@ -467,6 +468,24 @@ namespace ICSharpCode.Decompiler.IL } } + /// + /// For == Equality, matches 'comp(arg == ldc.i4 0)' or 'comp.unsigned(arg <= ldc.i4 0)'. + /// For == Inequality, matches 'comp(arg != ldc.i4 0)' or 'comp.unsigned(arg > ldc.i4 0)'. + /// + public bool MatchCompUnsignedZero(ComparisonKind kind, [NotNullWhen(true)] out ILInstruction? arg) + { + Debug.Assert(kind.IsEqualityOrInequality()); + var unsignedKind = kind == ComparisonKind.Equality ? ComparisonKind.LessThanOrEqual : ComparisonKind.GreaterThan; + if (this is Comp { IsLifted: false } comp && comp.Right.MatchLdcI4(0) + && (comp.Kind == kind || (comp.Kind == unsignedKind && comp.Sign == Sign.Unsigned))) + { + arg = comp.Left; + return true; + } + arg = null; + return false; + } + public bool MatchLdFld([NotNullWhen(true)] out ILInstruction? target, [NotNullWhen(true)] out IField? field) { if (this is LdObj ldobj && ldobj.Target is LdFlda ldflda && ldobj.UnalignedPrefix == 0 && !ldobj.IsVolatile) diff --git a/ICSharpCode.Decompiler/IL/Transforms/DetectCatchWhenConditionBlocks.cs b/ICSharpCode.Decompiler/IL/Transforms/DetectCatchWhenConditionBlocks.cs index 2cc1ba5ef..9b6d7e67b 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/DetectCatchWhenConditionBlocks.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/DetectCatchWhenConditionBlocks.cs @@ -72,15 +72,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms PropagateExceptionVariable(context, catchBlock); } else if (catchBlock.Filter is BlockContainer filterContainer - && MatchTypeTestConjunction(catchBlock.Variable, filterContainer, - out var conjunctionType, out var typeTest) - && conjunctionType.GetStackType() == catchBlock.Variable.StackType) + && MatchVBOnErrorCatchFilter(context, catchBlock.Variable, filterContainer, out exceptionType, out var typeTest) + && exceptionType.GetStackType() == catchBlock.Variable.StackType) { - context.Step($"Detected catch-when type test for {catchBlock.Variable.Name}", typeTest); - catchBlock.Variable.Type = conjunctionType; - var remainder = typeTest.Right; - typeTest.ReplaceWith(remainder); - context.EndStep(remainder); + context.Step($"Detected catch-when for {catchBlock.Variable.Name} (bit.and)", typeTest); + catchBlock.Variable.Type = exceptionType; + var condition = typeTest.Right; + typeTest.ReplaceWith(condition); + context.EndStep(condition); PropagateExceptionVariable(context, catchBlock); } @@ -90,16 +89,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// BlockContainer { /// Block entryPoint (incoming: 1) { - /// leave container(bit.and(bit.and(comp(isinst T(ldloc exceptionVar) != ldnull), cond1), cond2)) + /// leave container(bit.and(bit.and(comp(isinst System.Exception(ldloc exceptionVar) > ldnull), comp.unsigned(ldloc activeHandler > ldc.i4 0)), logic.not(ldloc resumeTarget))) /// } /// } - /// The Visual Basic compiler emits the whole filter as one non-short-circuiting - /// expression rather than the block chain csc emits, so the type test sits in a - /// conjunction instead of a branch. On a match, typeTest is the innermost `bit.and`, - /// whose right operand is the filter that remains once the test moves to the catch type. + /// Only emitted for On Error Resume Next/GoTo. /// - bool MatchTypeTestConjunction(ILVariable exceptionVar, BlockContainer container, - out IType exceptionType, out BinaryNumericInstruction typeTest) + bool MatchVBOnErrorCatchFilter(ILTransformContext context, ILVariable exceptionVar, BlockContainer container, out IType exceptionType, out BinaryNumericInstruction typeTest) { exceptionType = null; typeTest = null; @@ -108,38 +103,31 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (!entryPoint.Instructions[0].MatchLeave(container, out var condition)) return false; - // Once the test moves to the catch type, the other conjuncts stop running for a - // non-matching exception, which is only invisible if they have no side effects. - if (!SemanticHelper.IsPure(condition.Flags)) + if (condition is not BinaryNumericInstruction { Operator: BinaryNumericOperator.BitAnd, Left: BinaryNumericInstruction { Operator: BinaryNumericOperator.BitAnd } bitAnd } outer) return false; - // The test is the leftmost operand of a left-nested chain of `&`. - while (condition is BinaryNumericInstruction { Operator: BinaryNumericOperator.BitAnd } and) - { - if (MatchTypeTest(and.Left, exceptionVar, out exceptionType)) - { - typeTest = and; - return true; - } - condition = and.Left; - } - return false; + if (!outer.Right.MatchCompUnsignedZero(ComparisonKind.Equality, out var resumeTarget) || !resumeTarget.MatchLdLoc(out _)) + return false; + if (!bitAnd.Right.MatchCompUnsignedZero(ComparisonKind.Inequality, out var activeHandler) || !activeHandler.MatchLdLoc(out _)) + return false; + if (bitAnd.Left is not Comp comp) + return false; + EarlyExpressionTransforms.FixComparisonKindLdNull(comp, context); + if (!MatchIsInstNotNull(comp, exceptionVar, out _, out exceptionType) || !exceptionType.IsKnownType(KnownTypeCode.Exception)) + return false; + typeTest = bitAnd; + return true; } /// - /// comp(isinst T(ldloc exceptionVar) != ldnull), however the compiler spelled the null test. + /// comp(isinst exceptionType(ldloc exceptionVar) != ldnull) /// - static bool MatchTypeTest(ILInstruction condition, ILVariable exceptionVar, out IType exceptionType) + static bool MatchIsInstNotNull(ILInstruction condition, ILVariable exceptionVar, out ILInstruction exceptionSlot, out IType exceptionType) { + exceptionSlot = null; exceptionType = null; - if (condition is not Comp comp || !comp.Right.MatchLdNull()) - return false; - // `cgt.un x, null` is how both compilers spell `x != null` for a reference. - if (comp.Kind != ComparisonKind.Inequality - && !(comp.Kind == ComparisonKind.GreaterThan && comp.InputType == StackType.O)) - return false; - if (!comp.Left.MatchIsInst(out var argument, out exceptionType)) - return false; - return argument.MatchLdLoc(exceptionVar); + return condition.MatchCompNotEqualsNull(out var arg) + && arg.MatchIsInst(out exceptionSlot, out exceptionType) + && exceptionSlot.MatchLdLoc(exceptionVar); } /// @@ -263,18 +251,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms // br falseBlock if (!entryPoint.Instructions[0].MatchIfInstruction(out var condition, out var branch)) return false; - if (!condition.MatchCompNotEquals(out var left, out var right)) + if (!MatchIsInstNotNull(condition, exceptionVar, out exceptionSlot, out exceptionType)) return false; if (!entryPoint.Instructions[1].MatchBranch(out var falseBlock) || !MatchFalseBlock(container, falseBlock, out var returnVar, out var exitBlock)) return false; - if (!left.MatchIsInst(out exceptionSlot, out exceptionType)) - return false; - if (!exceptionSlot.MatchLdLoc(exceptionVar)) - return false; - if (right.MatchLdNull()) - { - return branch.MatchBranch(out whenConditionBlock); - } + return branch.MatchBranch(out whenConditionBlock); } return false; }