Browse Source

Records: detect when ToString() is compiler-generated

pull/2251/head
Daniel Grunwald 5 years ago
parent
commit
0babcc5fe4
  1. 98
      ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs

98
ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs

@ -69,6 +69,8 @@ namespace ICSharpCode.Decompiler.CSharp
case "<Clone>$" when method.Parameters.Count == 0: case "<Clone>$" when method.Parameters.Count == 0:
// Always generated; Method name cannot be expressed in C# // Always generated; Method name cannot be expressed in C#
return true; return true;
case "ToString" when method.Parameters.Count == 0:
return IsGeneratedToString(method);
default: default:
return false; return false;
} }
@ -96,6 +98,8 @@ namespace ICSharpCode.Decompiler.CSharp
return false; return false;
if (!(property.IsVirtual || property.IsOverride)) if (!(property.IsVirtual || property.IsOverride))
return false; return false;
if (property.IsSealed)
return false;
var getter = property.Getter; var getter = property.Getter;
if (!(getter != null && !property.CanSet)) if (!(getter != null && !property.CanSet))
return false; return false;
@ -109,7 +113,9 @@ namespace ICSharpCode.Decompiler.CSharp
if (!attrs[0].AttributeType.IsKnownType(KnownAttribute.CompilerGenerated)) if (!attrs[0].AttributeType.IsKnownType(KnownAttribute.CompilerGenerated))
return false; return false;
var body = DecompileBody(getter); var body = DecompileBody(getter);
if (!(body?.SingleInstruction() is Leave leave)) if (body == null || body.Instructions.Count != 1)
return false;
if (!(body.Instructions.Single() is Leave leave))
return false; return false;
// leave IL_0000 (call GetTypeFromHandle(ldtypetoken R)) // leave IL_0000 (call GetTypeFromHandle(ldtypetoken R))
if (!TransformExpressionTrees.MatchGetTypeFromHandle(leave.Value, out IType ty)) if (!TransformExpressionTrees.MatchGetTypeFromHandle(leave.Value, out IType ty))
@ -117,7 +123,74 @@ namespace ICSharpCode.Decompiler.CSharp
return IsRecordType(ty); return IsRecordType(ty);
} }
BlockContainer DecompileBody(IMethod method) private bool IsGeneratedToString(IMethod method)
{
Debug.Assert(method.Name == "ToString");
if (!method.IsOverride)
return false;
if (method.IsSealed)
return false;
if (method.GetAttributes().Any() || method.GetReturnTypeAttributes().Any())
return false;
var body = DecompileBody(method);
if (body == null)
return false;
// stloc stringBuilder(newobj StringBuilder..ctor())
if (!body.Instructions[0].MatchStLoc(out var stringBuilder, out var stringBuilderInit))
return false;
if (!(stringBuilderInit is NewObj { Arguments: { Count: 0 }, Method: { DeclaringTypeDefinition: { Name: "StringBuilder", Namespace: "System.Text" } } }))
return false;
// callvirt Append(ldloc stringBuilder, ldstr "R")
if (!MatchAppendCallWithValue(body.Instructions[1], recordTypeDef.Name))
return false;
// callvirt Append(ldloc stringBuilder, ldstr " { ")
if (!MatchAppendCallWithValue(body.Instructions[2], " { "))
return false;
// if (callvirt PrintMembers(ldloc this, ldloc stringBuilder)) { trueInst }
if (!body.Instructions[3].MatchIfInstruction(out var condition, out var trueInst))
return true;
if (!(condition is CallVirt { Method: { Name: "PrintMembers" } } printMembersCall))
return false;
if (printMembersCall.Arguments.Count != 2)
return false;
if (!printMembersCall.Arguments[0].MatchLdThis())
return false;
if (!printMembersCall.Arguments[1].MatchLdLoc(stringBuilder))
return false;
// trueInst: callvirt Append(ldloc stringBuilder, ldstr " ")
if (!MatchAppendCallWithValue(Block.Unwrap(trueInst), " "))
return false;
// callvirt Append(ldloc stringBuilder, ldstr "}")
if (!MatchAppendCallWithValue(body.Instructions[4], "}"))
return false;
// leave IL_0000 (callvirt ToString(ldloc stringBuilder))
if (!(body.Instructions[5] is Leave leave))
return false;
if (!(leave.Value is CallVirt { Method: { Name: "ToString" } } toStringCall))
return false;
if (toStringCall.Arguments.Count != 1)
return false;
return toStringCall.Arguments[0].MatchLdLoc(stringBuilder);
bool MatchAppendCall(ILInstruction inst, out string val)
{
val = null;
if (!(inst is CallVirt { Method: { Name: "Append" } } call))
return false;
if (call.Arguments.Count != 2)
return false;
if (!call.Arguments[0].MatchLdLoc(stringBuilder))
return false;
return call.Arguments[1].MatchLdStr(out val);
}
bool MatchAppendCallWithValue(ILInstruction inst, string val)
{
return MatchAppendCall(inst, out string tmp) && tmp == val;
}
}
Block DecompileBody(IMethod method)
{ {
if (method == null || method.MetadataToken.IsNil) if (method == null || method.MetadataToken.IsNil)
return null; return null;
@ -134,12 +207,27 @@ namespace ICSharpCode.Decompiler.CSharp
var body = typeSystem.MainModule.PEFile.Reader.GetMethodBody(methodDef.RelativeVirtualAddress); var body = typeSystem.MainModule.PEFile.Reader.GetMethodBody(methodDef.RelativeVirtualAddress);
var ilReader = new ILReader(typeSystem.MainModule); var ilReader = new ILReader(typeSystem.MainModule);
var il = ilReader.ReadIL(methodDefHandle, body, genericContext, ILFunctionKind.TopLevelFunction, cancellationToken); var il = ilReader.ReadIL(methodDefHandle, body, genericContext, ILFunctionKind.TopLevelFunction, cancellationToken);
var settings = new DecompilerSettings(LanguageVersion.CSharp2); var settings = new DecompilerSettings(LanguageVersion.CSharp1);
il.RunTransforms(CSharpDecompiler.EarlyILTransforms(), var transforms = CSharpDecompiler.GetILTransforms();
// Remove the last couple transforms -- we don't need variable names etc. here
int lastBlockTransform = transforms.FindLastIndex(t => t is BlockILTransform);
transforms.RemoveRange(lastBlockTransform + 1, transforms.Count - (lastBlockTransform + 1));
il.RunTransforms(transforms,
new ILTransformContext(il, typeSystem, debugInfo: null, settings) { new ILTransformContext(il, typeSystem, debugInfo: null, settings) {
CancellationToken = cancellationToken CancellationToken = cancellationToken
}); });
return (BlockContainer)il.Body; if (il.Body is BlockContainer container)
{
return container.EntryPoint;
}
else if (il.Body is Block block)
{
return block;
}
else
{
return null;
}
} }
} }
} }

Loading…
Cancel
Save