Browse Source

Fix #2787: Enable NRT in TransformCollectionAndObjectInitializers and fix problems.

pull/2792/head
Siegfried Pammer 3 years ago
parent
commit
927b46b17d
  1. 101
      ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs

101
ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs

@ -16,6 +16,8 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -44,13 +46,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
IType instType; IType instType;
var blockKind = BlockKind.CollectionInitializer; var blockKind = BlockKind.CollectionInitializer;
var insertionPos = initInst.ChildIndex; var insertionPos = initInst.ChildIndex;
var siblings = initInst.Parent.Children; var siblings = initInst.Parent!.Children;
IMethod currentMethod = context.Function.Method!;
switch (initInst) switch (initInst)
{ {
case NewObj newObjInst: case NewObj newObjInst:
if (newObjInst.ILStackWasEmpty && v.Kind == VariableKind.Local if (newObjInst.ILStackWasEmpty && v.Kind == VariableKind.Local
&& !context.Function.Method.IsConstructor && !currentMethod.IsConstructor
&& !context.Function.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) && !currentMethod.IsCompilerGeneratedOrIsInCompilerGeneratedClass())
{ {
// on statement level (no other expressions on IL stack), // on statement level (no other expressions on IL stack),
// prefer to keep local variables (but not stack slots), // prefer to keep local variables (but not stack slots),
@ -70,7 +73,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
instType = newObjInst.Method.DeclaringType; instType = newObjInst.Method.DeclaringType;
break; break;
case DefaultValue defaultVal: case DefaultValue defaultVal:
if (defaultVal.ILStackWasEmpty && v.Kind == VariableKind.Local && !context.Function.Method.IsConstructor) if (defaultVal.ILStackWasEmpty && v.Kind == VariableKind.Local && !currentMethod.IsConstructor)
{ {
// on statement level (no other expressions on IL stack), // on statement level (no other expressions on IL stack),
// prefer to keep local variables (but not stack slots), // prefer to keep local variables (but not stack slots),
@ -100,10 +103,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return; return;
} }
int initializerItemsCount = 0; int initializerItemsCount = 0;
possibleIndexVariables = new Dictionary<ILVariable, (int Index, ILInstruction Value)>(); possibleIndexVariables.Clear();
currentPath = new List<AccessPathElement>(); currentPath.Clear();
isCollection = false; isCollection = false;
pathStack = new Stack<HashSet<AccessPathElement>>(); pathStack.Clear();
pathStack.Push(new HashSet<AccessPathElement>()); pathStack.Push(new HashSet<AccessPathElement>());
// Detect initializer type by scanning the following statements // Detect initializer type by scanning the following statements
// each must be a callvirt with ldloc v as first argument // each must be a callvirt with ldloc v as first argument
@ -192,10 +195,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
} }
Dictionary<ILVariable, (int Index, ILInstruction Value)> possibleIndexVariables; readonly Dictionary<ILVariable, (int Index, ILInstruction Value)> possibleIndexVariables = new Dictionary<ILVariable, (int Index, ILInstruction Value)>();
List<AccessPathElement> currentPath; readonly List<AccessPathElement> currentPath = new List<AccessPathElement>();
bool isCollection; bool isCollection;
Stack<HashSet<AccessPathElement>> pathStack; readonly Stack<HashSet<AccessPathElement>> pathStack = new Stack<HashSet<AccessPathElement>>();
bool IsPartOfInitializer(InstructionCollection<ILInstruction> instructions, int pos, ILVariable target, IType rootType, ref BlockKind blockKind, StatementTransformContext context) bool IsPartOfInitializer(InstructionCollection<ILInstruction> instructions, int pos, ILVariable target, IType rootType, ref BlockKind blockKind, StatementTransformContext context)
{ {
@ -248,7 +251,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case AccessPathKind.Setter: case AccessPathKind.Setter:
if (isCollection || !pathStack.Peek().Add(lastElement)) if (isCollection || !pathStack.Peek().Add(lastElement))
return false; return false;
if (values.Count != 1 || !IsValidObjectInitializerTarget(currentPath)) if (values?.Count != 1 || !IsValidObjectInitializerTarget(currentPath))
return false; return false;
if (blockKind != BlockKind.ObjectInitializer && blockKind != BlockKind.WithInitializer) if (blockKind != BlockKind.ObjectInitializer && blockKind != BlockKind.WithInitializer)
blockKind = BlockKind.ObjectInitializer; blockKind = BlockKind.ObjectInitializer;
@ -264,9 +267,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
var element = path.Last(); var element = path.Last();
var previous = path.SkipLast(1).LastOrDefault(); var previous = path.SkipLast(1).LastOrDefault();
if (!(element.Member is IProperty p)) if (element.Member is not IProperty p)
return true;
if (!p.IsIndexer)
return true; return true;
return !p.IsIndexer || NormalizeTypeVisitor.IgnoreNullabilityAndTuples.EquivalentTypes(previous.Member?.ReturnType, element.Member.DeclaringType); if (previous != default)
{
return NormalizeTypeVisitor.IgnoreNullabilityAndTuples
.EquivalentTypes(previous.Member.ReturnType, element.Member.DeclaringType);
}
return false;
} }
} }
@ -279,7 +289,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public struct AccessPathElement : IEquatable<AccessPathElement> public struct AccessPathElement : IEquatable<AccessPathElement>
{ {
public AccessPathElement(OpCode opCode, IMember member, ILInstruction[] indices = null) public AccessPathElement(OpCode opCode, IMember member, ILInstruction[]? indices = null)
{ {
this.OpCode = opCode; this.OpCode = opCode;
this.Member = member; this.Member = member;
@ -288,24 +298,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public readonly OpCode OpCode; public readonly OpCode OpCode;
public readonly IMember Member; public readonly IMember Member;
public readonly ILInstruction[] Indices; public readonly ILInstruction[]? Indices;
public override string ToString() => $"[{Member}, {Indices}]"; public override string ToString() => $"[{Member}, {Indices}]";
public static (AccessPathKind Kind, List<AccessPathElement> Path, List<ILInstruction> Values, ILVariable Target) GetAccessPath( public static (AccessPathKind Kind, List<AccessPathElement> Path, List<ILInstruction>? Values, ILVariable? Target) GetAccessPath(
ILInstruction instruction, IType rootType, DecompilerSettings settings = null, ILInstruction instruction, IType rootType, DecompilerSettings? settings = null,
CSharpTypeResolveContext resolveContext = null, CSharpTypeResolveContext? resolveContext = null,
Dictionary<ILVariable, (int Index, ILInstruction Value)> possibleIndexVariables = null) Dictionary<ILVariable, (int Index, ILInstruction Value)>? possibleIndexVariables = null)
{ {
List<AccessPathElement> path = new List<AccessPathElement>(); List<AccessPathElement> path = new List<AccessPathElement>();
ILVariable target = null; ILVariable? target = null;
AccessPathKind kind = AccessPathKind.Invalid; AccessPathKind kind = AccessPathKind.Invalid;
List<ILInstruction> values = null; List<ILInstruction>? values = null;
IMethod method; IMethod method;
var inst = instruction; ILInstruction? inst = instruction;
while (instruction != null) while (inst != null)
{ {
switch (instruction) switch (inst)
{ {
case CallInstruction call: case CallInstruction call:
if (!(call is CallVirt || call is Call)) if (!(call is CallVirt || call is Call))
@ -313,12 +323,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms
method = call.Method; method = call.Method;
if (resolveContext != null && !IsMethodApplicable(method, call.Arguments, rootType, resolveContext, settings)) if (resolveContext != null && !IsMethodApplicable(method, call.Arguments, rootType, resolveContext, settings))
goto default; goto default;
instruction = call.Arguments[0]; inst = call.Arguments[0];
if (method.IsAccessor) if (method.AccessorOwner is not null)
{ {
var property = method.AccessorOwner as IProperty; if (method.AccessorOwner is IProperty property &&
if (!CanBeUsedInInitializer(property, resolveContext, kind, path)) !CanBeUsedInInitializer(property, resolveContext, kind))
{
goto default; goto default;
}
var isGetter = method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter; var isGetter = method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter;
var indices = call.Arguments.Skip(1).Take(call.Arguments.Count - (isGetter ? 1 : 2)).ToArray(); var indices = call.Arguments.Skip(1).Take(call.Arguments.Count - (isGetter ? 1 : 2)).ToArray();
if (indices.Length > 0 && settings?.DictionaryInitializers == false) if (indices.Length > 0 && settings?.DictionaryInitializers == false)
@ -359,7 +372,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (ldobj.Target is LdFlda ldflda && (kind != AccessPathKind.Setter || !ldflda.Field.IsReadOnly)) if (ldobj.Target is LdFlda ldflda && (kind != AccessPathKind.Setter || !ldflda.Field.IsReadOnly))
{ {
path.Insert(0, new AccessPathElement(ldobj.OpCode, ldflda.Field)); path.Insert(0, new AccessPathElement(ldobj.OpCode, ldflda.Field));
instruction = ldflda.Target; inst = ldflda.Target;
break; break;
} }
goto default; goto default;
@ -369,7 +382,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (stobj.Target is LdFlda ldflda) if (stobj.Target is LdFlda ldflda)
{ {
path.Insert(0, new AccessPathElement(stobj.OpCode, ldflda.Field)); path.Insert(0, new AccessPathElement(stobj.OpCode, ldflda.Field));
instruction = ldflda.Target; inst = ldflda.Target;
if (values == null) if (values == null)
{ {
values = new List<ILInstruction>(new[] { stobj.Value }); values = new List<ILInstruction>(new[] { stobj.Value });
@ -381,35 +394,35 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
case LdLoc ldloc: case LdLoc ldloc:
target = ldloc.Variable; target = ldloc.Variable;
instruction = null; inst = null;
break; break;
case LdLoca ldloca: case LdLoca ldloca:
target = ldloca.Variable; target = ldloca.Variable;
instruction = null; inst = null;
break; break;
case LdFlda ldflda: case LdFlda ldflda:
path.Insert(0, new AccessPathElement(ldflda.OpCode, ldflda.Field)); path.Insert(0, new AccessPathElement(ldflda.OpCode, ldflda.Field));
instruction = ldflda.Target; inst = ldflda.Target;
break; break;
default: default:
kind = AccessPathKind.Invalid; kind = AccessPathKind.Invalid;
instruction = null; inst = null;
break; break;
} }
} }
if (kind != AccessPathKind.Invalid && values.SelectMany(v => v.Descendants).OfType<IInstructionWithVariableOperand>().Any(ld => ld.Variable == target && (ld is LdLoc || ld is LdLoca))) if (kind != AccessPathKind.Invalid && values != null && values.SelectMany(v => v.Descendants).OfType<IInstructionWithVariableOperand>().Any(ld => ld.Variable == target && (ld is LdLoc || ld is LdLoca)))
kind = AccessPathKind.Invalid; kind = AccessPathKind.Invalid;
return (kind, path, values, target); return (kind, path, values, target);
} }
private static bool CanBeUsedInInitializer(IProperty property, CSharpTypeResolveContext resolveContext, AccessPathKind kind, List<AccessPathElement> path) private static bool CanBeUsedInInitializer(IProperty property, CSharpTypeResolveContext? resolveContext, AccessPathKind kind)
{ {
if (property.CanSet && (property.Accessibility == property.Setter.Accessibility || IsAccessorAccessible(property.Setter, resolveContext))) if (property.CanSet && (property.Accessibility == property.Setter.Accessibility || IsAccessorAccessible(property.Setter, resolveContext)))
return true; return true;
return kind != AccessPathKind.Setter; return kind != AccessPathKind.Setter;
} }
private static bool IsAccessorAccessible(IMethod setter, CSharpTypeResolveContext resolveContext) private static bool IsAccessorAccessible(IMethod setter, CSharpTypeResolveContext? resolveContext)
{ {
if (resolveContext == null) if (resolveContext == null)
return true; return true;
@ -417,7 +430,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return lookup.IsAccessible(setter, allowProtectedAccess: setter.DeclaringTypeDefinition == resolveContext.CurrentTypeDefinition); return lookup.IsAccessible(setter, allowProtectedAccess: setter.DeclaringTypeDefinition == resolveContext.CurrentTypeDefinition);
} }
static bool IsMethodApplicable(IMethod method, IReadOnlyList<ILInstruction> arguments, IType rootType, CSharpTypeResolveContext resolveContext, DecompilerSettings settings) static bool IsMethodApplicable(IMethod method, IReadOnlyList<ILInstruction> arguments, IType rootType, CSharpTypeResolveContext resolveContext, DecompilerSettings? settings)
{ {
if (method.IsStatic && !method.IsExtensionMethod) if (method.IsStatic && !method.IsExtensionMethod)
return false; return false;
@ -453,7 +466,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
static IType GetReturnTypeFromInstruction(ILInstruction instruction) static IType? GetReturnTypeFromInstruction(ILInstruction instruction)
{ {
switch (instruction) switch (instruction)
{ {
@ -474,7 +487,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
public override bool Equals(object obj) public override bool Equals(object? obj)
{ {
if (obj is AccessPathElement) if (obj is AccessPathElement)
return Equals((AccessPathElement)obj); return Equals((AccessPathElement)obj);
@ -494,8 +507,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
public bool Equals(AccessPathElement other) public bool Equals(AccessPathElement other)
{ {
return other.Member.Equals(this.Member) return (other.Member == this.Member
&& (other.Indices == this.Indices || other.Indices.SequenceEqual(this.Indices, ILInstructionMatchComparer.Instance)); || this.Member.Equals(other.Member))
&& (other.Indices == this.Indices
|| (other.Indices != null && this.Indices != null && this.Indices.SequenceEqual(other.Indices, ILInstructionMatchComparer.Instance)));
} }
public static bool operator ==(AccessPathElement lhs, AccessPathElement rhs) public static bool operator ==(AccessPathElement lhs, AccessPathElement rhs)
@ -513,7 +528,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
public static readonly ILInstructionMatchComparer Instance = new ILInstructionMatchComparer(); public static readonly ILInstructionMatchComparer Instance = new ILInstructionMatchComparer();
public bool Equals(ILInstruction x, ILInstruction y) public bool Equals(ILInstruction? x, ILInstruction? y)
{ {
if (x == y) if (x == y)
return true; return true;

Loading…
Cancel
Save