From e02c4789f150282d9a53540010ca7ce0a3d3b88c Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 25 Dec 2020 21:16:38 +0100 Subject: [PATCH] Records: detect copy constructor --- .../CSharp/RecordDecompiler.cs | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs b/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs index dcd2d5530..b66f1dad6 100644 --- a/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs @@ -170,6 +170,11 @@ namespace ICSharpCode.Decompiler.CSharp /// public bool MethodIsGenerated(IMethod method) { + if (method.IsConstructor && method.Parameters.Count == 1 + && IsRecordType(method.Parameters[0].Type)) + { + return IsGeneratedCopyConstructor(method); + } switch (method.Name) { // Some members in records are always compiler-generated and lead to a @@ -226,6 +231,68 @@ namespace ICSharpCode.Decompiler.CSharp } } + private bool IsGeneratedCopyConstructor(IMethod method) + { + /* + call BaseClass..ctor(ldloc this, ldloc original) + stfld k__BackingField(ldloc this, ldfld k__BackingField(ldloc original)) + leave IL_0000 (nop) + */ + Debug.Assert(method.IsConstructor && method.Parameters.Count == 1); + if (method.GetAttributes().Any() || method.GetReturnTypeAttributes().Any()) + return false; + if (orderedMembers == null) + return false; + var body = DecompileBody(method); + if (body == null) + return false; + var variables = body.Ancestors.OfType().Single().Variables; + var other = variables.Single(v => v.Kind == VariableKind.Parameter && v.Index == 0); + Debug.Assert(IsRecordType(other.Type)); + int pos = 0; + // First instruction is the base constructor call + if (!(body.Instructions[pos] is Call { Method: { IsConstructor: true } } baseCtorCall)) + return false; + if (!object.Equals(baseCtorCall.Method.DeclaringType, baseClass)) + return false; + if (baseCtorCall.Arguments.Count != (isInheritedRecord ? 2 : 1)) + return false; + if (!baseCtorCall.Arguments[0].MatchLdThis()) + return false; + if (isInheritedRecord) + { + if (!baseCtorCall.Arguments[1].MatchLdLoc(other)) + return false; + } + pos++; + // Then all the fields are copied over + foreach (var member in orderedMembers) + { + if (!(member is IField field)) + { + if (!autoPropertyToBackingField.TryGetValue((IProperty)member, out field)) + continue; + } + if (pos >= body.Instructions.Count) + return false; + if (!body.Instructions[pos].MatchStFld(out var lhsTarget, out var lhsField, out var valueInst)) + return false; + if (!lhsTarget.MatchLdThis()) + return false; + if (!lhsField.Equals(field)) + return false; + + if (!valueInst.MatchLdFld(out var rhsTarget, out var rhsField)) + return false; + if (!rhsTarget.MatchLdThis()) + return false; + if (!rhsField.Equals(field)) + return false; + pos++; + } + return body.Instructions[pos] is Leave; + } + private bool IsGeneratedEqualityContract(IProperty property) { // Generated member: