mirror of https://github.com/icsharpcode/ILSpy.git
6 changed files with 281 additions and 1 deletions
@ -0,0 +1,136 @@
@@ -0,0 +1,136 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Text; |
||||
using System.Threading.Tasks; |
||||
using ICSharpCode.Decompiler.TypeSystem; |
||||
|
||||
namespace ICSharpCode.Decompiler.IL.Transforms |
||||
{ |
||||
/// <summary>
|
||||
/// Transforms array initialization pattern of System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray.
|
||||
/// For collection and object initializers see <see cref="TransformInitializers"/>
|
||||
/// </summary>
|
||||
public class TransformCollectionAndObjectInitializers : IBlockTransform |
||||
{ |
||||
BlockTransformContext context; |
||||
|
||||
void IBlockTransform.Run(Block block, BlockTransformContext context) |
||||
{ |
||||
this.context = context; |
||||
for (int i = block.Instructions.Count - 1; i >= 0; i--) |
||||
{ |
||||
DoTransform(block, i); |
||||
} |
||||
} |
||||
|
||||
enum InitializerType |
||||
{ |
||||
None, |
||||
Collection, |
||||
Object |
||||
} |
||||
|
||||
bool DoTransform(Block body, int pos) |
||||
{ |
||||
ILInstruction inst = body.Instructions[pos]; |
||||
// Match stloc(v, newobj)
|
||||
if (inst.MatchStLoc(out var v, out var initInst) && v.Kind == VariableKind.StackSlot && initInst is NewObj newObj) { |
||||
context.Step("CollectionOrObjectInitializer", inst); |
||||
int initializerItemsCount = 0; |
||||
var initializerType = InitializerType.None; |
||||
// Detect initializer type by scanning the following statements
|
||||
// each must be a callvirt with ldloc v as first argument
|
||||
// if the method is a setter we're dealing with an object initializer
|
||||
// if the method is named Add and has at least 2 arguments we're dealing with a collection/dictionary initializer
|
||||
while (pos + initializerItemsCount + 1 < body.Instructions.Count |
||||
&& (MatchesCallVirt(body.Instructions[pos + initializerItemsCount + 1], v, ref initializerType) || MatchesStObj(body.Instructions[pos + initializerItemsCount + 1], v, ref initializerType))) |
||||
{ |
||||
initializerItemsCount++; |
||||
} |
||||
if (initializerItemsCount == 0) |
||||
return false; |
||||
Block initBlock; |
||||
switch (initializerType) { |
||||
case InitializerType.Collection: |
||||
initBlock = new Block(BlockType.CollectionInitializer); |
||||
break; |
||||
case InitializerType.Object: |
||||
initBlock = new Block(BlockType.ObjectInitializer); |
||||
break; |
||||
default: return false; |
||||
} |
||||
var finalSlot = context.Function.RegisterVariable(VariableKind.StackSlot, v.Type); |
||||
initBlock.FinalInstruction = new LdLoc(finalSlot); |
||||
initBlock.Instructions.Add(new StLoc(finalSlot, initInst.Clone())); |
||||
for (int i = 1; i <= initializerItemsCount; i++) { |
||||
switch (body.Instructions[i + pos]) { |
||||
case CallVirt callVirt: |
||||
var newCallVirt = new CallVirt(callVirt.Method); |
||||
var newTarget = callVirt.Arguments[0].Clone(); |
||||
foreach (var load in newTarget.Descendants.OfType<LdLoc>()) |
||||
load.Variable = finalSlot; |
||||
newCallVirt.Arguments.Add(newTarget); |
||||
newCallVirt.Arguments.AddRange(callVirt.Arguments.Skip(1).Select(a => a.Clone())); |
||||
initBlock.Instructions.Add(newCallVirt); |
||||
break; |
||||
case StObj stObj: |
||||
var newStObj = (StObj)stObj.Clone(); |
||||
foreach (var load in newStObj.Target.Descendants.OfType<LdLoc>()) |
||||
load.Variable = finalSlot; |
||||
initBlock.Instructions.Add(newStObj); |
||||
break; |
||||
} |
||||
|
||||
} |
||||
initInst.ReplaceWith(initBlock); |
||||
for (int i = 0; i < initializerItemsCount; i++) |
||||
body.Instructions.RemoveAt(pos + 1); |
||||
ILInlining.InlineIfPossible(body, ref pos, context); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
bool MatchesStObj(ILInstruction current, ILVariable v, ref InitializerType initializerType) |
||||
{ |
||||
if (current is StObj so |
||||
&& so.Target is LdFlda ldfa |
||||
&& ldfa.Target.MatchLdLoc(v)) |
||||
return true; |
||||
return false; |
||||
} |
||||
|
||||
bool MatchesCallVirt(ILInstruction current, ILVariable v, ref InitializerType initializerType) |
||||
{ |
||||
return current is CallVirt cv |
||||
&& cv.Arguments.Count >= 2 |
||||
&& !cv.Arguments.Skip(1).Any(a => a.Descendants.OfType<LdLoc>().Any(b => b.MatchLdLoc(v))) |
||||
&& IsMatchingTarget(cv.Arguments[0], v) |
||||
&& IsMatchingMethod(cv, ref initializerType); |
||||
} |
||||
|
||||
bool IsMatchingTarget(ILInstruction target, ILVariable targetVariable) |
||||
{ |
||||
if (target.MatchLdLoc(targetVariable)) |
||||
return true; |
||||
if (target is LdObj lo && lo.Target is LdFlda ldfa && ldfa.Target.MatchLdLoc(targetVariable)) |
||||
return true; |
||||
if (target is CallVirt cv && cv.Method.IsAccessor && cv.Arguments.Count == 1 && cv.Arguments[0].MatchLdLoc(targetVariable)) |
||||
return true; |
||||
return false; |
||||
} |
||||
|
||||
bool IsMatchingMethod(CallVirt cv, ref InitializerType type) |
||||
{ |
||||
if (cv.Method.IsAccessor && cv.Method.AccessorOwner is IProperty p && p.Setter == cv.Method) { |
||||
type = InitializerType.Object; |
||||
return true; |
||||
} else if (cv.Method.Name.Equals("Add", StringComparison.Ordinal)) { |
||||
if (type == InitializerType.None) |
||||
type = InitializerType.Collection; |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue