From 7554c1b0042dddac2a7385cea5b64f12dd52281b Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 18 Jul 2026 19:32:39 +0200 Subject: [PATCH] Remove the syntactic automatic-event patterns With recognition on the ILAst and reference substitution during translation, the four accessor-body patterns in PatternStatementTransform were only reachable as a fallback, and any divergence between them and the AutoEventDecompiler verdict produced inconsistent output. A compiler shape the ILAst matchers do not know now degrades to explicit accessors with the backing field kept in the output, which stays compilable. Bodyless events (abstract, extern, interface members) previously relied on the patterns' no-body clause to become field-like; since C# cannot express bodyless custom accessors, DoDecompile now chooses the field-like form for them directly. Also deletes the orphaned IsEventBackingFieldName helper; the name association lives in PropertyAndEventBackingFieldLookup. Assisted-by: Claude:claude-fable-5:Claude Code Claude-Session: https://claude.ai/code/session_01Btdypgm8utyxqt1Etn2BDi --- .../PdbGen/MemberInitializerEvents.cs | 4 +- .../CSharp/CSharpDecompiler.cs | 21 +- .../Transforms/PatternStatementTransform.cs | 278 ------------------ 3 files changed, 6 insertions(+), 297 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/MemberInitializerEvents.cs b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/MemberInitializerEvents.cs index bbda36917..245556605 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/MemberInitializerEvents.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/MemberInitializerEvents.cs @@ -6,12 +6,12 @@ internal class MemberInitializerEvents public MemberInitializerEvents() { - this.Changed?.Invoke(this, EventArgs.Empty); + Changed?.Invoke(this, EventArgs.Empty); } public MemberInitializerEvents(int value) { - this.Changed?.Invoke(this, EventArgs.Empty); + Changed?.Invoke(this, EventArgs.Empty); } private static void Handler(object sender, EventArgs e) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 947e4d5a9..c828ad48a 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -609,20 +609,6 @@ namespace ICSharpCode.Decompiler.CSharp return metadata.GetString(field.Name).StartsWith("<>f__switch", StringComparison.Ordinal); } - internal static bool IsEventBackingFieldName(string fieldName, string eventName, out int suffixLength) - { - suffixLength = 0; - if (fieldName == eventName) - return true; - var vbSuffixLength = "Event".Length; - if (fieldName.Length == eventName.Length + vbSuffixLength && fieldName.StartsWith(eventName, StringComparison.Ordinal) && fieldName.EndsWith("Event", StringComparison.Ordinal)) - { - suffixLength = vbSuffixLength; - return true; - } - return false; - } - static bool IsAnonymousMethodCacheField(SRM.FieldDefinition field, MetadataReader metadata) { var name = metadata.GetString(field.Name); @@ -2539,10 +2525,11 @@ namespace ICSharpCode.Decompiler.CSharp bool isAutomaticEvent = adderHasBody && removerHasBody && decompileRun.Settings.AutomaticEvents && AutoEventDecompiler.IsAutomaticEvent(typeSystem, ev, decompileRun, CancellationToken, out backingField); // A recognized automatic event is built in field-like form directly; its - // compiler-generated accessor bodies are never decompiled. + // compiler-generated accessor bodies are never decompiled. Accessors without + // bodies (abstract, extern, interface members) cannot be expressed as custom + // accessors in C#, so only the field-like form is valid for them as well. typeSystemAstBuilder.UseCustomEvents = !isAutomaticEvent - && (ev.DeclaringTypeDefinition!.Kind != TypeKind.Interface - || ev.IsExplicitInterfaceImplementation + && (ev.IsExplicitInterfaceImplementation || adderHasBody || removerHasBody); var eventDecl = typeSystemAstBuilder.ConvertEntity(ev); diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs index 2f868b6d4..41e7a28b8 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs @@ -121,19 +121,6 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms return base.VisitPropertyDeclaration(propertyDeclaration); } - public override AstNode VisitCustomEventDeclaration(CustomEventDeclaration eventDeclaration) - { - // first apply transforms to the accessor bodies - base.VisitCustomEventDeclaration(eventDeclaration); - if (context.Settings.AutomaticEvents) - { - AstNode? result = TransformAutomaticEvents(eventDeclaration); - if (result != null) - return result; - } - return eventDeclaration; - } - public override AstNode VisitEventDeclaration(EventDeclaration eventDeclaration) { // A field-like event declaration hides its backing field; remove the field declaration @@ -922,276 +909,11 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } #region Automatic Events - static readonly Expression fieldReferencePattern = new Choice { - new IdentifierExpression(Pattern.AnyString), - new MemberReferenceExpression { - Target = new Choice { new ThisReferenceExpression(), new TypeReferenceExpression { Type = new AnyNode() } }, - MemberName = Pattern.AnyString - } - }; - - static readonly Accessor automaticEventPatternV2 = new Accessor { - Attributes = { new Repeat(new AnyNode()) }, - Body = new BlockStatement { - new AssignmentExpression { - Left = new NamedNode("field", fieldReferencePattern), - Operator = AssignmentOperatorType.Assign, - Right = new CastExpression( - new AnyNode("type"), - new InvocationExpression(new AnyNode("delegateCombine").ToExpression(), new Backreference("field"), new IdentifierExpression("value")) - ) - }, - } - }; - - static readonly Accessor automaticEventPatternV4 = new Accessor { - Attributes = { new Repeat(new AnyNode()) }, - Body = new BlockStatement { - new AssignmentExpression { - Left = new NamedNode("var1", new IdentifierExpression(Pattern.AnyString)), - Operator = AssignmentOperatorType.Assign, - Right = new NamedNode("field", fieldReferencePattern) - }, - new DoWhileStatement { - EmbeddedStatement = new BlockStatement { - new AssignmentExpression(new NamedNode("var2", new IdentifierExpression(Pattern.AnyString)), new IdentifierExpressionBackreference("var1")), - new AssignmentExpression { - Left = new NamedNode("var3", new IdentifierExpression(Pattern.AnyString)), - Operator = AssignmentOperatorType.Assign, - Right = new CastExpression(new AnyNode("type"), new InvocationExpression(new AnyNode("delegateCombine").ToExpression(), new IdentifierExpressionBackreference("var2"), new IdentifierExpression("value"))) - }, - new AssignmentExpression { - Left = new IdentifierExpressionBackreference("var1"), - Right = new InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Interlocked)).ToType()), - "CompareExchange"), - new Expression[] { // arguments - new DirectionExpression { FieldDirection = FieldDirection.Ref, Expression = new Backreference("field") }, - new IdentifierExpressionBackreference("var3"), - new IdentifierExpressionBackreference("var2") - } - )} - }, - Condition = new BinaryOperatorExpression { - Left = new CastExpression(new TypePattern(typeof(object)), new IdentifierExpressionBackreference("var1")), - Operator = BinaryOperatorType.InEquality, - Right = new IdentifierExpressionBackreference("var2") - }, - } - } - }; - - static readonly Accessor automaticEventPatternV4AggressivelyInlined = new Accessor { - Attributes = { new Repeat(new AnyNode()) }, - Body = new BlockStatement { - new AssignmentExpression { - Left = new NamedNode("var1", new IdentifierExpression(Pattern.AnyString)), - Operator = AssignmentOperatorType.Assign, - Right = new NamedNode("field", fieldReferencePattern) - }, - new DoWhileStatement { - EmbeddedStatement = new BlockStatement { - new AssignmentExpression(new NamedNode("var2", new IdentifierExpression(Pattern.AnyString)), new IdentifierExpressionBackreference("var1")), - new AssignmentExpression { - Left = new IdentifierExpressionBackreference("var1"), - Right = new InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Interlocked)).ToType()), - "CompareExchange"), - new Expression[] { // arguments - new NamedArgumentExpression("value", new CastExpression(new AnyNode("type"), new InvocationExpression(new AnyNode("delegateCombine").ToExpression(), new IdentifierExpressionBackreference("var2"), new IdentifierExpression("value")))), - new NamedArgumentExpression("location1", new DirectionExpression { FieldDirection = FieldDirection.Ref, Expression = new Backreference("field") }), - new NamedArgumentExpression("comparand", new IdentifierExpressionBackreference("var2")) - } - )} - }, - Condition = new BinaryOperatorExpression { - Left = new CastExpression(new TypePattern(typeof(object)), new IdentifierExpressionBackreference("var1")), - Operator = BinaryOperatorType.InEquality, - Right = new IdentifierExpressionBackreference("var2") - }, - } - } - }; - - static readonly Accessor automaticEventPatternV4MCS = new Accessor { - Attributes = { new Repeat(new AnyNode()) }, - Body = new BlockStatement { - new AssignmentExpression { - Left = new NamedNode("var1", new IdentifierExpression(Pattern.AnyString)), - Operator = AssignmentOperatorType.Assign, - Right = new NamedNode( - "field", - new MemberReferenceExpression { - Target = new Choice { new ThisReferenceExpression(), new TypeReferenceExpression { Type = new AnyNode() } }, - MemberName = Pattern.AnyString - } - ) - }, - new DoWhileStatement { - EmbeddedStatement = new BlockStatement { - new AssignmentExpression(new NamedNode("var2", new IdentifierExpression(Pattern.AnyString)), new IdentifierExpressionBackreference("var1")), - new AssignmentExpression { - Left = new IdentifierExpressionBackreference("var1"), - Right = new InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Interlocked)).ToType()), - "CompareExchange", - new AstType[] { new Repeat(new AnyNode()) }), // optional type arguments - new Expression[] { // arguments - new DirectionExpression { FieldDirection = FieldDirection.Ref, Expression = new Backreference("field") }, - new CastExpression(new AnyNode("type"), new InvocationExpression(new AnyNode("delegateCombine").ToExpression(), new IdentifierExpressionBackreference("var2"), new IdentifierExpression("value"))), - new IdentifierExpressionBackreference("var1") - } - ) - } - }, - Condition = new BinaryOperatorExpression { - Left = new CastExpression(new TypePattern(typeof(object)), new IdentifierExpressionBackreference("var1")), - Operator = BinaryOperatorType.InEquality, - Right = new IdentifierExpressionBackreference("var2") - }, - } - } - }; - - bool CheckAutomaticEventMatch(Match m, CustomEventDeclaration ev, bool isAddAccessor) - { - if (!m.Success) - return false; - Expression fieldExpression = m.Get("field").Single(); - IField? eventField = fieldExpression.GetSymbol() as IField; - if (eventField == null) - return false; - var module = eventField.ParentModule as MetadataModule; - if (module == null) - return false; - if (!module.MetadataFile.PropertyAndEventBackingFieldLookup.IsEventBackingField((FieldDefinitionHandle)eventField.MetadataToken, out _)) - return false; - var returnType = ev.ReturnType.GetResolveResult().Type; - var eventType = m.Get("type").Single().GetResolveResult().Type; - // ignore tuple element names, dynamic and nullability - if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(returnType, eventType)) - return false; - var combineMethod = m.Get("delegateCombine").Single().Parent!.GetSymbol() as IMethod; - if (combineMethod == null || combineMethod.Name != (isAddAccessor ? "Combine" : "Remove")) - return false; - return combineMethod.DeclaringType.FullName == "System.Delegate"; - } - - static readonly string[] attributeTypesToRemoveFromAutoEvents = new[] { - "System.Runtime.CompilerServices.CompilerGeneratedAttribute", - "System.Diagnostics.DebuggerBrowsableAttribute", - "System.Runtime.CompilerServices.MethodImplAttribute" - }; - internal static readonly string[] attributeTypesToRemoveFromAutoProperties = new[] { "System.Runtime.CompilerServices.CompilerGeneratedAttribute", "System.Diagnostics.DebuggerBrowsableAttribute" }; - bool CheckAutomaticEventV4(CustomEventDeclaration ev) - { - Match addMatch = automaticEventPatternV4.Match(ev.AddAccessor); - if (!CheckAutomaticEventMatch(addMatch, ev, isAddAccessor: true)) - return false; - Match removeMatch = automaticEventPatternV4.Match(ev.RemoveAccessor); - if (!CheckAutomaticEventMatch(removeMatch, ev, isAddAccessor: false)) - return false; - return true; - } - - bool CheckAutomaticEventV4AggressivelyInlined(CustomEventDeclaration ev) - { - if (!context.Settings.AggressiveInlining) - return false; - Match addMatch = automaticEventPatternV4AggressivelyInlined.Match(ev.AddAccessor); - if (!CheckAutomaticEventMatch(addMatch, ev, isAddAccessor: true)) - return false; - Match removeMatch = automaticEventPatternV4AggressivelyInlined.Match(ev.RemoveAccessor); - if (!CheckAutomaticEventMatch(removeMatch, ev, isAddAccessor: false)) - return false; - return true; - } - - bool CheckAutomaticEventV2(CustomEventDeclaration ev) - { - Match addMatch = automaticEventPatternV2.Match(ev.AddAccessor); - if (!CheckAutomaticEventMatch(addMatch, ev, isAddAccessor: true)) - return false; - Match removeMatch = automaticEventPatternV2.Match(ev.RemoveAccessor); - if (!CheckAutomaticEventMatch(removeMatch, ev, isAddAccessor: false)) - return false; - return true; - } - - bool CheckAutomaticEventV4MCS(CustomEventDeclaration ev) - { - Match addMatch = automaticEventPatternV4MCS.Match(ev.AddAccessor); - if (!CheckAutomaticEventMatch(addMatch, ev, true)) - return false; - Match removeMatch = automaticEventPatternV4MCS.Match(ev.RemoveAccessor); - if (!CheckAutomaticEventMatch(removeMatch, ev, false)) - return false; - return true; - } - - EventDeclaration? TransformAutomaticEvents(CustomEventDeclaration ev) - { - if (ev.PrivateImplementationType is not null) - return null; - const Modifiers withoutBody = Modifiers.Abstract | Modifiers.Extern; - if (ev.GetSymbol() is not IEvent symbol) - return null; - if ((ev.Modifiers & withoutBody) == 0) - { - if (!CheckAutomaticEventV4AggressivelyInlined(ev) && !CheckAutomaticEventV4(ev) && !CheckAutomaticEventV2(ev) && !CheckAutomaticEventV4MCS(ev)) - return null; - } - if (ev.AddAccessor is not { }) - return null; - context.Step("Convert custom event to field-like event", ev); - var fieldDecl = ev.Parent?.Children.OfType() - .FirstOrDefault(fd => IsEventBackingFieldDeclaration(fd, symbol)); - fieldDecl?.Remove(); - EventDeclaration ed = ConvertToFieldLikeEvent(ev, fieldDecl); - ev.ReplaceWith(ed); - context.EndStep(ed); - return ed; - } - - /// - /// Builds a field-like event declaration from a custom event declaration whose accessors - /// are compiler-generated: moves the event attributes, the add-accessor attributes - /// (as "method:" sections) and the backing-field attributes (as "field:" sections) over, - /// dropping the attributes the compiler puts on automatic events. - /// The caller is responsible for detaching from the - /// syntax tree and for replacing with the returned declaration. - /// - internal static EventDeclaration ConvertToFieldLikeEvent(CustomEventDeclaration ev, EntityDeclaration? backingFieldDecl) - { - var addAccessor = ev.AddAccessor!; - RemoveCompilerGeneratedAttribute(addAccessor.Attributes, attributeTypesToRemoveFromAutoEvents); - EventDeclaration ed = new EventDeclaration(); - ev.Attributes.MoveTo(ed.Attributes); - foreach (var attr in addAccessor.Attributes) - { - attr.AttributeTarget = "method"; - ed.Attributes.Add(attr.Detach()); - } - ed.ReturnType = ev.ReturnType.Detach(); - ed.Modifiers = ev.Modifiers; - ed.Variables.Add(new VariableInitializer(ev.Name)); - ed.CopyAnnotationsFrom(ev); - - if (backingFieldDecl != null) - { - CSharpDecompiler.RemoveAttribute(backingFieldDecl, KnownAttribute.CompilerGenerated); - CSharpDecompiler.RemoveAttribute(backingFieldDecl, KnownAttribute.DebuggerBrowsable); - foreach (var section in backingFieldDecl.Attributes) - { - section.AttributeTarget = "field"; - ed.Attributes.Add(section.Detach()); - } - } - return ed; - } - static bool IsEventBackingFieldDeclaration(FieldDeclaration fd, IEvent ev) { if (fd.Variables.Count > 1)