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

Loading…
Cancel
Save