|
|
@ -37,6 +37,8 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
readonly CancellationToken cancellationToken; |
|
|
|
readonly CancellationToken cancellationToken; |
|
|
|
readonly List<IMember> orderedMembers; |
|
|
|
readonly List<IMember> orderedMembers; |
|
|
|
readonly bool isInheritedRecord; |
|
|
|
readonly bool isInheritedRecord; |
|
|
|
|
|
|
|
readonly bool isStruct; |
|
|
|
|
|
|
|
readonly bool isSealed; |
|
|
|
readonly IMethod primaryCtor; |
|
|
|
readonly IMethod primaryCtor; |
|
|
|
readonly IType baseClass; |
|
|
|
readonly IType baseClass; |
|
|
|
readonly Dictionary<IField, IProperty> backingFieldToAutoProperty = new Dictionary<IField, IProperty>(); |
|
|
|
readonly Dictionary<IField, IProperty> backingFieldToAutoProperty = new Dictionary<IField, IProperty>(); |
|
|
@ -51,7 +53,9 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
this.settings = settings; |
|
|
|
this.settings = settings; |
|
|
|
this.cancellationToken = cancellationToken; |
|
|
|
this.cancellationToken = cancellationToken; |
|
|
|
this.baseClass = recordTypeDef.DirectBaseTypes.FirstOrDefault(b => b.Kind == TypeKind.Class); |
|
|
|
this.baseClass = recordTypeDef.DirectBaseTypes.FirstOrDefault(b => b.Kind == TypeKind.Class); |
|
|
|
this.isInheritedRecord = !baseClass.IsKnownType(KnownTypeCode.Object); |
|
|
|
this.isStruct = baseClass.IsKnownType(KnownTypeCode.ValueType); |
|
|
|
|
|
|
|
this.isInheritedRecord = !isStruct && !baseClass.IsKnownType(KnownTypeCode.Object); |
|
|
|
|
|
|
|
this.isSealed = recordTypeDef.IsSealed; |
|
|
|
DetectAutomaticProperties(); |
|
|
|
DetectAutomaticProperties(); |
|
|
|
this.orderedMembers = DetectMemberOrder(recordTypeDef, backingFieldToAutoProperty); |
|
|
|
this.orderedMembers = DetectMemberOrder(recordTypeDef, backingFieldToAutoProperty); |
|
|
|
this.primaryCtor = DetectPrimaryConstructor(); |
|
|
|
this.primaryCtor = DetectPrimaryConstructor(); |
|
|
@ -180,10 +184,11 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
if (method.Parameters.Count == 0) |
|
|
|
if (method.Parameters.Count == 0) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
|
|
if (body.Instructions.Count != method.Parameters.Count + 2) |
|
|
|
var addonInst = isStruct ? 1 : 2; |
|
|
|
|
|
|
|
if (body.Instructions.Count < method.Parameters.Count + addonInst) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < body.Instructions.Count - 2; i++) |
|
|
|
for (int i = 0; i < method.Parameters.Count; i++) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (!body.Instructions[i].MatchStFld(out var target, out var field, out var valueInst)) |
|
|
|
if (!body.Instructions[i].MatchStFld(out var target, out var field, out var valueInst)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
@ -199,9 +204,12 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
autoPropertyToPrimaryCtorParameter.Add(property, method.Parameters[i]); |
|
|
|
autoPropertyToPrimaryCtorParameter.Add(property, method.Parameters[i]); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var baseCtorCall = body.Instructions.SecondToLastOrDefault() as CallInstruction; |
|
|
|
if (!isStruct) |
|
|
|
if (baseCtorCall == null) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
var baseCtorCall = body.Instructions.SecondToLastOrDefault() as CallInstruction; |
|
|
|
|
|
|
|
if (baseCtorCall == null) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var returnInst = body.Instructions.LastOrDefault(); |
|
|
|
var returnInst = body.Instructions.LastOrDefault(); |
|
|
|
return returnInst != null && returnInst.MatchReturn(out var retVal) && retVal.MatchNop(); |
|
|
|
return returnInst != null && returnInst.MatchReturn(out var retVal) && retVal.MatchNop(); |
|
|
@ -233,6 +241,8 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
/// </summary>
|
|
|
|
/// </summary>
|
|
|
|
public IMethod PrimaryConstructor => primaryCtor; |
|
|
|
public IMethod PrimaryConstructor => primaryCtor; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public bool IsInheritedRecord => isInheritedRecord; |
|
|
|
|
|
|
|
|
|
|
|
bool IsRecordType(IType type) |
|
|
|
bool IsRecordType(IType type) |
|
|
|
{ |
|
|
|
{ |
|
|
|
return type.GetDefinition() == recordTypeDef |
|
|
|
return type.GetDefinition() == recordTypeDef |
|
|
@ -309,7 +319,7 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
{ |
|
|
|
{ |
|
|
|
switch (property.Name) |
|
|
|
switch (property.Name) |
|
|
|
{ |
|
|
|
{ |
|
|
|
case "EqualityContract": |
|
|
|
case "EqualityContract" when !isStruct: |
|
|
|
return IsGeneratedEqualityContract(property); |
|
|
|
return IsGeneratedEqualityContract(property); |
|
|
|
default: |
|
|
|
default: |
|
|
|
return IsPropertyDeclaredByPrimaryConstructor(property); |
|
|
|
return IsPropertyDeclaredByPrimaryConstructor(property); |
|
|
@ -333,7 +343,7 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
Debug.Assert(method.IsConstructor && method.Parameters.Count == 1); |
|
|
|
Debug.Assert(method.IsConstructor && method.Parameters.Count == 1); |
|
|
|
if (method.GetAttributes().Any() || method.GetReturnTypeAttributes().Any()) |
|
|
|
if (method.GetAttributes().Any() || method.GetReturnTypeAttributes().Any()) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (method.Accessibility != Accessibility.Protected) |
|
|
|
if (method.Accessibility != Accessibility.Protected && (!isSealed || method.Accessibility != Accessibility.Private)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (orderedMembers == null) |
|
|
|
if (orderedMembers == null) |
|
|
|
return false; |
|
|
|
return false; |
|
|
@ -393,10 +403,10 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
// protected virtual Type EqualityContract {
|
|
|
|
// protected virtual Type EqualityContract {
|
|
|
|
// [CompilerGenerated] get => typeof(R);
|
|
|
|
// [CompilerGenerated] get => typeof(R);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
Debug.Assert(property.Name == "EqualityContract"); |
|
|
|
Debug.Assert(!isStruct && property.Name == "EqualityContract"); |
|
|
|
if (property.Accessibility != Accessibility.Protected) |
|
|
|
if (property.Accessibility != Accessibility.Protected && (!isSealed || property.Accessibility != Accessibility.Private)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!(property.IsVirtual || property.IsOverride)) |
|
|
|
if (!(isSealed || property.IsVirtual || property.IsOverride)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (property.IsSealed) |
|
|
|
if (property.IsSealed) |
|
|
|
return false; |
|
|
|
return false; |
|
|
@ -428,11 +438,11 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
Debug.Assert(method.Name == "PrintMembers"); |
|
|
|
Debug.Assert(method.Name == "PrintMembers"); |
|
|
|
if (method.Parameters.Count != 1) |
|
|
|
if (method.Parameters.Count != 1) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!method.IsOverridable) |
|
|
|
if (!isSealed && !method.IsOverridable) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (method.GetAttributes().Any() || method.GetReturnTypeAttributes().Any()) |
|
|
|
if (method.GetAttributes().Any() || method.GetReturnTypeAttributes().Any()) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (method.Accessibility != Accessibility.Protected) |
|
|
|
if (method.Accessibility != Accessibility.Protected && (!isSealed || method.Accessibility != Accessibility.Private)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (orderedMembers == null) |
|
|
|
if (orderedMembers == null) |
|
|
|
return false; |
|
|
|
return false; |
|
|
@ -551,7 +561,7 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
{ |
|
|
|
{ |
|
|
|
return false; // static fields/properties are not printed
|
|
|
|
return false; // static fields/properties are not printed
|
|
|
|
} |
|
|
|
} |
|
|
|
if (member.Name == "EqualityContract") |
|
|
|
if (!isStruct && member.Name == "EqualityContract") |
|
|
|
{ |
|
|
|
{ |
|
|
|
return false; // EqualityContract is never printed
|
|
|
|
return false; // EqualityContract is never printed
|
|
|
|
} |
|
|
|
} |
|
|
@ -617,7 +627,8 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
// if (callvirt PrintMembers(ldloc this, ldloc stringBuilder)) { trueInst }
|
|
|
|
// if (callvirt PrintMembers(ldloc this, ldloc stringBuilder)) { trueInst }
|
|
|
|
if (!body.Instructions[3].MatchIfInstruction(out var condition, out var trueInst)) |
|
|
|
if (!body.Instructions[3].MatchIfInstruction(out var condition, out var trueInst)) |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
if (!(condition is CallVirt { Method: { Name: "PrintMembers" } } printMembersCall)) |
|
|
|
if (!((condition is CallInstruction { Method: { Name: "PrintMembers" } } printMembersCall) && |
|
|
|
|
|
|
|
(condition is CallVirt || (isSealed && condition is Call)))) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (printMembersCall.Arguments.Count != 2) |
|
|
|
if (printMembersCall.Arguments.Count != 2) |
|
|
|
return false; |
|
|
|
return false; |
|
|
@ -670,7 +681,7 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
Debug.Assert(method.Name == "Equals" && method.Parameters.Count == 1); |
|
|
|
Debug.Assert(method.Name == "Equals" && method.Parameters.Count == 1); |
|
|
|
if (method.Parameters.Count != 1) |
|
|
|
if (method.Parameters.Count != 1) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!method.IsOverridable) |
|
|
|
if (!isSealed && !method.IsOverridable) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (method.GetAttributes().Any() || method.GetReturnTypeAttributes().Any()) |
|
|
|
if (method.GetAttributes().Any() || method.GetReturnTypeAttributes().Any()) |
|
|
|
return false; |
|
|
|
return false; |
|
|
@ -698,58 +709,61 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
var conditions = UnpackLogicAndChain(returnValue); |
|
|
|
var conditions = UnpackLogicAndChain(returnValue); |
|
|
|
Debug.Assert(conditions.Count >= 1); |
|
|
|
Debug.Assert(conditions.Count >= 1); |
|
|
|
int pos = 0; |
|
|
|
int pos = 0; |
|
|
|
if (isInheritedRecord) |
|
|
|
if (!isStruct) |
|
|
|
{ |
|
|
|
|
|
|
|
// call BaseClass::Equals(ldloc this, ldloc other)
|
|
|
|
|
|
|
|
if (pos >= conditions.Count) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!(conditions[pos] is Call { Method: { Name: "Equals" } } call)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(call.Method.DeclaringType, baseClass)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (call.Arguments.Count != 2) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!call.Arguments[0].MatchLdThis()) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!call.Arguments[1].MatchLdLoc(other)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
pos++; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
// comp.o(ldloc other != ldnull)
|
|
|
|
if (isInheritedRecord) |
|
|
|
if (pos >= conditions.Count) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
// call BaseClass::Equals(ldloc this, ldloc other)
|
|
|
|
if (!conditions[pos].MatchCompNotEqualsNull(out var arg)) |
|
|
|
if (pos >= conditions.Count) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!arg.MatchLdLoc(other)) |
|
|
|
if (!(conditions[pos] is Call { Method: { Name: "Equals" } } call)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
pos++; |
|
|
|
if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(call.Method.DeclaringType, baseClass)) |
|
|
|
// call op_Equality(callvirt get_EqualityContract(ldloc this), callvirt get_EqualityContract(ldloc other))
|
|
|
|
return false; |
|
|
|
// Special-cased because Roslyn isn't using EqualityComparer<T> here.
|
|
|
|
if (call.Arguments.Count != 2) |
|
|
|
if (pos >= conditions.Count) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!call.Arguments[0].MatchLdThis()) |
|
|
|
if (!(conditions[pos] is Call { Method: { IsOperator: true, Name: "op_Equality" } } opEqualityCall)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!call.Arguments[1].MatchLdLoc(other)) |
|
|
|
if (!opEqualityCall.Method.DeclaringType.IsKnownType(KnownTypeCode.Type)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
pos++; |
|
|
|
if (opEqualityCall.Arguments.Count != 2) |
|
|
|
} |
|
|
|
return false; |
|
|
|
else |
|
|
|
if (!MatchGetEqualityContract(opEqualityCall.Arguments[0], out var target1)) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
// comp.o(ldloc other != ldnull)
|
|
|
|
if (!MatchGetEqualityContract(opEqualityCall.Arguments[1], out var target2)) |
|
|
|
if (pos >= conditions.Count) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!target1.MatchLdThis()) |
|
|
|
if (!conditions[pos].MatchCompNotEqualsNull(out var arg)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!target2.MatchLdLoc(other)) |
|
|
|
if (!arg.MatchLdLoc(other)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
pos++; |
|
|
|
pos++; |
|
|
|
|
|
|
|
// call op_Equality(callvirt get_EqualityContract(ldloc this), callvirt get_EqualityContract(ldloc other))
|
|
|
|
|
|
|
|
// Special-cased because Roslyn isn't using EqualityComparer<T> here.
|
|
|
|
|
|
|
|
if (pos >= conditions.Count) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!(conditions[pos] is Call { Method: { IsOperator: true, Name: "op_Equality" } } opEqualityCall)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!opEqualityCall.Method.DeclaringType.IsKnownType(KnownTypeCode.Type)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (opEqualityCall.Arguments.Count != 2) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!MatchGetEqualityContract(opEqualityCall.Arguments[0], out var target1)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!MatchGetEqualityContract(opEqualityCall.Arguments[1], out var target2)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!target1.MatchLdThis()) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!target2.MatchLdLoc(other)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
pos++; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
foreach (var member in orderedMembers) |
|
|
|
foreach (var member in orderedMembers) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (!MemberConsideredForEquality(member)) |
|
|
|
if (!MemberConsideredForEquality(member)) |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
if (member.Name == "EqualityContract") |
|
|
|
if (!isStruct && member.Name == "EqualityContract") |
|
|
|
{ |
|
|
|
{ |
|
|
|
continue; // already special-cased
|
|
|
|
continue; // already special-cased
|
|
|
|
} |
|
|
|
} |
|
|
@ -771,7 +785,7 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!member1.Equals(member)) |
|
|
|
if (!member1.Equals(member)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!target2.MatchLdLoc(other)) |
|
|
|
if (!(isStruct ? target2.MatchLdLoca(other) : target2.MatchLdLoc(other))) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!member2.Equals(member)) |
|
|
|
if (!member2.Equals(member)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
@ -800,10 +814,12 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static bool MatchGetEqualityContract(ILInstruction inst, out ILInstruction target) |
|
|
|
private bool MatchGetEqualityContract(ILInstruction inst, out ILInstruction target) |
|
|
|
{ |
|
|
|
{ |
|
|
|
target = null; |
|
|
|
target = null; |
|
|
|
if (!(inst is CallVirt { Method: { Name: "get_EqualityContract" } } call)) |
|
|
|
if (!(inst is CallInstruction { Method: { Name: "get_EqualityContract" } } call)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!(inst is CallVirt || (isSealed && inst is Call))) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (call.Arguments.Count != 1) |
|
|
|
if (call.Arguments.Count != 1) |
|
|
|
return false; |
|
|
|
return false; |
|
|
@ -830,7 +846,7 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (member is IProperty property) |
|
|
|
if (member is IProperty property) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (property.Name == "EqualityContract") |
|
|
|
if (!isStruct && property.Name == "EqualityContract") |
|
|
|
return !isInheritedRecord; |
|
|
|
return !isInheritedRecord; |
|
|
|
return autoPropertyToBackingField.ContainsKey(property); |
|
|
|
return autoPropertyToBackingField.ContainsKey(property); |
|
|
|
} |
|
|
|
} |
|
|
@ -985,14 +1001,14 @@ namespace ICSharpCode.Decompiler.CSharp |
|
|
|
{ |
|
|
|
{ |
|
|
|
target = null; |
|
|
|
target = null; |
|
|
|
member = null; |
|
|
|
member = null; |
|
|
|
if (inst is CallVirt |
|
|
|
if (inst is CallInstruction |
|
|
|
{ |
|
|
|
{ |
|
|
|
Method: |
|
|
|
Method: |
|
|
|
{ |
|
|
|
{ |
|
|
|
AccessorKind: System.Reflection.MethodSemanticsAttributes.Getter, |
|
|
|
AccessorKind: System.Reflection.MethodSemanticsAttributes.Getter, |
|
|
|
AccessorOwner: IProperty property |
|
|
|
AccessorOwner: IProperty property |
|
|
|
} |
|
|
|
} |
|
|
|
} call) |
|
|
|
} call && (call is CallVirt || (isSealed && call is Call))) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (call.Arguments.Count != 1) |
|
|
|
if (call.Arguments.Count != 1) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|