Skip to content

Commit 7554c1b

Browse files
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
1 parent 461df88 commit 7554c1b

3 files changed

Lines changed: 6 additions & 297 deletions

File tree

ICSharpCode.Decompiler.Tests/TestCases/PdbGen/MemberInitializerEvents.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ internal class MemberInitializerEvents
66

77
public MemberInitializerEvents()
88
{
9-
this.Changed?.Invoke(this, EventArgs.Empty);
9+
Changed?.Invoke(this, EventArgs.Empty);
1010
}
1111

1212
public MemberInitializerEvents(int value)
1313
{
14-
this.Changed?.Invoke(this, EventArgs.Empty);
14+
Changed?.Invoke(this, EventArgs.Empty);
1515
}
1616

1717
private static void Handler(object sender, EventArgs e)

ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -609,20 +609,6 @@ static bool IsSwitchOnStringCache(SRM.FieldDefinition field, MetadataReader meta
609609
return metadata.GetString(field.Name).StartsWith("<>f__switch", StringComparison.Ordinal);
610610
}
611611

612-
internal static bool IsEventBackingFieldName(string fieldName, string eventName, out int suffixLength)
613-
{
614-
suffixLength = 0;
615-
if (fieldName == eventName)
616-
return true;
617-
var vbSuffixLength = "Event".Length;
618-
if (fieldName.Length == eventName.Length + vbSuffixLength && fieldName.StartsWith(eventName, StringComparison.Ordinal) && fieldName.EndsWith("Event", StringComparison.Ordinal))
619-
{
620-
suffixLength = vbSuffixLength;
621-
return true;
622-
}
623-
return false;
624-
}
625-
626612
static bool IsAnonymousMethodCacheField(SRM.FieldDefinition field, MetadataReader metadata)
627613
{
628614
var name = metadata.GetString(field.Name);
@@ -2539,10 +2525,11 @@ EntityDeclaration DoDecompile(IEvent ev, DecompileRun decompileRun, ITypeResolve
25392525
bool isAutomaticEvent = adderHasBody && removerHasBody && decompileRun.Settings.AutomaticEvents
25402526
&& AutoEventDecompiler.IsAutomaticEvent(typeSystem, ev, decompileRun, CancellationToken, out backingField);
25412527
// A recognized automatic event is built in field-like form directly; its
2542-
// compiler-generated accessor bodies are never decompiled.
2528+
// compiler-generated accessor bodies are never decompiled. Accessors without
2529+
// bodies (abstract, extern, interface members) cannot be expressed as custom
2530+
// accessors in C#, so only the field-like form is valid for them as well.
25432531
typeSystemAstBuilder.UseCustomEvents = !isAutomaticEvent
2544-
&& (ev.DeclaringTypeDefinition!.Kind != TypeKind.Interface
2545-
|| ev.IsExplicitInterfaceImplementation
2532+
&& (ev.IsExplicitInterfaceImplementation
25462533
|| adderHasBody
25472534
|| removerHasBody);
25482535
var eventDecl = typeSystemAstBuilder.ConvertEntity(ev);

ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs

Lines changed: 0 additions & 278 deletions
Original file line numberDiff line numberDiff line change
@@ -121,19 +121,6 @@ public override AstNode VisitPropertyDeclaration(PropertyDeclaration propertyDec
121121
return base.VisitPropertyDeclaration(propertyDeclaration);
122122
}
123123

124-
public override AstNode VisitCustomEventDeclaration(CustomEventDeclaration eventDeclaration)
125-
{
126-
// first apply transforms to the accessor bodies
127-
base.VisitCustomEventDeclaration(eventDeclaration);
128-
if (context.Settings.AutomaticEvents)
129-
{
130-
AstNode? result = TransformAutomaticEvents(eventDeclaration);
131-
if (result != null)
132-
return result;
133-
}
134-
return eventDeclaration;
135-
}
136-
137124
public override AstNode VisitEventDeclaration(EventDeclaration eventDeclaration)
138125
{
139126
// A field-like event declaration hides its backing field; remove the field declaration
@@ -922,276 +909,11 @@ static bool NameCouldBeBackingFieldOfAutomaticProperty(string name, [NotNullWhen
922909
}
923910

924911
#region Automatic Events
925-
static readonly Expression fieldReferencePattern = new Choice {
926-
new IdentifierExpression(Pattern.AnyString),
927-
new MemberReferenceExpression {
928-
Target = new Choice { new ThisReferenceExpression(), new TypeReferenceExpression { Type = new AnyNode() } },
929-
MemberName = Pattern.AnyString
930-
}
931-
};
932-
933-
static readonly Accessor automaticEventPatternV2 = new Accessor {
934-
Attributes = { new Repeat(new AnyNode()) },
935-
Body = new BlockStatement {
936-
new AssignmentExpression {
937-
Left = new NamedNode("field", fieldReferencePattern),
938-
Operator = AssignmentOperatorType.Assign,
939-
Right = new CastExpression(
940-
new AnyNode("type"),
941-
new InvocationExpression(new AnyNode("delegateCombine").ToExpression(), new Backreference("field"), new IdentifierExpression("value"))
942-
)
943-
},
944-
}
945-
};
946-
947-
static readonly Accessor automaticEventPatternV4 = new Accessor {
948-
Attributes = { new Repeat(new AnyNode()) },
949-
Body = new BlockStatement {
950-
new AssignmentExpression {
951-
Left = new NamedNode("var1", new IdentifierExpression(Pattern.AnyString)),
952-
Operator = AssignmentOperatorType.Assign,
953-
Right = new NamedNode("field", fieldReferencePattern)
954-
},
955-
new DoWhileStatement {
956-
EmbeddedStatement = new BlockStatement {
957-
new AssignmentExpression(new NamedNode("var2", new IdentifierExpression(Pattern.AnyString)), new IdentifierExpressionBackreference("var1")),
958-
new AssignmentExpression {
959-
Left = new NamedNode("var3", new IdentifierExpression(Pattern.AnyString)),
960-
Operator = AssignmentOperatorType.Assign,
961-
Right = new CastExpression(new AnyNode("type"), new InvocationExpression(new AnyNode("delegateCombine").ToExpression(), new IdentifierExpressionBackreference("var2"), new IdentifierExpression("value")))
962-
},
963-
new AssignmentExpression {
964-
Left = new IdentifierExpressionBackreference("var1"),
965-
Right = new InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Interlocked)).ToType()),
966-
"CompareExchange"),
967-
new Expression[] { // arguments
968-
new DirectionExpression { FieldDirection = FieldDirection.Ref, Expression = new Backreference("field") },
969-
new IdentifierExpressionBackreference("var3"),
970-
new IdentifierExpressionBackreference("var2")
971-
}
972-
)}
973-
},
974-
Condition = new BinaryOperatorExpression {
975-
Left = new CastExpression(new TypePattern(typeof(object)), new IdentifierExpressionBackreference("var1")),
976-
Operator = BinaryOperatorType.InEquality,
977-
Right = new IdentifierExpressionBackreference("var2")
978-
},
979-
}
980-
}
981-
};
982-
983-
static readonly Accessor automaticEventPatternV4AggressivelyInlined = new Accessor {
984-
Attributes = { new Repeat(new AnyNode()) },
985-
Body = new BlockStatement {
986-
new AssignmentExpression {
987-
Left = new NamedNode("var1", new IdentifierExpression(Pattern.AnyString)),
988-
Operator = AssignmentOperatorType.Assign,
989-
Right = new NamedNode("field", fieldReferencePattern)
990-
},
991-
new DoWhileStatement {
992-
EmbeddedStatement = new BlockStatement {
993-
new AssignmentExpression(new NamedNode("var2", new IdentifierExpression(Pattern.AnyString)), new IdentifierExpressionBackreference("var1")),
994-
new AssignmentExpression {
995-
Left = new IdentifierExpressionBackreference("var1"),
996-
Right = new InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Interlocked)).ToType()),
997-
"CompareExchange"),
998-
new Expression[] { // arguments
999-
new NamedArgumentExpression("value", new CastExpression(new AnyNode("type"), new InvocationExpression(new AnyNode("delegateCombine").ToExpression(), new IdentifierExpressionBackreference("var2"), new IdentifierExpression("value")))),
1000-
new NamedArgumentExpression("location1", new DirectionExpression { FieldDirection = FieldDirection.Ref, Expression = new Backreference("field") }),
1001-
new NamedArgumentExpression("comparand", new IdentifierExpressionBackreference("var2"))
1002-
}
1003-
)}
1004-
},
1005-
Condition = new BinaryOperatorExpression {
1006-
Left = new CastExpression(new TypePattern(typeof(object)), new IdentifierExpressionBackreference("var1")),
1007-
Operator = BinaryOperatorType.InEquality,
1008-
Right = new IdentifierExpressionBackreference("var2")
1009-
},
1010-
}
1011-
}
1012-
};
1013-
1014-
static readonly Accessor automaticEventPatternV4MCS = new Accessor {
1015-
Attributes = { new Repeat(new AnyNode()) },
1016-
Body = new BlockStatement {
1017-
new AssignmentExpression {
1018-
Left = new NamedNode("var1", new IdentifierExpression(Pattern.AnyString)),
1019-
Operator = AssignmentOperatorType.Assign,
1020-
Right = new NamedNode(
1021-
"field",
1022-
new MemberReferenceExpression {
1023-
Target = new Choice { new ThisReferenceExpression(), new TypeReferenceExpression { Type = new AnyNode() } },
1024-
MemberName = Pattern.AnyString
1025-
}
1026-
)
1027-
},
1028-
new DoWhileStatement {
1029-
EmbeddedStatement = new BlockStatement {
1030-
new AssignmentExpression(new NamedNode("var2", new IdentifierExpression(Pattern.AnyString)), new IdentifierExpressionBackreference("var1")),
1031-
new AssignmentExpression {
1032-
Left = new IdentifierExpressionBackreference("var1"),
1033-
Right = new InvocationExpression(new MemberReferenceExpression(new TypeReferenceExpression(new TypePattern(typeof(System.Threading.Interlocked)).ToType()),
1034-
"CompareExchange",
1035-
new AstType[] { new Repeat(new AnyNode()) }), // optional type arguments
1036-
new Expression[] { // arguments
1037-
new DirectionExpression { FieldDirection = FieldDirection.Ref, Expression = new Backreference("field") },
1038-
new CastExpression(new AnyNode("type"), new InvocationExpression(new AnyNode("delegateCombine").ToExpression(), new IdentifierExpressionBackreference("var2"), new IdentifierExpression("value"))),
1039-
new IdentifierExpressionBackreference("var1")
1040-
}
1041-
)
1042-
}
1043-
},
1044-
Condition = new BinaryOperatorExpression {
1045-
Left = new CastExpression(new TypePattern(typeof(object)), new IdentifierExpressionBackreference("var1")),
1046-
Operator = BinaryOperatorType.InEquality,
1047-
Right = new IdentifierExpressionBackreference("var2")
1048-
},
1049-
}
1050-
}
1051-
};
1052-
1053-
bool CheckAutomaticEventMatch(Match m, CustomEventDeclaration ev, bool isAddAccessor)
1054-
{
1055-
if (!m.Success)
1056-
return false;
1057-
Expression fieldExpression = m.Get<Expression>("field").Single();
1058-
IField? eventField = fieldExpression.GetSymbol() as IField;
1059-
if (eventField == null)
1060-
return false;
1061-
var module = eventField.ParentModule as MetadataModule;
1062-
if (module == null)
1063-
return false;
1064-
if (!module.MetadataFile.PropertyAndEventBackingFieldLookup.IsEventBackingField((FieldDefinitionHandle)eventField.MetadataToken, out _))
1065-
return false;
1066-
var returnType = ev.ReturnType.GetResolveResult().Type;
1067-
var eventType = m.Get<AstType>("type").Single().GetResolveResult().Type;
1068-
// ignore tuple element names, dynamic and nullability
1069-
if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(returnType, eventType))
1070-
return false;
1071-
var combineMethod = m.Get<AstNode>("delegateCombine").Single().Parent!.GetSymbol() as IMethod;
1072-
if (combineMethod == null || combineMethod.Name != (isAddAccessor ? "Combine" : "Remove"))
1073-
return false;
1074-
return combineMethod.DeclaringType.FullName == "System.Delegate";
1075-
}
1076-
1077-
static readonly string[] attributeTypesToRemoveFromAutoEvents = new[] {
1078-
"System.Runtime.CompilerServices.CompilerGeneratedAttribute",
1079-
"System.Diagnostics.DebuggerBrowsableAttribute",
1080-
"System.Runtime.CompilerServices.MethodImplAttribute"
1081-
};
1082-
1083912
internal static readonly string[] attributeTypesToRemoveFromAutoProperties = new[] {
1084913
"System.Runtime.CompilerServices.CompilerGeneratedAttribute",
1085914
"System.Diagnostics.DebuggerBrowsableAttribute"
1086915
};
1087916

1088-
bool CheckAutomaticEventV4(CustomEventDeclaration ev)
1089-
{
1090-
Match addMatch = automaticEventPatternV4.Match(ev.AddAccessor);
1091-
if (!CheckAutomaticEventMatch(addMatch, ev, isAddAccessor: true))
1092-
return false;
1093-
Match removeMatch = automaticEventPatternV4.Match(ev.RemoveAccessor);
1094-
if (!CheckAutomaticEventMatch(removeMatch, ev, isAddAccessor: false))
1095-
return false;
1096-
return true;
1097-
}
1098-
1099-
bool CheckAutomaticEventV4AggressivelyInlined(CustomEventDeclaration ev)
1100-
{
1101-
if (!context.Settings.AggressiveInlining)
1102-
return false;
1103-
Match addMatch = automaticEventPatternV4AggressivelyInlined.Match(ev.AddAccessor);
1104-
if (!CheckAutomaticEventMatch(addMatch, ev, isAddAccessor: true))
1105-
return false;
1106-
Match removeMatch = automaticEventPatternV4AggressivelyInlined.Match(ev.RemoveAccessor);
1107-
if (!CheckAutomaticEventMatch(removeMatch, ev, isAddAccessor: false))
1108-
return false;
1109-
return true;
1110-
}
1111-
1112-
bool CheckAutomaticEventV2(CustomEventDeclaration ev)
1113-
{
1114-
Match addMatch = automaticEventPatternV2.Match(ev.AddAccessor);
1115-
if (!CheckAutomaticEventMatch(addMatch, ev, isAddAccessor: true))
1116-
return false;
1117-
Match removeMatch = automaticEventPatternV2.Match(ev.RemoveAccessor);
1118-
if (!CheckAutomaticEventMatch(removeMatch, ev, isAddAccessor: false))
1119-
return false;
1120-
return true;
1121-
}
1122-
1123-
bool CheckAutomaticEventV4MCS(CustomEventDeclaration ev)
1124-
{
1125-
Match addMatch = automaticEventPatternV4MCS.Match(ev.AddAccessor);
1126-
if (!CheckAutomaticEventMatch(addMatch, ev, true))
1127-
return false;
1128-
Match removeMatch = automaticEventPatternV4MCS.Match(ev.RemoveAccessor);
1129-
if (!CheckAutomaticEventMatch(removeMatch, ev, false))
1130-
return false;
1131-
return true;
1132-
}
1133-
1134-
EventDeclaration? TransformAutomaticEvents(CustomEventDeclaration ev)
1135-
{
1136-
if (ev.PrivateImplementationType is not null)
1137-
return null;
1138-
const Modifiers withoutBody = Modifiers.Abstract | Modifiers.Extern;
1139-
if (ev.GetSymbol() is not IEvent symbol)
1140-
return null;
1141-
if ((ev.Modifiers & withoutBody) == 0)
1142-
{
1143-
if (!CheckAutomaticEventV4AggressivelyInlined(ev) && !CheckAutomaticEventV4(ev) && !CheckAutomaticEventV2(ev) && !CheckAutomaticEventV4MCS(ev))
1144-
return null;
1145-
}
1146-
if (ev.AddAccessor is not { })
1147-
return null;
1148-
context.Step("Convert custom event to field-like event", ev);
1149-
var fieldDecl = ev.Parent?.Children.OfType<FieldDeclaration>()
1150-
.FirstOrDefault(fd => IsEventBackingFieldDeclaration(fd, symbol));
1151-
fieldDecl?.Remove();
1152-
EventDeclaration ed = ConvertToFieldLikeEvent(ev, fieldDecl);
1153-
ev.ReplaceWith(ed);
1154-
context.EndStep(ed);
1155-
return ed;
1156-
}
1157-
1158-
/// <summary>
1159-
/// Builds a field-like event declaration from a custom event declaration whose accessors
1160-
/// are compiler-generated: moves the event attributes, the add-accessor attributes
1161-
/// (as "method:" sections) and the backing-field attributes (as "field:" sections) over,
1162-
/// dropping the attributes the compiler puts on automatic events.
1163-
/// The caller is responsible for detaching <paramref name="backingFieldDecl"/> from the
1164-
/// syntax tree and for replacing <paramref name="ev"/> with the returned declaration.
1165-
/// </summary>
1166-
internal static EventDeclaration ConvertToFieldLikeEvent(CustomEventDeclaration ev, EntityDeclaration? backingFieldDecl)
1167-
{
1168-
var addAccessor = ev.AddAccessor!;
1169-
RemoveCompilerGeneratedAttribute(addAccessor.Attributes, attributeTypesToRemoveFromAutoEvents);
1170-
EventDeclaration ed = new EventDeclaration();
1171-
ev.Attributes.MoveTo(ed.Attributes);
1172-
foreach (var attr in addAccessor.Attributes)
1173-
{
1174-
attr.AttributeTarget = "method";
1175-
ed.Attributes.Add(attr.Detach());
1176-
}
1177-
ed.ReturnType = ev.ReturnType.Detach();
1178-
ed.Modifiers = ev.Modifiers;
1179-
ed.Variables.Add(new VariableInitializer(ev.Name));
1180-
ed.CopyAnnotationsFrom(ev);
1181-
1182-
if (backingFieldDecl != null)
1183-
{
1184-
CSharpDecompiler.RemoveAttribute(backingFieldDecl, KnownAttribute.CompilerGenerated);
1185-
CSharpDecompiler.RemoveAttribute(backingFieldDecl, KnownAttribute.DebuggerBrowsable);
1186-
foreach (var section in backingFieldDecl.Attributes)
1187-
{
1188-
section.AttributeTarget = "field";
1189-
ed.Attributes.Add(section.Detach());
1190-
}
1191-
}
1192-
return ed;
1193-
}
1194-
1195917
static bool IsEventBackingFieldDeclaration(FieldDeclaration fd, IEvent ev)
1196918
{
1197919
if (fd.Variables.Count > 1)

0 commit comments

Comments
 (0)