Browse Source

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
pull/3889/head v11.0-preview1
Siegfried Pammer 2 months ago committed by Siegfried Pammer
parent
commit
7554c1b004
  1. 4
      ICSharpCode.Decompiler.Tests/TestCases/PdbGen/MemberInitializerEvents.cs
  2. 21
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  3. 278
      ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs

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

@ -6,12 +6,12 @@ internal class MemberInitializerEvents @@ -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)

21
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -609,20 +609,6 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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 @@ -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);

278
ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs

@ -121,19 +121,6 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -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 @@ -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<Expression>("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<AstType>("type").Single().GetResolveResult().Type;
// ignore tuple element names, dynamic and nullability
if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(returnType, eventType))
return false;
var combineMethod = m.Get<AstNode>("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<FieldDeclaration>()
.FirstOrDefault(fd => IsEventBackingFieldDeclaration(fd, symbol));
fieldDecl?.Remove();
EventDeclaration ed = ConvertToFieldLikeEvent(ev, fieldDecl);
ev.ReplaceWith(ed);
context.EndStep(ed);
return ed;
}
/// <summary>
/// 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 <paramref name="backingFieldDecl"/> from the
/// syntax tree and for replacing <paramref name="ev"/> with the returned declaration.
/// </summary>
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)

Loading…
Cancel
Save