Browse Source

Add support for slicing using C# 8 ranges.

pull/1986/head
Daniel Grunwald 5 years ago
parent
commit
0dd75d6852
  1. 42
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs
  2. 10
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  3. 341
      ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs
  4. 19
      ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs

42
ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs

@ -1,4 +1,22 @@ @@ -1,4 +1,22 @@
using System;
// Copyright (c) 2020 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
@ -110,57 +128,47 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -110,57 +128,47 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
public static void UseNewRangeFromIndex()
{
Console.WriteLine(GetArray()[GetIndex()..GetIndex()]);
#if TODO
//Console.WriteLine(GetList()[GetIndex()..GetIndex()]); // fails to compile
Console.WriteLine(GetSpan()[GetIndex()..GetIndex()].ToString());
Console.WriteLine(GetString()[GetIndex()..GetIndex()]);
Console.WriteLine(new CustomList()[GetIndex()..GetIndex()]);
#endif
Console.WriteLine(new CustomList2()[GetIndex()..GetIndex()]);
Console.WriteLine(GetArray()[GetIndex(1)..GetIndex(2)]);
//Console.WriteLine(GetList()[GetIndex(1)..GetIndex(2)]); // fails to compile
Console.WriteLine(GetSpan()[GetIndex(1)..GetIndex(2)].ToString());
Console.WriteLine(GetString()[GetIndex(1)..GetIndex(2)]);
Console.WriteLine(new CustomList()[GetIndex(1)..GetIndex(2)]);
Console.WriteLine(new CustomList2()[GetIndex(1)..GetIndex(2)]);
}
public static void UseNewRangeFromIntegers_BothFromStart()
{
Console.WriteLine(GetArray()[GetInt(1)..GetInt(2)]);
#if TODO
//Console.WriteLine(GetList()[GetInt()..GetInt()]); // fails to compile
Console.WriteLine(GetSpan()[GetInt(1)..GetInt(2)].ToString());
Console.WriteLine(GetString()[GetInt(1)..GetInt(2)]);
Console.WriteLine(new CustomList()[GetInt(1)..GetInt(2)]);
#endif
Console.WriteLine(new CustomList2()[GetInt(1)..GetInt(2)]);
}
public static void UseNewRangeFromIntegers_BothFromEnd()
{
Console.WriteLine(GetArray()[^GetInt(1)..^GetInt(2)]);
#if TODO
//Console.WriteLine(GetList()[^GetInt()..^GetInt()]); // fails to compile
Console.WriteLine(GetSpan()[^GetInt(1)..^GetInt(2)].ToString());
Console.WriteLine(GetString()[^GetInt(1)..^GetInt(2)]);
Console.WriteLine(new CustomList()[^GetInt(1)..^GetInt(2)]);
#endif
Console.WriteLine(new CustomList2()[^GetInt(1)..^GetInt(2)]);
}
public static void UseNewRangeFromIntegers_FromStartAndEnd()
{
#if TODO
Console.WriteLine(GetArray()[GetInt(1)..^GetInt(2)]);
//Console.WriteLine(GetList()[GetInt()..^GetInt()]); // fails to compile
Console.WriteLine(GetSpan()[GetInt(1)..^GetInt(2)].ToString());
Console.WriteLine(GetString()[GetInt(1)..^GetInt(2)]);
Console.WriteLine(new CustomList()[GetInt(1)..^GetInt(2)]);
#endif
Console.WriteLine(new CustomList2()[GetInt(1)..^GetInt(2)]);
}
public static void UseNewRangeFromIntegers_FromEndAndStart()
{
Console.WriteLine(GetArray()[^GetInt(1)..GetInt(2)]);
#if TODO
//Console.WriteLine(GetList()[^GetInt()..GetInt()]); // fails to compile
Console.WriteLine(GetSpan()[^GetInt(1)..GetInt(2)].ToString());
Console.WriteLine(GetString()[^GetInt(1)..GetInt(2)]);
Console.WriteLine(new CustomList()[^GetInt(1)..GetInt(2)]);
#endif
Console.WriteLine(new CustomList2()[^GetInt(1)..GetInt(2)]);
}

10
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -253,7 +253,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -253,7 +253,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
if (settings.Ranges) {
if (HandleRangeConstruction(out var result, callOpCode, method, argumentList)) {
if (HandleRangeConstruction(out var result, callOpCode, method, target, argumentList)) {
return result;
}
}
@ -1501,7 +1501,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1501,7 +1501,7 @@ namespace ICSharpCode.Decompiler.CSharp
.WithILInstruction(call).WithILInstruction(block);
}
private bool HandleRangeConstruction(out ExpressionWithResolveResult result, OpCode callOpCode, IMethod method, ArgumentList argumentList)
private bool HandleRangeConstruction(out ExpressionWithResolveResult result, OpCode callOpCode, IMethod method, TranslatedExpression target, ArgumentList argumentList)
{
result = default;
if (argumentList.ArgumentNames != null) {
@ -1533,6 +1533,12 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1533,6 +1533,12 @@ namespace ICSharpCode.Decompiler.CSharp
result = new UnaryOperatorExpression(UnaryOperatorType.IndexFromEnd, argumentList.Arguments[0])
.WithRR(new MemberResolveResult(null, method));
return true;
} else if (method is SyntheticRangeIndexAccessor rangeIndexAccessor && rangeIndexAccessor.IsSlicing) {
// For slicing the method is called Slice()/Substring(), but we still need to output indexer notation.
// So special-case range-based slicing here.
result = new IndexerExpression(target, argumentList.Arguments.Select(a => a.Expression))
.WithRR(new MemberResolveResult(target.ResolveResult, method));
return true;
}
return false;
}

341
ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler.TypeSystem;
@ -61,31 +62,52 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -61,31 +62,52 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// -> withsystemindex.ldelema T(ldloc array, newobj System.Index(..., fromEnd: true))
if (!(bni.Left.MatchLdLen(StackType.I4, out var arrayLoad) && arrayLoad.MatchLdLoc(array)))
return false;
var indexCtor = FindIndexConstructor(context.TypeSystem);
if (indexCtor == null)
var indexMethods = new IndexMethods(context.TypeSystem);
if (!indexMethods.AllValid)
return false; // don't use System.Index if not supported by the target framework
context.Step("ldelema indexed from end", ldelema);
foreach (var node in bni.Left.Descendants)
ldelema.AddILRange(node);
ldelema.AddILRange(bni);
ldelema.WithSystemIndex = true;
ldelema.Indices[0] = new NewObj(indexCtor) { Arguments = { bni.Right, new LdcI4(1) } };
ldelema.Indices[0] = MakeIndex(IndexKind.FromEnd, bni.Right, indexMethods);
return true;
}
return false;
}
static IMethod FindIndexConstructor(ICompilation compilation)
class IndexMethods
{
var indexType = compilation.FindType(KnownTypeCode.Index);
foreach (var ctor in indexType.GetConstructors(m => m.Parameters.Count == 2)) {
if (ctor.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)
&& ctor.Parameters[1].Type.IsKnownType(KnownTypeCode.Boolean)) {
return ctor;
public readonly IMethod IndexCtor;
public readonly IMethod IndexImplicitConv;
public readonly IMethod RangeCtor;
public IType IndexType => IndexCtor?.DeclaringType;
public IType RangeType => RangeCtor?.DeclaringType;
public bool AllValid => IndexCtor != null && IndexImplicitConv != null && RangeCtor != null;
public IndexMethods(ICompilation compilation)
{
var indexType = compilation.FindType(KnownTypeCode.Index);
foreach (var ctor in indexType.GetConstructors(m => m.Parameters.Count == 2)) {
if (ctor.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)
&& ctor.Parameters[1].Type.IsKnownType(KnownTypeCode.Boolean)) {
this.IndexCtor = ctor;
}
}
foreach (var op in indexType.GetMethods(m => m.IsOperator && m.Name == "op_Implicit")) {
if (op.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)) {
this.IndexImplicitConv = op;
}
}
var rangeType = compilation.FindType(KnownTypeCode.Range);
foreach (var ctor in rangeType.GetConstructors(m => m.Parameters.Count == 2)) {
if (ctor.Parameters[0].Type.IsKnownType(KnownTypeCode.Index)
&& ctor.Parameters[1].Type.IsKnownType(KnownTypeCode.Index)) {
this.RangeCtor = ctor;
}
}
}
return null;
}
void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
@ -93,21 +115,50 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -93,21 +115,50 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!context.Settings.Ranges)
return;
int startPos = pos;
ILVariable containerVar = null;
// The container length access may be a separate instruction, or it may be inline with the variable's use
if (MatchContainerLengthStore(block.Instructions[pos], out ILVariable containerLengthVar, out ILVariable containerVar)) {
if (MatchContainerLengthStore(block.Instructions[pos], out ILVariable containerLengthVar, ref containerVar)) {
// stloc containerLengthVar(call get_Length/get_Count(ldloc container))
pos++;
} else {
// Reset if MatchContainerLengthStore only had a partial match. MatchGetOffset() will then set `containerVar`.
containerLengthVar = null;
containerVar = null;
}
var startIndexKind = MatchGetOffset(block.Instructions[pos], out ILVariable startOffsetVar, out ILInstruction startIndexLoad, containerLengthVar, ref containerVar);
pos++;
if (startIndexKind == IndexKind.None)
if (block.Instructions[pos].MatchStLoc(out var rangeVar, out var rangeVarInit) && rangeVar.Type.IsKnownType(KnownTypeCode.Range)) {
// stloc rangeVar(rangeVarInit)
pos++;
} else {
rangeVar = null;
rangeVarInit = null;
}
// stloc startOffsetVar(call GetOffset(startIndexLoad, ldloc length))
if (!block.Instructions[pos].MatchStLoc(out ILVariable startOffsetVar, out ILInstruction startOffsetVarInit))
return;
if (!(startOffsetVar.IsSingleDefinition && startOffsetVar.StackType == StackType.I4))
return;
var startIndexKind = MatchGetOffset(startOffsetVarInit, out ILInstruction startIndexLoad, containerLengthVar, ref containerVar);
pos++;
if (startOffsetVar.LoadCount == 1) {
TransformIndexing();
} else if (startOffsetVar.LoadCount == 2) {
// might be slicing: startOffset is used once for the slice length calculation, and once for the Slice() method call
TransformSlicing();
}
void TransformIndexing()
{
// complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
if (rangeVar != null)
return;
if (startIndexKind == IndexKind.FromStart) {
// FromStart is only relevant for slicing; indexing from the start does not involve System.Index at all.
return;
}
if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind)) {
return;
}
// startOffsetVar might be used deep inside a complex statement, ensure we can inline up to that point:
for (int i = startPos; i < pos; i++) {
if (!ILInlining.CanInlineInto(block.Instructions[pos], startOffsetVar, block.Instructions[i]))
@ -130,39 +181,117 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -130,39 +181,117 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
if (!call.Method.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32))
return;
if (!call.Arguments[0].MatchLdLoc(containerVar) && !call.Arguments[0].MatchLdLoca(containerVar))
if (!MatchContainerVar(call.Arguments[0], ref containerVar))
return;
if (!call.Arguments[1].MatchLdLoc(startOffsetVar))
return;
var indexType = context.TypeSystem.FindType(KnownTypeCode.Index);
var indexCtor = FindIndexConstructor(context.TypeSystem);
if (indexCtor == null)
var specialMethods = new IndexMethods(context.TypeSystem);
if (!specialMethods.AllValid)
return;
if (!CSharpWillGenerateIndexer(call.Method.DeclaringType))
if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, slicing: false))
return;
context.Step($"{call.Method.Name} indexed with {startIndexKind}", call);
var newMethod = new SyntheticRangeIndexAccessor(call.Method, indexType);
var newMethod = new SyntheticRangeIndexAccessor(call.Method, specialMethods.IndexType, slicing: false);
var newCall = CallInstruction.Create(call.OpCode, newMethod);
newCall.ConstrainedTo = call.ConstrainedTo;
newCall.ILStackWasEmpty = call.ILStackWasEmpty;
newCall.Arguments.Add(call.Arguments[0]);
if (startIndexKind == IndexKind.RefSystemIndex) {
// stloc length(call get_Length/get_Count(ldloc container))
// stloc startOffsetVar(call GetOffset(startIndexLoad, ldloc length))
// complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
// -->
// complex_expr(call get_Item(ldloc container, ldobj startIndexLoad))
newCall.Arguments.Add(new LdObj(startIndexLoad, indexType));
newCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods));
newCall.Arguments.AddRange(call.Arguments.Skip(2));
newCall.AddILRange(call);
for (int i = startPos; i < pos; i++) {
newCall.AddILRange(block.Instructions[i]);
}
call.ReplaceWith(newCall);
block.Instructions.RemoveRange(startPos, pos - startPos);
}
void TransformSlicing()
{
// stloc containerLengthVar(call get_Length(ldloc containerVar))
// stloc startOffset(call GetOffset(startIndexLoad, ldloc length))
// -- we are here --
// stloc sliceLengthVar(binary.sub.i4(call GetOffset(endIndexLoad, ldloc length), ldloc startOffset))
// complex_expr(call Slice(ldloc containerVar, ldloc startOffset, ldloc sliceLength))
if (!block.Instructions[pos].MatchStLoc(out var sliceLengthVar, out var sliceLengthVarInit))
return;
pos++;
if (!(sliceLengthVar.IsSingleDefinition && sliceLengthVar.LoadCount == 1))
return;
if (!MatchSliceLength(sliceLengthVarInit, out IndexKind endIndexKind, out ILInstruction endIndexLoad, containerLengthVar, ref containerVar, startOffsetVar))
return;
if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind, endIndexKind)) {
return;
}
if (rangeVar != null) {
if (!MatchIndexFromRange(startIndexKind, startIndexLoad, rangeVar, "get_Start"))
return;
if (!MatchIndexFromRange(endIndexKind, endIndexLoad, rangeVar, "get_End"))
return;
}
if (!(sliceLengthVar.LoadInstructions.Single().Parent is CallInstruction call))
return;
if (call.Method.Name == "Slice") {
// OK, custom class slicing
} else if (call.Method.Name == "Substring" && call.Method.DeclaringType.IsKnownType(KnownTypeCode.String)) {
// OK, string slicing
} else {
// stloc offsetVar(binary.sub.i4(ldloc containerLengthVar, startIndexLoad))
// complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
// -->
// complex_expr(call get_Item(ldloc container, newobj System.Index(startIndexLoad, fromEnd: true)))
Debug.Assert(startIndexKind == IndexKind.FromEnd);
newCall.Arguments.Add(new NewObj(indexCtor) { Arguments = { startIndexLoad, new LdcI4(1) } });
return;
}
if (call.Method.IsExtensionMethod)
return;
if (call.Method.Parameters.Count != 2)
return;
if (!call.Method.Parameters.All(p => p.Type.IsKnownType(KnownTypeCode.Int32)))
return;
if (call.Arguments.Count != 3)
return;
if (!MatchContainerVar(call.Arguments[0], ref containerVar))
return;
if (!call.Arguments[1].MatchLdLoc(startOffsetVar))
return;
if (!call.Arguments[2].MatchLdLoc(sliceLengthVar))
return;
if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, slicing: true))
return;
if (startIndexKind == IndexKind.FromStart && endIndexKind == IndexKind.FromStart) {
// It's possible we actually have a startIndex/endIndex that involves the container length,
// but we couldn't detect it yet because the statement initializing the containerLengthVar is
// not yet part of the region to be transformed.
// If we transform now, we'd end up with:
// int length = span.Length;
// Console.WriteLine(span[(length - GetInt(1))..(length - GetInt(2))].ToString());
// which is correct but unnecessarily complex.
// So we peek ahead at the next instruction to be transformed:
if (startPos > 0 && MatchContainerLengthStore(block.Instructions[startPos - 1], out _, ref containerVar)) {
// Looks like the transform would be able do to a better job including that previous instruction,
// so let's avoid transforming just yet.
return;
}
// Something similar happens with the rangeVar:
if (startPos > 0 && block.Instructions[startPos - 1] is StLoc stloc && stloc.Variable.Type.IsKnownType(KnownTypeCode.Range)) {
return;
}
}
var specialMethods = new IndexMethods(context.TypeSystem);
if (!specialMethods.AllValid)
return;
context.Step($"{call.Method.Name} sliced with {startIndexKind}..{endIndexKind}", call);
var newMethod = new SyntheticRangeIndexAccessor(call.Method, specialMethods.RangeType, slicing: true);
var newCall = CallInstruction.Create(call.OpCode, newMethod);
newCall.ConstrainedTo = call.ConstrainedTo;
newCall.ILStackWasEmpty = call.ILStackWasEmpty;
newCall.Arguments.Add(call.Arguments[0]);
if (rangeVar != null) {
newCall.Arguments.Add(rangeVarInit);
} else {
var rangeCtorCall = new NewObj(specialMethods.RangeCtor);
rangeCtorCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods));
rangeCtorCall.Arguments.Add(MakeIndex(endIndexKind, endIndexLoad, specialMethods));
newCall.Arguments.Add(rangeCtorCall);
}
newCall.Arguments.AddRange(call.Arguments.Skip(2));
newCall.AddILRange(call);
for (int i = startPos; i < pos; i++) {
newCall.AddILRange(block.Instructions[i]);
@ -172,13 +301,74 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -172,13 +301,74 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
}
/// <summary>
/// Check that the number of uses of the containerLengthVar variable matches those expected in the pattern.
/// </summary>
private bool CheckContainerLengthVariableUseCount(ILVariable containerLengthVar, IndexKind startIndexKind, IndexKind endIndexKind = IndexKind.FromStart)
{
int expectedUses = 0;
if (startIndexKind != IndexKind.FromStart)
expectedUses += 1;
if (endIndexKind != IndexKind.FromStart)
expectedUses += 1;
if (containerLengthVar != null) {
return containerLengthVar.LoadCount == expectedUses;
} else {
return expectedUses <= 1; // can have one inline use
}
}
/// <summary>
/// Matches 'addressof System.Index(call get_Start/get_End(ldloca rangeVar))'
/// </summary>
static bool MatchIndexFromRange(IndexKind indexKind, ILInstruction indexLoad, ILVariable rangeVar, string accessorName)
{
if (indexKind != IndexKind.RefSystemIndex)
return false;
if (!(indexLoad is AddressOf addressOf))
return false;
if (!addressOf.Type.IsKnownType(KnownTypeCode.Index))
return false;
if (!(addressOf.Value is Call call))
return false;
if (call.Method.Name != accessorName)
return false;
if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Range))
return false;
if (call.Arguments.Count != 1)
return false;
return call.Arguments[0].MatchLdLoca(rangeVar);
}
static ILInstruction MakeIndex(IndexKind indexKind, ILInstruction indexLoad, IndexMethods specialMethods)
{
if (indexKind == IndexKind.RefSystemIndex) {
// stloc containerLengthVar(call get_Length/get_Count(ldloc container))
// stloc startOffsetVar(call GetOffset(startIndexLoad, ldloc length))
// complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
// -->
// complex_expr(call get_Item(ldloc container, ldobj startIndexLoad))
return new LdObj(indexLoad, specialMethods.IndexType);
} else if (indexKind == IndexKind.FromEnd) {
// stloc offsetVar(binary.sub.i4(call get_Length/get_Count(ldloc container), startIndexLoad))
// complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
// -->
// complex_expr(call get_Item(ldloc container, newobj System.Index(startIndexLoad, fromEnd: true)))
return new NewObj(specialMethods.IndexCtor) { Arguments = { indexLoad, new LdcI4(1) } };
} else {
Debug.Assert(indexKind == IndexKind.FromStart);
return new Call(specialMethods.IndexImplicitConv) { Arguments = { indexLoad } };
}
}
/// <summary>
/// Gets whether the C# compiler will call `container[int]` when using `container[Index]`.
/// </summary>
private bool CSharpWillGenerateIndexer(IType declaringType)
private bool CSharpWillGenerateIndexer(IType declaringType, bool slicing)
{
bool foundInt32Overload = false;
bool foundIndexOverload = false;
bool foundRangeOverload = false;
bool foundCountProperty = false;
foreach (var prop in declaringType.GetProperties(p => p.IsIndexer || (p.Name == "Length" || p.Name == "Count"))) {
if (prop.IsIndexer && prop.Parameters.Count == 1) {
@ -187,21 +377,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -187,21 +377,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms
foundInt32Overload = true;
} else if (p.Type.IsKnownType(KnownTypeCode.Index)) {
foundIndexOverload = true;
} else if (p.Type.IsKnownType(KnownTypeCode.Range)) {
foundRangeOverload = true;
}
} else if (prop.Name == "Length" || prop.Name=="Count") {
} else if (prop.Name == "Length" || prop.Name == "Count") {
foundCountProperty = true;
}
}
return foundInt32Overload && foundCountProperty && !foundIndexOverload;
if (slicing) {
return /* foundSlicingMethod && */ foundCountProperty && !foundRangeOverload;
} else {
return foundInt32Overload && foundCountProperty && !foundIndexOverload;
}
}
/// <summary>
/// Matches the instruction:
/// stloc containerLengthVar(call get_Length/get_Count(ldloc containerVar))
/// </summary>
static bool MatchContainerLengthStore(ILInstruction inst, out ILVariable lengthVar, out ILVariable containerVar)
static bool MatchContainerLengthStore(ILInstruction inst, out ILVariable lengthVar, ref ILVariable containerVar)
{
containerVar = null;
if (!inst.MatchStLoc(out lengthVar, out var init))
return false;
if (!(lengthVar.IsSingleDefinition && lengthVar.StackType == StackType.I4))
@ -242,16 +437,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -242,16 +437,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (call.Arguments.Count != 1)
return false;
return MatchContainerVar(call.Arguments[0], ref containerVar);
}
static bool MatchContainerVar(ILInstruction inst, ref ILVariable containerVar)
{
if (containerVar != null) {
return call.Arguments[0].MatchLdLoc(containerVar) || call.Arguments[0].MatchLdLoca(containerVar);
return inst.MatchLdLoc(containerVar) || inst.MatchLdLoca(containerVar);
} else {
return call.Arguments[0].MatchLdLoc(out containerVar) || call.Arguments[0].MatchLdLoca(out containerVar);
return inst.MatchLdLoc(out containerVar) || inst.MatchLdLoca(out containerVar);
}
}
enum IndexKind
{
None,
/// <summary>
/// indexLoad is an integer, from the start of the container
/// </summary>
FromStart,
/// <summary>
/// indexLoad is loading the address of a System.Index struct
/// </summary>
@ -264,40 +467,58 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -264,40 +467,58 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary>
/// Matches an instruction computing an offset:
/// stloc offsetVar(call System.Index.GetOffset(indexLoad, ldloc containerLengthVar))
/// call System.Index.GetOffset(indexLoad, ldloc containerLengthVar)
/// or
/// stloc offsetVar(binary.sub.i4(ldloc containerLengthVar, indexLoad))
/// binary.sub.i4(ldloc containerLengthVar, indexLoad)
///
/// Anything else not matching these patterns is interpreted as an `int` expression from the start of the container.
/// </summary>
static IndexKind MatchGetOffset(ILInstruction inst, out ILVariable offsetVar, out ILInstruction indexLoad,
static IndexKind MatchGetOffset(ILInstruction inst, out ILInstruction indexLoad,
ILVariable containerLengthVar, ref ILVariable containerVar)
{
indexLoad = null;
if (!inst.MatchStLoc(out offsetVar, out var offsetValue))
return IndexKind.None;
if (!(offsetVar.IsSingleDefinition && offsetVar.StackType == StackType.I4))
return IndexKind.None;
if (offsetValue is CallInstruction call) {
indexLoad = inst;
if (inst is CallInstruction call) {
// call System.Index.GetOffset(indexLoad, ldloc containerLengthVar)
if (call.Method.Name != "GetOffset")
return IndexKind.None;
return IndexKind.FromStart;
if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index))
return IndexKind.None;
return IndexKind.FromStart;
if (call.Arguments.Count != 2)
return IndexKind.None;
return IndexKind.FromStart;
if (!MatchContainerLength(call.Arguments[1], containerLengthVar, ref containerVar))
return IndexKind.None;
return IndexKind.FromStart;
indexLoad = call.Arguments[0];
return IndexKind.RefSystemIndex;
} else if (offsetValue is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub) {
} else if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub) {
if (bni.CheckForOverflow || bni.ResultType != StackType.I4 || bni.IsLifted)
return IndexKind.None;
return IndexKind.FromStart;
// binary.sub.i4(ldloc containerLengthVar, indexLoad)
if (!MatchContainerLength(bni.Left, containerLengthVar, ref containerVar))
return IndexKind.None;
return IndexKind.FromStart;
indexLoad = bni.Right;
return IndexKind.FromEnd;
} else {
return IndexKind.None;
return IndexKind.FromStart;
}
}
/// <summary>
/// Matches an instruction computing a slice length:
/// binary.sub.i4(call GetOffset(endIndexLoad, ldloc length), ldloc startOffset))
/// </summary>
static bool MatchSliceLength(ILInstruction inst, out IndexKind endIndexKind, out ILInstruction endIndexLoad, ILVariable containerLengthVar, ref ILVariable containerVar, ILVariable startOffsetVar)
{
endIndexKind = default;
endIndexLoad = default;
if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub) {
if (bni.CheckForOverflow || bni.ResultType != StackType.I4 || bni.IsLifted)
return false;
if (!bni.Right.MatchLdLoc(startOffsetVar))
return false;
endIndexKind = MatchGetOffset(bni.Left, out endIndexLoad, containerLengthVar, ref containerVar);
return true;
} else {
return false;
}
}
}

19
ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs

@ -39,19 +39,27 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -39,19 +39,27 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
readonly IMethod underlyingMethod;
readonly IType indexOrRangeType;
readonly IReadOnlyList<IParameter> parameters;
readonly bool slicing;
public SyntheticRangeIndexAccessor(IMethod underlyingMethod, IType indexOrRangeType)
public SyntheticRangeIndexAccessor(IMethod underlyingMethod, IType indexOrRangeType, bool slicing)
{
Debug.Assert(underlyingMethod != null);
Debug.Assert(indexOrRangeType != null);
this.underlyingMethod = underlyingMethod;
this.indexOrRangeType = indexOrRangeType;
this.slicing = slicing;
var parameters = new List<IParameter>();
parameters.Add(new DefaultParameter(indexOrRangeType, ""));
parameters.AddRange(underlyingMethod.Parameters.Skip(1));
if (slicing) {
Debug.Assert(underlyingMethod.Parameters.Count == 2);
} else {
parameters.AddRange(underlyingMethod.Parameters.Skip(1));
}
this.parameters = parameters;
}
public bool IsSlicing => slicing;
bool IMethod.ReturnTypeIsRefReadOnly => underlyingMethod.ReturnTypeIsRefReadOnly;
bool IMethod.ThisIsRefReadOnly => underlyingMethod.ThisIsRefReadOnly;
@ -96,7 +104,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -96,7 +104,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
{
return obj is SyntheticRangeIndexAccessor g
&& this.underlyingMethod.Equals(g.underlyingMethod)
&& this.indexOrRangeType.Equals(g.indexOrRangeType);
&& this.indexOrRangeType.Equals(g.indexOrRangeType)
&& this.slicing == g.slicing;
}
public override int GetHashCode()
@ -117,12 +126,12 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation @@ -117,12 +126,12 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
IMethod IMethod.Specialize(TypeParameterSubstitution substitution)
{
return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType);
return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType, slicing);
}
IMember IMember.Specialize(TypeParameterSubstitution substitution)
{
return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType);
return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType, slicing);
}
}
}

Loading…
Cancel
Save