diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index f5f4337e8..b409376e0 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -90,6 +90,7 @@
+
diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
index 66456641c..7a6cc9aa0 100644
--- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
@@ -89,7 +89,13 @@ namespace ICSharpCode.Decompiler.Tests
RunForLibrary();
RunForLibrary(asmOptions: AssemblerOptions.UseDebug);
}
-
+
+ [Test]
+ public void IndexRangeTest([ValueSource(nameof(dotnetCoreOnlyOptions))] CompilerOptions cscOptions)
+ {
+ RunForLibrary(cscOptions: cscOptions);
+ }
+
[Test]
public void InlineAssignmentTest([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs
new file mode 100644
index 000000000..c4c7f6a6c
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs
@@ -0,0 +1,281 @@
+// 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
+{
+ internal class CustomList
+ {
+ public int Count => 0;
+ public int this[int index] => 0;
+
+ public CustomList Slice(int start, int length)
+ {
+ return this;
+ }
+ }
+
+ internal class CustomList2
+ {
+ public int Count => 0;
+ public int this[int index] => 0;
+ public int this[Index index] => 0;
+ public CustomList2 this[Range range] => this;
+
+ public CustomList2 Slice(int start, int length)
+ {
+ return this;
+ }
+ }
+
+ internal class IndexRangeTest
+ {
+ public static int[] GetArray()
+ {
+ throw null;
+ }
+ public static List GetList()
+ {
+ throw null;
+ }
+ public static Span GetSpan()
+ {
+ throw null;
+ }
+ public static string GetString()
+ {
+ throw null;
+ }
+ public static Index GetIndex(int i = 0)
+ {
+ return i;
+ }
+ public static Range GetRange(int i = 0)
+ {
+ return i..^i;
+ }
+ public static int GetInt(int i = 0)
+ {
+ return i;
+ }
+ public static Range[] SeveralRanges()
+ {
+ // Some of these are semantically identical, but we can still distinguish them in the IL code:
+ return new Range[14] {
+ ..,
+ 0..,
+ ^0..,
+ GetInt(1)..,
+ ^GetInt(2)..,
+ ..0,
+ ..^0,
+ ..GetInt(3),
+ ..^GetInt(4),
+ 0..^0,
+ ^0..0,
+ 0..0,
+ GetInt(5)..GetInt(6),
+ 0..(GetInt(7) + GetInt(8))
+ };
+ }
+
+ public static void UseIndex()
+ {
+ Console.WriteLine(GetArray()[GetIndex()]);
+ Console.WriteLine(GetList()[GetIndex()]);
+ Console.WriteLine(GetSpan()[GetIndex()]);
+ Console.WriteLine(GetString()[GetIndex()]);
+ Console.WriteLine(new CustomList()[GetIndex()]);
+ Console.WriteLine(new CustomList2()[GetIndex()]);
+ }
+
+ public static void UseIndexFromEnd()
+ {
+ Console.WriteLine(GetArray()[^GetInt()]);
+ Console.WriteLine(GetList()[^GetInt()]);
+ Console.WriteLine(GetSpan()[^GetInt()]);
+ Console.WriteLine(GetString()[^GetInt()]);
+ Console.WriteLine(new CustomList()[^GetInt()]);
+ Console.WriteLine(new CustomList2()[^GetInt()]);
+ }
+
+ public static void UseIndexForWrite()
+ {
+ GetArray()[GetIndex()] = GetInt();
+ GetList()[GetIndex()] = GetInt();
+ GetSpan()[GetIndex()] = GetInt();
+ }
+
+ private static void UseRef(ref int i)
+ {
+ }
+
+ public static void UseIndexForRef()
+ {
+ UseRef(ref GetArray()[GetIndex()]);
+ UseRef(ref GetArray()[^GetInt()]);
+ UseRef(ref GetSpan()[GetIndex()]);
+ UseRef(ref GetSpan()[^GetInt()]);
+ }
+
+ public static void UseRange()
+ {
+ Console.WriteLine(GetArray()[GetRange()]);
+ //Console.WriteLine(GetList()[GetRange()]); // fails to compile
+ Console.WriteLine(GetSpan()[GetRange()].ToString());
+ Console.WriteLine(GetString()[GetRange()]);
+ Console.WriteLine(new CustomList()[GetRange()]);
+ Console.WriteLine(new CustomList2()[GetRange()]);
+ }
+ public static void UseNewRangeFromIndex()
+ {
+ 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)]);
+ //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)]);
+ Console.WriteLine(new CustomList2()[GetInt(1)..GetInt(2)]);
+ }
+ public static void UseNewRangeFromIntegers_BothFromEnd()
+ {
+ 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)]);
+ Console.WriteLine(new CustomList2()[^GetInt(1)..^GetInt(2)]);
+ }
+ public static void UseNewRangeFromIntegers_FromStartAndEnd()
+ {
+ 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)]);
+ Console.WriteLine(new CustomList2()[GetInt(1)..^GetInt(2)]);
+ }
+ public static void UseNewRangeFromIntegers_FromEndAndStart()
+ {
+ 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)]);
+ Console.WriteLine(new CustomList2()[^GetInt(1)..GetInt(2)]);
+ }
+
+ public static void UseNewRangeFromIntegers_OnlyEndPoint()
+ {
+ Console.WriteLine(GetArray()[..GetInt(2)]);
+ //Console.WriteLine(GetList()[..GetInt()]); // fails to compile
+ Console.WriteLine(GetSpan()[..GetInt(2)].ToString());
+ Console.WriteLine(GetString()[..GetInt(2)]);
+ Console.WriteLine(new CustomList()[..GetInt(2)]);
+ Console.WriteLine(new CustomList2()[..GetInt(2)]);
+ }
+
+ public static void UseNewRangeFromIntegers_OnlyEndPoint_FromEnd()
+ {
+ Console.WriteLine(GetArray()[..^GetInt(2)]);
+ //Console.WriteLine(GetList()[..^GetInt()]); // fails to compile
+ Console.WriteLine(GetSpan()[..^GetInt(2)].ToString());
+ Console.WriteLine(GetString()[..^GetInt(2)]);
+ Console.WriteLine(new CustomList()[..^GetInt(2)]);
+ Console.WriteLine(new CustomList2()[..^GetInt(2)]);
+ }
+
+ public static void UseNewRangeFromIntegers_OnlyStartPoint()
+ {
+ Console.WriteLine(GetArray()[GetInt(1)..]);
+ //Console.WriteLine(GetList()[GetInt()..]); // fails to compile
+ Console.WriteLine(GetSpan()[GetInt(1)..].ToString());
+ Console.WriteLine(GetString()[GetInt(1)..]);
+ Console.WriteLine(new CustomList()[GetInt(1)..]);
+ Console.WriteLine(new CustomList2()[GetInt(1)..]);
+ }
+
+ public static void UseNewRangeFromIntegers_OnlyStartPoint_FromEnd()
+ {
+ Console.WriteLine(GetArray()[^GetInt(1)..]);
+ //Console.WriteLine(GetList()[^GetInt()..]); // fails to compile
+ Console.WriteLine(GetSpan()[^GetInt(1)..].ToString());
+ Console.WriteLine(GetString()[^GetInt(1)..]);
+ Console.WriteLine(new CustomList()[^GetInt(1)..]);
+ Console.WriteLine(new CustomList2()[^GetInt(1)..]);
+ }
+
+ public static void UseConstantRange()
+ {
+ // Fortunately the C# compiler doesn't optimize
+ // "str.Length - 2 - 1" here, so the normal pattern applies.
+ Console.WriteLine(GetString()[1..2]);
+ Console.WriteLine(GetString()[1..^1]);
+ Console.WriteLine(GetString()[^2..^1]);
+
+ Console.WriteLine(GetString()[..1]);
+ Console.WriteLine(GetString()[..^1]);
+ Console.WriteLine(GetString()[1..]);
+ Console.WriteLine(GetString()[^1..]);
+ }
+
+ public static void UseWholeRange()
+ {
+ Console.WriteLine(GetArray()[..]);
+ //Console.WriteLine(GetList()[..]); // fails to compile
+ Console.WriteLine(GetSpan()[..].ToString());
+ Console.WriteLine(GetString()[..]);
+ Console.WriteLine(new CustomList()[..]);
+ Console.WriteLine(new CustomList2()[..]);
+ }
+
+ public static void UseIndexForIntIndexerWhenIndexIndexerIsAvailable()
+ {
+ // Same code as the compiler emits for CustomList,
+ // but here we can't translate it back to `customList[GetIndex()]`
+ // because that would call a different overload.
+ CustomList2 customList = new CustomList2();
+ int count = customList.Count;
+ int offset = GetIndex().GetOffset(count);
+ Console.WriteLine(customList[offset]);
+ }
+
+ public static void UseSliceWhenRangeIndexerIsAvailable()
+ {
+ // Same code as the compiler emits for CustomList,
+ // but here we can't translate it back to `customList[GetIndex()]`
+ // because that would call a different overload.
+ CustomList2 customList = new CustomList2();
+ int count = customList.Count;
+ Range range = GetRange();
+ int offset = range.Start.GetOffset(count);
+ int length = range.End.GetOffset(count) - offset;
+ Console.WriteLine(customList.Slice(offset, length));
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs
index 6d2191833..6e927db63 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs
@@ -68,6 +68,48 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
}
}
+
+ private class Container
+ {
+ public GenericStruct Other;
+ }
+
+ private struct GenericStruct
+ {
+ public T1 Field1;
+ public T2 Field2;
+ public Container Other;
+
+ public override string ToString()
+ {
+ return "(" + Field1?.ToString() + ", " + Field2?.ToString() + ")";
+ }
+
+ public int? GetTextLength()
+ {
+ return Field1?.ToString().Length + Field2?.ToString().Length + 4;
+ }
+
+ public string Chain1()
+ {
+ return Other?.Other.Other?.Other.Field1?.ToString();
+ }
+
+ public string Chain2()
+ {
+ return Other?.Other.Other?.Other.Field1?.ToString()?.GetType().Name;
+ }
+
+ public int? Test2()
+ {
+ return Field1?.ToString().Length ?? 42;
+ }
+
+ public int? GetTextLengthNRE()
+ {
+ return (Field1?.ToString()).Length;
+ }
+ }
public interface ITest
{
@@ -243,12 +285,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return t?.Int();
}
- // See also: https://github.com/icsharpcode/ILSpy/issues/1050
- // The C# compiler generates pretty weird code in this case.
- //private static int? GenericRefUnconstrainedInt(ref T t) where T : ITest
- //{
- // return t?.Int();
- //}
+ private static int? GenericRefUnconstrainedInt(ref T t) where T : ITest
+ {
+ return t?.Int();
+ }
private static int? GenericRefClassConstraintInt(ref T t) where T : class, ITest
{
diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index d5d9ce9d9..92f1594fd 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -150,7 +150,8 @@ namespace ICSharpCode.Decompiler.CSharp
new TransformCollectionAndObjectInitializers(),
new TransformExpressionTrees(),
new NamedArgumentTransform(),
- new UserDefinedLogicTransform()
+ new UserDefinedLogicTransform(),
+ new IndexRangeTransform()
),
}
},
@@ -365,7 +366,7 @@ namespace ICSharpCode.Decompiler.CSharp
static bool IsAnonymousMethodCacheField(SRM.FieldDefinition field, MetadataReader metadata)
{
var name = metadata.GetString(field.Name);
- return name.StartsWith("CS$<>", StringComparison.Ordinal) || name.StartsWith("<>f__am", StringComparison.Ordinal);
+ return name.StartsWith("CS$<>", StringComparison.Ordinal) || name.StartsWith("<>f__am", StringComparison.Ordinal) || name.StartsWith("<>f__mg", StringComparison.Ordinal);
}
static bool IsClosureType(SRM.TypeDefinition type, MetadataReader metadata)
diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
index 12da7ce17..9eeec424c 100644
--- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs
@@ -253,6 +253,12 @@ namespace ICSharpCode.Decompiler.CSharp
argumentList.ExpectedParameters = method.Parameters.ToArray();
}
+ if (settings.Ranges) {
+ if (HandleRangeConstruction(out var result, callOpCode, method, target, argumentList)) {
+ return result;
+ }
+ }
+
if (callOpCode == OpCode.NewObj) {
return HandleConstructorCall(expectedTargetDetails, target.ResolveResult, method, argumentList);
}
@@ -1495,5 +1501,47 @@ namespace ICSharpCode.Decompiler.CSharp
return Build(call.OpCode, call.Method, arguments, argumentToParameterMap, call.ConstrainedTo)
.WithILInstruction(call).WithILInstruction(block);
}
+
+ private bool HandleRangeConstruction(out ExpressionWithResolveResult result, OpCode callOpCode, IMethod method, TranslatedExpression target, ArgumentList argumentList)
+ {
+ result = default;
+ if (argumentList.ArgumentNames != null) {
+ return false; // range syntax doesn't support named arguments
+ }
+ if (method.DeclaringType.IsKnownType(KnownTypeCode.Range)) {
+ if (callOpCode == OpCode.NewObj && argumentList.Length == 2) {
+ result = new BinaryOperatorExpression(argumentList.Arguments[0], BinaryOperatorType.Range, argumentList.Arguments[1])
+ .WithRR(new MemberResolveResult(null, method));
+ return true;
+ } else if (callOpCode == OpCode.Call && method.Name == "get_All" && argumentList.Length == 0) {
+ result = new BinaryOperatorExpression(Expression.Null, BinaryOperatorType.Range, Expression.Null)
+ .WithRR(new MemberResolveResult(null, method.AccessorOwner ?? method));
+ return true;
+ } else if (callOpCode == OpCode.Call && method.Name == "StartAt" && argumentList.Length == 1) {
+ result = new BinaryOperatorExpression(argumentList.Arguments[0], BinaryOperatorType.Range, Expression.Null)
+ .WithRR(new MemberResolveResult(null, method));
+ return true;
+ } else if (callOpCode == OpCode.Call && method.Name == "EndAt" && argumentList.Length == 1) {
+ result = new BinaryOperatorExpression(Expression.Null, BinaryOperatorType.Range, argumentList.Arguments[0])
+ .WithRR(new MemberResolveResult(null, method));
+ return true;
+ }
+ } else if (callOpCode == OpCode.NewObj && method.DeclaringType.IsKnownType(KnownTypeCode.Index)) {
+ if (argumentList.Length != 2)
+ return false;
+ if (!(argumentList.Arguments[1].Expression is PrimitiveExpression pe && pe.Value is true))
+ return false;
+ 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;
+ }
}
}
\ No newline at end of file
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 31f9fa0f1..7c6660e50 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
@@ -2244,14 +2244,23 @@ namespace ICSharpCode.Decompiler.CSharp
arrayType = new ArrayType(compilation, inst.Type, inst.Indices.Count);
arrayExpr = arrayExpr.ConvertTo(arrayType, this);
}
- TranslatedExpression expr = new IndexerExpression(
- arrayExpr, inst.Indices.Select(i => TranslateArrayIndex(i).Expression)
- ).WithILInstruction(inst).WithRR(new ResolveResult(arrayType.ElementType));
+ IndexerExpression indexerExpr;
+ if (inst.WithSystemIndex) {
+ var systemIndex = compilation.FindType(KnownTypeCode.Index);
+ indexerExpr = new IndexerExpression(
+ arrayExpr, inst.Indices.Select(i => Translate(i, typeHint: systemIndex).ConvertTo(systemIndex, this).Expression)
+ );
+ } else {
+ indexerExpr = new IndexerExpression(
+ arrayExpr, inst.Indices.Select(i => TranslateArrayIndex(i).Expression)
+ );
+ }
+ TranslatedExpression expr = indexerExpr.WithILInstruction(inst).WithRR(new ResolveResult(arrayType.ElementType));
return new DirectionExpression(FieldDirection.Ref, expr)
.WithoutILInstruction().WithRR(new ByReferenceResolveResult(expr.Type, ReferenceKind.Ref));
}
- TranslatedExpression TranslateArrayIndex(ILInstruction i)
+ TranslatedExpression TranslateArrayIndex(ILInstruction i, bool expectSystemIndex = false)
{
var input = Translate(i);
KnownTypeCode targetType;
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
index faa67d21c..1d3f99617 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
@@ -696,6 +696,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
case BinaryOperatorType.NullCoalescing:
spacePolicy = true;
break;
+ case BinaryOperatorType.Range:
+ spacePolicy = false;
+ break;
default:
throw new NotSupportedException("Invalid value for BinaryOperatorType");
}
@@ -1812,11 +1815,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
Space();
WriteKeyword(CatchClause.WhenKeywordRole);
Space(policy.SpaceBeforeIfParentheses);
- LPar();
+ WriteToken(CatchClause.CondLPar);
Space(policy.SpacesWithinIfParentheses);
catchClause.Condition.AcceptVisitor(this);
Space(policy.SpacesWithinIfParentheses);
- RPar();
+ WriteToken(CatchClause.CondRPar);
}
WriteBlock(catchClause.Body, policy.StatementBraceStyle);
EndNode(catchClause);
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertMissingTokensDecorator.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertMissingTokensDecorator.cs
index 7a56242a0..c40094a20 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertMissingTokensDecorator.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertMissingTokensDecorator.cs
@@ -46,6 +46,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
} else if (node is Comment comment) {
comment.SetStartLocation(locationProvider.Location);
}
+ if (node is ErrorExpression error) {
+ error.Location = locationProvider.Location;
+ }
base.StartNode(node);
}
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
index 3af55614f..4791b3127 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
+// Copyright (c) 2010-2020 AlphaSierraPapa for the SharpDevelop Team
//
// 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
@@ -36,25 +36,39 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
///
public bool InsertParenthesesForReadability { get; set; }
- const int Primary = 17;
- const int NullableRewrap = 16;
- const int QueryOrLambda = 15;
- const int Unary = 14;
- const int RelationalAndTypeTesting = 10;
- const int Equality = 9;
- const int Conditional = 2;
- const int Assignment = 1;
+ enum PrecedenceLevel
+ {
+ // Higher integer value = higher precedence.
+ Assignment,
+ Conditional, // ?:
+ NullCoalescing, // ??
+ ConditionalOr, // ||
+ ConditionalAnd, // &&
+ BitwiseOr, // |
+ ExclusiveOr, // binary ^
+ BitwiseAnd, // binary &
+ Equality, // == !=
+ RelationalAndTypeTesting, // < <= > >= is
+ Shift, // << >>
+ Additive, // binary + -
+ Multiplicative, // * / %
+ Range, // ..
+ Unary,
+ QueryOrLambda,
+ NullableRewrap,
+ Primary
+ }
///
/// Gets the row number in the C# 4.0 spec operator precedence table.
///
- static int GetPrecedence(Expression expr)
+ static PrecedenceLevel GetPrecedence(Expression expr)
{
// Note: the operator precedence table on MSDN is incorrect
if (expr is QueryExpression) {
// Not part of the table in the C# spec, but we need to ensure that queries within
// primary expressions get parenthesized.
- return QueryOrLambda;
+ return PrecedenceLevel.QueryOrLambda;
}
if (expr is UnaryOperatorExpression uoe) {
switch (uoe.Operator) {
@@ -62,81 +76,83 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
case UnaryOperatorType.PostIncrement:
case UnaryOperatorType.NullConditional:
case UnaryOperatorType.SuppressNullableWarning:
- return Primary;
+ return PrecedenceLevel.Primary;
case UnaryOperatorType.NullConditionalRewrap:
- return NullableRewrap;
+ return PrecedenceLevel.NullableRewrap;
case UnaryOperatorType.IsTrue:
- return Conditional;
+ return PrecedenceLevel.Conditional;
default:
- return Unary;
+ return PrecedenceLevel.Unary;
}
}
if (expr is CastExpression)
- return Unary;
+ return PrecedenceLevel.Unary;
if (expr is PrimitiveExpression primitive) {
var value = primitive.Value;
if (value is int i && i < 0)
- return Unary;
+ return PrecedenceLevel.Unary;
if (value is long l && l < 0)
- return Unary;
+ return PrecedenceLevel.Unary;
if (value is float f && f < 0)
- return Unary;
+ return PrecedenceLevel.Unary;
if (value is double d && d < 0)
- return Unary;
+ return PrecedenceLevel.Unary;
if (value is decimal de && de < 0)
- return Unary;
+ return PrecedenceLevel.Unary;
+ return PrecedenceLevel.Primary;
}
- BinaryOperatorExpression boe = expr as BinaryOperatorExpression;
- if (boe != null) {
+ if (expr is BinaryOperatorExpression boe) {
switch (boe.Operator) {
+ case BinaryOperatorType.Range:
+ return PrecedenceLevel.Range;
case BinaryOperatorType.Multiply:
case BinaryOperatorType.Divide:
case BinaryOperatorType.Modulus:
- return 13; // multiplicative
+ return PrecedenceLevel.Multiplicative;
case BinaryOperatorType.Add:
case BinaryOperatorType.Subtract:
- return 12; // additive
+ return PrecedenceLevel.Additive;
case BinaryOperatorType.ShiftLeft:
case BinaryOperatorType.ShiftRight:
- return 11;
+ return PrecedenceLevel.Shift;
case BinaryOperatorType.GreaterThan:
case BinaryOperatorType.GreaterThanOrEqual:
case BinaryOperatorType.LessThan:
case BinaryOperatorType.LessThanOrEqual:
- return RelationalAndTypeTesting;
+ return PrecedenceLevel.RelationalAndTypeTesting;
case BinaryOperatorType.Equality:
case BinaryOperatorType.InEquality:
- return Equality;
+ return PrecedenceLevel.Equality;
case BinaryOperatorType.BitwiseAnd:
- return 8;
+ return PrecedenceLevel.BitwiseAnd;
case BinaryOperatorType.ExclusiveOr:
- return 7;
+ return PrecedenceLevel.ExclusiveOr;
case BinaryOperatorType.BitwiseOr:
- return 6;
+ return PrecedenceLevel.BitwiseOr;
case BinaryOperatorType.ConditionalAnd:
- return 5;
+ return PrecedenceLevel.ConditionalAnd;
case BinaryOperatorType.ConditionalOr:
- return 4;
+ return PrecedenceLevel.ConditionalOr;
case BinaryOperatorType.NullCoalescing:
- return 3;
+ return PrecedenceLevel.NullCoalescing;
default:
throw new NotSupportedException("Invalid value for BinaryOperatorType");
}
}
if (expr is IsExpression || expr is AsExpression)
- return RelationalAndTypeTesting;
+ return PrecedenceLevel.RelationalAndTypeTesting;
if (expr is ConditionalExpression || expr is DirectionExpression)
- return Conditional;
+ return PrecedenceLevel.Conditional;
if (expr is AssignmentExpression || expr is LambdaExpression)
- return Assignment;
+ return PrecedenceLevel.Assignment;
// anything else: primary expression
- return Primary;
+ return PrecedenceLevel.Primary;
}
///
/// Parenthesizes the expression if it does not have the minimum required precedence.
///
- static void ParenthesizeIfRequired(Expression expr, int minimumPrecedence)
+ static void ParenthesizeIfRequired(Expression expr, PrecedenceLevel minimumPrecedence)
{
if (GetPrecedence(expr) < minimumPrecedence) {
Parenthesize(expr);
@@ -151,25 +167,25 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// Primary expressions
public override void VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression)
{
- ParenthesizeIfRequired(memberReferenceExpression.Target, Primary);
+ ParenthesizeIfRequired(memberReferenceExpression.Target, PrecedenceLevel.Primary);
base.VisitMemberReferenceExpression(memberReferenceExpression);
}
public override void VisitPointerReferenceExpression(PointerReferenceExpression pointerReferenceExpression)
{
- ParenthesizeIfRequired(pointerReferenceExpression.Target, Primary);
+ ParenthesizeIfRequired(pointerReferenceExpression.Target, PrecedenceLevel.Primary);
base.VisitPointerReferenceExpression(pointerReferenceExpression);
}
public override void VisitInvocationExpression(InvocationExpression invocationExpression)
{
- ParenthesizeIfRequired(invocationExpression.Target, Primary);
+ ParenthesizeIfRequired(invocationExpression.Target, PrecedenceLevel.Primary);
base.VisitInvocationExpression(invocationExpression);
}
public override void VisitIndexerExpression(IndexerExpression indexerExpression)
{
- ParenthesizeIfRequired(indexerExpression.Target, Primary);
+ ParenthesizeIfRequired(indexerExpression.Target, PrecedenceLevel.Primary);
ArrayCreateExpression ace = indexerExpression.Target as ArrayCreateExpression;
if (ace != null && (InsertParenthesesForReadability || ace.Initializer.IsNull)) {
// require parentheses for "(new int[1])[0]"
@@ -192,7 +208,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{
// Even in readability mode, don't parenthesize casts of casts.
if (!(castExpression.Expression is CastExpression)) {
- ParenthesizeIfRequired(castExpression.Expression, InsertParenthesesForReadability ? NullableRewrap : Unary);
+ ParenthesizeIfRequired(castExpression.Expression, InsertParenthesesForReadability ? PrecedenceLevel.NullableRewrap : PrecedenceLevel.Unary);
}
// There's a nasty issue in the C# grammar: cast expressions including certain operators are ambiguous in some cases
// "(int)-1" is fine, but "(A)-b" is not a cast.
@@ -255,14 +271,14 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// Binary Operators
public override void VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression)
{
- int precedence = GetPrecedence(binaryOperatorExpression);
+ PrecedenceLevel precedence = GetPrecedence(binaryOperatorExpression);
if (binaryOperatorExpression.Operator == BinaryOperatorType.NullCoalescing) {
if (InsertParenthesesForReadability) {
- ParenthesizeIfRequired(binaryOperatorExpression.Left, NullableRewrap);
+ ParenthesizeIfRequired(binaryOperatorExpression.Left, PrecedenceLevel.NullableRewrap);
if (GetBinaryOperatorType(binaryOperatorExpression.Right) == BinaryOperatorType.NullCoalescing) {
ParenthesizeIfRequired(binaryOperatorExpression.Right, precedence);
} else {
- ParenthesizeIfRequired(binaryOperatorExpression.Right, NullableRewrap);
+ ParenthesizeIfRequired(binaryOperatorExpression.Right, PrecedenceLevel.NullableRewrap);
}
} else {
// ?? is right-associative
@@ -270,10 +286,10 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
ParenthesizeIfRequired(binaryOperatorExpression.Right, precedence);
}
} else {
- if (InsertParenthesesForReadability && precedence < Equality) {
+ if (InsertParenthesesForReadability && precedence < PrecedenceLevel.Equality) {
// In readable mode, boost the priority of the left-hand side if the operator
// there isn't the same as the operator on this expression.
- int boostTo = IsBitwise(binaryOperatorExpression.Operator) ? Unary : Equality;
+ PrecedenceLevel boostTo = IsBitwise(binaryOperatorExpression.Operator) ? PrecedenceLevel.Unary : PrecedenceLevel.Equality;
if (GetBinaryOperatorType(binaryOperatorExpression.Left) == binaryOperatorExpression.Operator) {
ParenthesizeIfRequired(binaryOperatorExpression.Left, precedence);
} else {
@@ -309,9 +325,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{
if (InsertParenthesesForReadability) {
// few people know the precedence of 'is', so always put parentheses in nice-looking mode.
- ParenthesizeIfRequired(isExpression.Expression, NullableRewrap);
+ ParenthesizeIfRequired(isExpression.Expression, PrecedenceLevel.NullableRewrap);
} else {
- ParenthesizeIfRequired(isExpression.Expression, RelationalAndTypeTesting);
+ ParenthesizeIfRequired(isExpression.Expression, PrecedenceLevel.RelationalAndTypeTesting);
}
base.VisitIsExpression(isExpression);
}
@@ -320,9 +336,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{
if (InsertParenthesesForReadability) {
// few people know the precedence of 'as', so always put parentheses in nice-looking mode.
- ParenthesizeIfRequired(asExpression.Expression, NullableRewrap);
+ ParenthesizeIfRequired(asExpression.Expression, PrecedenceLevel.NullableRewrap);
} else {
- ParenthesizeIfRequired(asExpression.Expression, RelationalAndTypeTesting);
+ ParenthesizeIfRequired(asExpression.Expression, PrecedenceLevel.RelationalAndTypeTesting);
}
base.VisitAsExpression(asExpression);
}
@@ -341,13 +357,13 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// Only ((a ? b : c) ? d : e) strictly needs the additional parentheses
if (InsertParenthesesForReadability && !IsConditionalRefExpression(conditionalExpression)) {
// Precedence of ?: can be confusing; so always put parentheses in nice-looking mode.
- ParenthesizeIfRequired(conditionalExpression.Condition, NullableRewrap);
- ParenthesizeIfRequired(conditionalExpression.TrueExpression, NullableRewrap);
- ParenthesizeIfRequired(conditionalExpression.FalseExpression, NullableRewrap);
+ ParenthesizeIfRequired(conditionalExpression.Condition, PrecedenceLevel.NullableRewrap);
+ ParenthesizeIfRequired(conditionalExpression.TrueExpression, PrecedenceLevel.NullableRewrap);
+ ParenthesizeIfRequired(conditionalExpression.FalseExpression, PrecedenceLevel.NullableRewrap);
} else {
- ParenthesizeIfRequired(conditionalExpression.Condition, Conditional + 1);
- ParenthesizeIfRequired(conditionalExpression.TrueExpression, Conditional);
- ParenthesizeIfRequired(conditionalExpression.FalseExpression, Conditional);
+ ParenthesizeIfRequired(conditionalExpression.Condition, PrecedenceLevel.Conditional + 1);
+ ParenthesizeIfRequired(conditionalExpression.TrueExpression, PrecedenceLevel.Conditional);
+ ParenthesizeIfRequired(conditionalExpression.FalseExpression, PrecedenceLevel.Conditional);
}
base.VisitConditionalExpression(conditionalExpression);
}
@@ -361,11 +377,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public override void VisitAssignmentExpression(AssignmentExpression assignmentExpression)
{
// assignment is right-associative
- ParenthesizeIfRequired(assignmentExpression.Left, Assignment + 1);
+ ParenthesizeIfRequired(assignmentExpression.Left, PrecedenceLevel.Assignment + 1);
if (InsertParenthesesForReadability && !(assignmentExpression.Right is DirectionExpression)) {
- ParenthesizeIfRequired(assignmentExpression.Right, RelationalAndTypeTesting + 1);
+ ParenthesizeIfRequired(assignmentExpression.Right, PrecedenceLevel.RelationalAndTypeTesting + 1);
} else {
- ParenthesizeIfRequired(assignmentExpression.Right, Assignment);
+ ParenthesizeIfRequired(assignmentExpression.Right, PrecedenceLevel.Assignment);
}
base.VisitAssignmentExpression(assignmentExpression);
}
@@ -394,7 +410,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public override void VisitNamedExpression (NamedExpression namedExpression)
{
if (InsertParenthesesForReadability) {
- ParenthesizeIfRequired(namedExpression.Expression, RelationalAndTypeTesting + 1);
+ ParenthesizeIfRequired(namedExpression.Expression, PrecedenceLevel.RelationalAndTypeTesting + 1);
}
base.VisitNamedExpression (namedExpression);
}
diff --git a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs
index 399aa611b..7993932c9 100644
--- a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs
@@ -113,7 +113,13 @@ namespace ICSharpCode.Decompiler.CSharp
ILInstruction blockContainer = blockStatement.Annotations.OfType().FirstOrDefault();
if (blockContainer != null) {
StartSequencePoint(blockStatement.LBraceToken);
- int intervalStart = blockContainer.ILRanges.First().Start;
+ int intervalStart;
+ if (blockContainer.Parent is TryCatchHandler handler && !handler.ExceptionSpecifierILRange.IsEmpty) {
+ // if this block container is part of a TryCatchHandler, do not steal the exception-specifier IL range
+ intervalStart = handler.ExceptionSpecifierILRange.End;
+ } else {
+ intervalStart = blockContainer.StartILOffset;
+ }
// The end will be set to the first sequence point candidate location before the first statement of the function when the seqeunce point is adjusted
int intervalEnd = intervalStart + 1;
@@ -256,12 +262,15 @@ namespace ICSharpCode.Decompiler.CSharp
foreachStatement.InExpression.AcceptVisitor(this);
AddToSequencePoint(foreachInfo.GetEnumeratorCall);
EndSequencePoint(foreachStatement.InExpression.StartLocation, foreachStatement.InExpression.EndLocation);
+
StartSequencePoint(foreachStatement);
AddToSequencePoint(foreachInfo.MoveNextCall);
EndSequencePoint(foreachStatement.InToken.StartLocation, foreachStatement.InToken.EndLocation);
+
StartSequencePoint(foreachStatement);
AddToSequencePoint(foreachInfo.GetCurrentCall);
EndSequencePoint(foreachStatement.VariableType.StartLocation, foreachStatement.VariableNameToken.EndLocation);
+
VisitAsSequencePoint(foreachStatement.EmbeddedStatement);
}
@@ -310,6 +319,33 @@ namespace ICSharpCode.Decompiler.CSharp
VisitAsSequencePoint(fixedStatement.EmbeddedStatement);
}
+ public override void VisitTryCatchStatement(TryCatchStatement tryCatchStatement)
+ {
+ VisitAsSequencePoint(tryCatchStatement.TryBlock);
+ foreach (var c in tryCatchStatement.CatchClauses) {
+ VisitAsSequencePoint(c);
+ }
+ VisitAsSequencePoint(tryCatchStatement.FinallyBlock);
+ }
+
+ public override void VisitCatchClause(CatchClause catchClause)
+ {
+ if (catchClause.Condition.IsNull) {
+ var tryCatchHandler = catchClause.Annotation();
+ if (tryCatchHandler != null && !tryCatchHandler.ExceptionSpecifierILRange.IsEmpty) {
+ StartSequencePoint(catchClause.CatchToken);
+ var function = tryCatchHandler.Ancestors.OfType().FirstOrDefault();
+ AddToSequencePointRaw(function, new[] { tryCatchHandler.ExceptionSpecifierILRange });
+ EndSequencePoint(catchClause.CatchToken.StartLocation, catchClause.RParToken.IsNull ? catchClause.CatchToken.EndLocation : catchClause.RParToken.EndLocation);
+ }
+ } else {
+ StartSequencePoint(catchClause.WhenToken);
+ AddToSequencePoint(catchClause.Condition);
+ EndSequencePoint(catchClause.WhenToken.StartLocation, catchClause.CondRParToken.EndLocation);
+ }
+ VisitAsSequencePoint(catchClause.Body);
+ }
+
///
/// Start a new C# statement = new sequence point.
///
@@ -339,6 +375,13 @@ namespace ICSharpCode.Decompiler.CSharp
current = outerStates.Pop();
}
+ void AddToSequencePointRaw(ILFunction function, IEnumerable ranges)
+ {
+ current.Intervals.AddRange(ranges);
+ Debug.Assert(current.Function == null || current.Function == function);
+ current.Function = function;
+ }
+
///
/// Add the ILAst instruction associated with the AstNode to the sequence point.
/// Also add all its ILAst sub-instructions (unless they were already added to another sequence point).
diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
index e814836ea..076b2fcf1 100644
--- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
@@ -363,6 +363,7 @@ namespace ICSharpCode.Decompiler.CSharp
tryCatch.TryBlock = ConvertAsBlock(inst.TryBlock);
foreach (var handler in inst.Handlers) {
var catchClause = new CatchClause();
+ catchClause.AddAnnotation(handler);
var v = handler.Variable;
if (v != null) {
catchClause.AddAnnotation(new ILVariableResolveResult(v, v.Type));
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs
index a7eb4d6db..b6358f56a 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs
@@ -54,6 +54,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public readonly static TokenRole ShiftLeftRole = new TokenRole ("<<");
public readonly static TokenRole ShiftRightRole = new TokenRole (">>");
public readonly static TokenRole NullCoalescingRole = new TokenRole ("??");
+ public readonly static TokenRole RangeRole = new TokenRole ("..");
public readonly static Role LeftRole = new Role("Left", Expression.Null);
public readonly static Role RightRole = new Role("Right", Expression.Null);
@@ -151,6 +152,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return ShiftRightRole;
case BinaryOperatorType.NullCoalescing:
return NullCoalescingRole;
+ case BinaryOperatorType.Range:
+ return RangeRole;
default:
throw new NotSupportedException("Invalid value for BinaryOperatorType");
}
@@ -197,6 +200,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return ExpressionType.RightShift;
case BinaryOperatorType.NullCoalescing:
return ExpressionType.Coalesce;
+ case BinaryOperatorType.Range:
+ return ExpressionType.Extension;
default:
throw new NotSupportedException("Invalid value for BinaryOperatorType");
}
@@ -255,6 +260,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
ShiftRight,
/// left ?? right
- NullCoalescing
+ NullCoalescing,
+ /// left .. right
+ /// left and right are optional = may be Expression.Null
+ Range
}
}
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs
index 31e9bd9f1..eb29f2d65 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs
@@ -45,6 +45,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public readonly static TokenRole AwaitRole = new TokenRole ("await");
public readonly static TokenRole NullConditionalRole = new TokenRole ("?");
public readonly static TokenRole SuppressNullableWarningRole = new TokenRole ("!");
+ public readonly static TokenRole IndexFromEndRole = new TokenRole ("^");
public UnaryOperatorExpression()
{
@@ -122,6 +123,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return null; // no syntax
case UnaryOperatorType.SuppressNullableWarning:
return SuppressNullableWarningRole;
+ case UnaryOperatorType.IndexFromEnd:
+ return IndexFromEndRole;
default:
throw new NotSupportedException("Invalid value for UnaryOperatorType");
}
@@ -150,6 +153,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
case UnaryOperatorType.AddressOf:
case UnaryOperatorType.Await:
case UnaryOperatorType.SuppressNullableWarning:
+ case UnaryOperatorType.IndexFromEnd:
return ExpressionType.Extension;
default:
throw new NotSupportedException("Invalid value for UnaryOperatorType");
@@ -206,5 +210,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// C# 8 postfix ! operator (dammit operator)
///
SuppressNullableWarning,
+ ///
+ /// C# 8 prefix ^ operator
+ ///
+ IndexFromEnd,
}
}
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Statements/TryCatchStatement.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Statements/TryCatchStatement.cs
index 33f16e283..f3ad325bc 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/Statements/TryCatchStatement.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/Statements/TryCatchStatement.cs
@@ -32,68 +32,70 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
///
public class TryCatchStatement : Statement
{
- public static readonly TokenRole TryKeywordRole = new TokenRole ("try");
+ public static readonly TokenRole TryKeywordRole = new TokenRole("try");
public static readonly Role TryBlockRole = new Role("TryBlock", BlockStatement.Null);
public static readonly Role CatchClauseRole = new Role("CatchClause", CatchClause.Null);
- public static readonly TokenRole FinallyKeywordRole = new TokenRole ("finally");
+ public static readonly TokenRole FinallyKeywordRole = new TokenRole("finally");
public static readonly Role FinallyBlockRole = new Role("FinallyBlock", BlockStatement.Null);
-
+
public CSharpTokenNode TryToken {
- get { return GetChildByRole (TryKeywordRole); }
+ get { return GetChildByRole(TryKeywordRole); }
}
-
+
public BlockStatement TryBlock {
- get { return GetChildByRole (TryBlockRole); }
- set { SetChildByRole (TryBlockRole, value); }
+ get { return GetChildByRole(TryBlockRole); }
+ set { SetChildByRole(TryBlockRole, value); }
}
-
+
public AstNodeCollection CatchClauses {
- get { return GetChildrenByRole (CatchClauseRole); }
+ get { return GetChildrenByRole(CatchClauseRole); }
}
-
+
public CSharpTokenNode FinallyToken {
- get { return GetChildByRole (FinallyKeywordRole); }
+ get { return GetChildByRole(FinallyKeywordRole); }
}
-
+
public BlockStatement FinallyBlock {
- get { return GetChildByRole (FinallyBlockRole); }
- set { SetChildByRole (FinallyBlockRole, value); }
+ get { return GetChildByRole(FinallyBlockRole); }
+ set { SetChildByRole(FinallyBlockRole, value); }
}
-
- public override void AcceptVisitor (IAstVisitor visitor)
+
+ public override void AcceptVisitor(IAstVisitor visitor)
{
- visitor.VisitTryCatchStatement (this);
+ visitor.VisitTryCatchStatement(this);
}
-
- public override T AcceptVisitor (IAstVisitor visitor)
+
+ public override T AcceptVisitor(IAstVisitor visitor)
{
- return visitor.VisitTryCatchStatement (this);
+ return visitor.VisitTryCatchStatement(this);
}
-
- public override S AcceptVisitor (IAstVisitor visitor, T data)
+
+ public override S AcceptVisitor(IAstVisitor visitor, T data)
{
- return visitor.VisitTryCatchStatement (this, data);
+ return visitor.VisitTryCatchStatement(this, data);
}
-
+
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{
TryCatchStatement o = other as TryCatchStatement;
return o != null && this.TryBlock.DoMatch(o.TryBlock, match) && this.CatchClauses.DoMatch(o.CatchClauses, match) && this.FinallyBlock.DoMatch(o.FinallyBlock, match);
}
}
-
+
///
/// catch (Type VariableName) { Body }
///
public class CatchClause : AstNode
{
- public static readonly TokenRole CatchKeywordRole = new TokenRole ("catch");
- public static readonly TokenRole WhenKeywordRole = new TokenRole ("when");
+ public static readonly TokenRole CatchKeywordRole = new TokenRole("catch");
+ public static readonly TokenRole WhenKeywordRole = new TokenRole("when");
public static readonly Role ConditionRole = Roles.Condition;
+ public static readonly TokenRole CondLPar = new TokenRole("(");
+ public static readonly TokenRole CondRPar = new TokenRole(")");
#region Null
- public new static readonly CatchClause Null = new NullCatchClause ();
-
+ public new static readonly CatchClause Null = new NullCatchClause();
+
sealed class NullCatchClause : CatchClause
{
public override bool IsNull {
@@ -101,22 +103,22 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return true;
}
}
-
- public override void AcceptVisitor (IAstVisitor visitor)
+
+ public override void AcceptVisitor(IAstVisitor visitor)
{
visitor.VisitNullNode(this);
}
-
- public override T AcceptVisitor (IAstVisitor visitor)
+
+ public override T AcceptVisitor(IAstVisitor visitor)
{
return visitor.VisitNullNode(this);
}
-
- public override S AcceptVisitor (IAstVisitor visitor, T data)
+
+ public override S AcceptVisitor(IAstVisitor visitor, T data)
{
return visitor.VisitNullNode(this, data);
}
-
+
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{
return other == null || other.IsNull;
@@ -129,26 +131,26 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
return pattern != null ? new PatternPlaceholder(pattern) : null;
}
-
+
sealed class PatternPlaceholder : CatchClause, PatternMatching.INode
{
readonly PatternMatching.Pattern child;
-
+
public PatternPlaceholder(PatternMatching.Pattern child)
{
this.child = child;
}
-
+
public override NodeType NodeType {
get { return NodeType.Pattern; }
}
-
- public override void AcceptVisitor (IAstVisitor visitor)
+
+ public override void AcceptVisitor(IAstVisitor visitor)
{
visitor.VisitPatternPlaceholder(this, child);
}
-
- public override T AcceptVisitor (IAstVisitor visitor)
+
+ public override T AcceptVisitor(IAstVisitor visitor)
{
return visitor.VisitPatternPlaceholder(this, child);
}
@@ -157,90 +159,98 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
{
return visitor.VisitPatternPlaceholder(this, child, data);
}
-
+
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{
return child.DoMatch(other, match);
}
-
+
bool PatternMatching.INode.DoMatchCollection(Role role, PatternMatching.INode pos, PatternMatching.Match match, PatternMatching.BacktrackingInfo backtrackingInfo)
{
return child.DoMatchCollection(role, pos, match, backtrackingInfo);
}
}
#endregion
-
+
public override NodeType NodeType {
get {
return NodeType.Unknown;
}
}
-
+
public CSharpTokenNode CatchToken {
- get { return GetChildByRole (CatchKeywordRole); }
+ get { return GetChildByRole(CatchKeywordRole); }
}
-
+
public CSharpTokenNode LParToken {
- get { return GetChildByRole (Roles.LPar); }
+ get { return GetChildByRole(Roles.LPar); }
}
-
+
public AstType Type {
- get { return GetChildByRole (Roles.Type); }
- set { SetChildByRole (Roles.Type, value); }
+ get { return GetChildByRole(Roles.Type); }
+ set { SetChildByRole(Roles.Type, value); }
}
-
+
public string VariableName {
- get { return GetChildByRole (Roles.Identifier).Name; }
+ get { return GetChildByRole(Roles.Identifier).Name; }
set {
if (string.IsNullOrEmpty(value))
- SetChildByRole (Roles.Identifier, null);
+ SetChildByRole(Roles.Identifier, null);
else
- SetChildByRole (Roles.Identifier, Identifier.Create (value));
+ SetChildByRole(Roles.Identifier, Identifier.Create(value));
}
}
-
+
public Identifier VariableNameToken {
get {
- return GetChildByRole (Roles.Identifier);
+ return GetChildByRole(Roles.Identifier);
}
set {
SetChildByRole(Roles.Identifier, value);
}
}
-
+
public CSharpTokenNode RParToken {
- get { return GetChildByRole (Roles.RPar); }
+ get { return GetChildByRole(Roles.RPar); }
}
-
+
public CSharpTokenNode WhenToken {
- get { return GetChildByRole (WhenKeywordRole); }
+ get { return GetChildByRole(WhenKeywordRole); }
}
-
+
+ public CSharpTokenNode CondLParToken {
+ get { return GetChildByRole(CondLPar); }
+ }
+
public Expression Condition {
- get { return GetChildByRole(ConditionRole); }
+ get { return GetChildByRole(ConditionRole); }
set { SetChildByRole(ConditionRole, value); }
}
-
+
+ public CSharpTokenNode CondRParToken {
+ get { return GetChildByRole(CondRPar); }
+ }
+
public BlockStatement Body {
- get { return GetChildByRole (Roles.Body); }
- set { SetChildByRole (Roles.Body, value); }
+ get { return GetChildByRole(Roles.Body); }
+ set { SetChildByRole(Roles.Body, value); }
}
-
- public override void AcceptVisitor (IAstVisitor visitor)
+
+ public override void AcceptVisitor(IAstVisitor visitor)
{
- visitor.VisitCatchClause (this);
+ visitor.VisitCatchClause(this);
}
-
- public override T AcceptVisitor (IAstVisitor visitor)
+
+ public override T AcceptVisitor(IAstVisitor visitor)
{
- return visitor.VisitCatchClause (this);
+ return visitor.VisitCatchClause(this);
}
-
- public override S AcceptVisitor (IAstVisitor visitor, T data)
+
+ public override S AcceptVisitor(IAstVisitor visitor, T data)
{
- return visitor.VisitCatchClause (this, data);
+ return visitor.VisitCatchClause(this, data);
}
-
+
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
{
CatchClause o = other as CatchClause;
diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs
index f031778fd..e50eb7134 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs
@@ -125,6 +125,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
invocationExpression.ReplaceWith(new ObjectCreateExpression(context.TypeSystemAstBuilder.ConvertType(method.TypeArguments.First())));
}
break;
+ case "System.Runtime.CompilerServices.RuntimeHelpers.GetSubArray":
+ if (arguments.Length == 2 && context.Settings.Ranges) {
+ var slicing = new IndexerExpression(arguments[0].Detach(), arguments[1].Detach());
+ slicing.CopyAnnotationsFrom(invocationExpression);
+ invocationExpression.ReplaceWith(slicing);
+ }
+ break;
}
BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(method.Name);
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index 0a0ebeae5..c673b11ba 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -112,12 +112,13 @@ namespace ICSharpCode.Decompiler
asyncUsingAndForEachStatement = false;
asyncEnumerator = false;
staticLocalFunctions = false;
+ ranges = false;
}
}
public CSharp.LanguageVersion GetMinimumRequiredVersion()
{
- if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions)
+ if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions || ranges)
return CSharp.LanguageVersion.CSharp8_0;
if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers || patternBasedFixedStatement)
return CSharp.LanguageVersion.CSharp7_3;
@@ -1100,6 +1101,23 @@ namespace ICSharpCode.Decompiler
}
}
+ bool ranges = true;
+
+ ///
+ /// Gets/Sets whether C# 8.0 static local functions should be transformed.
+ ///
+ [Category("C# 8.0 / VS 2019")]
+ [Description("DecompilerSettings.Ranges")]
+ public bool Ranges {
+ get { return ranges; }
+ set {
+ if (ranges != value) {
+ ranges = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
bool nullableReferenceTypes = true;
///
diff --git a/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs b/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs
index 77327f9aa..da34d08a7 100644
--- a/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs
+++ b/ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs
@@ -302,6 +302,8 @@ namespace ICSharpCode.Decompiler.Disassembler
if (spaceAfter) {
output.Write(' ');
}
+ } else if (spaceBefore && spaceAfter) {
+ output.Write(' ');
}
}
@@ -1112,42 +1114,11 @@ namespace ICSharpCode.Decompiler.Disassembler
{ FieldAttributes.NotSerialized, "notserialized" },
};
- public void DisassembleField(PEFile module, FieldDefinitionHandle field)
+ public void DisassembleField(PEFile module, FieldDefinitionHandle handle)
{
var metadata = module.Metadata;
- var fieldDefinition = metadata.GetFieldDefinition(field);
- output.WriteReference(module, field, ".field ", isDefinition: true);
- int offset = fieldDefinition.GetOffset();
- if (offset > -1) {
- output.Write("[" + offset + "] ");
- }
- WriteEnum(fieldDefinition.Attributes & FieldAttributes.FieldAccessMask, fieldVisibility);
- const FieldAttributes hasXAttributes = FieldAttributes.HasDefault | FieldAttributes.HasFieldMarshal | FieldAttributes.HasFieldRVA;
- WriteFlags(fieldDefinition.Attributes & ~(FieldAttributes.FieldAccessMask | hasXAttributes), fieldAttributes);
-
- var signature = fieldDefinition.DecodeSignature(new DisassemblerSignatureTypeProvider(module, output), new GenericContext(fieldDefinition.GetDeclaringType(), module));
-
- var marshallingDescriptor = fieldDefinition.GetMarshallingDescriptor();
- if (!marshallingDescriptor.IsNil) {
- WriteMarshalInfo(metadata.GetBlobReader(marshallingDescriptor));
- }
-
- signature(ILNameSyntax.Signature);
- output.Write(' ');
- var fieldName = metadata.GetString(fieldDefinition.Name);
- output.Write(DisassemblerHelpers.Escape(fieldName));
- char sectionPrefix = 'D';
- if (fieldDefinition.HasFlag(FieldAttributes.HasFieldRVA)) {
- int rva = fieldDefinition.GetRelativeVirtualAddress();
- sectionPrefix = GetRVASectionPrefix(module.Reader.PEHeaders, rva);
- output.Write(" at {1}_{0:X8}", rva, sectionPrefix);
- }
-
- var defaultValue = fieldDefinition.GetDefaultValue();
- if (!defaultValue.IsNil) {
- output.Write(" = ");
- WriteConstant(metadata, metadata.GetConstant(defaultValue));
- }
+ var fieldDefinition = metadata.GetFieldDefinition(handle);
+ char sectionPrefix = DisassembleFieldHeaderInternal(module, handle, metadata, fieldDefinition);
output.WriteLine();
var attributes = fieldDefinition.GetCustomAttributes();
if (attributes.Count > 0) {
@@ -1187,6 +1158,53 @@ namespace ICSharpCode.Decompiler.Disassembler
}
}
+ public void DisassembleFieldHeader(PEFile module, FieldDefinitionHandle handle)
+ {
+ var metadata = module.Metadata;
+ var fieldDefinition = metadata.GetFieldDefinition(handle);
+ DisassembleFieldHeaderInternal(module, handle, metadata, fieldDefinition);
+ }
+
+ private char DisassembleFieldHeaderInternal(PEFile module, FieldDefinitionHandle handle, MetadataReader metadata, FieldDefinition fieldDefinition)
+ {
+ output.WriteReference(module, handle, ".field", isDefinition: true);
+ WriteMetadataToken(output, module, handle, MetadataTokens.GetToken(handle),
+ spaceAfter: true, spaceBefore: true, ShowMetadataTokens, ShowMetadataTokensInBase10);
+ int offset = fieldDefinition.GetOffset();
+ if (offset > -1) {
+ output.Write("[" + offset + "] ");
+ }
+ WriteEnum(fieldDefinition.Attributes & FieldAttributes.FieldAccessMask, fieldVisibility);
+ const FieldAttributes hasXAttributes = FieldAttributes.HasDefault | FieldAttributes.HasFieldMarshal | FieldAttributes.HasFieldRVA;
+ WriteFlags(fieldDefinition.Attributes & ~(FieldAttributes.FieldAccessMask | hasXAttributes), fieldAttributes);
+
+ var signature = fieldDefinition.DecodeSignature(new DisassemblerSignatureTypeProvider(module, output), new GenericContext(fieldDefinition.GetDeclaringType(), module));
+
+ var marshallingDescriptor = fieldDefinition.GetMarshallingDescriptor();
+ if (!marshallingDescriptor.IsNil) {
+ WriteMarshalInfo(metadata.GetBlobReader(marshallingDescriptor));
+ }
+
+ signature(ILNameSyntax.Signature);
+ output.Write(' ');
+ var fieldName = metadata.GetString(fieldDefinition.Name);
+ output.Write(DisassemblerHelpers.Escape(fieldName));
+ char sectionPrefix = 'D';
+ if (fieldDefinition.HasFlag(FieldAttributes.HasFieldRVA)) {
+ int rva = fieldDefinition.GetRelativeVirtualAddress();
+ sectionPrefix = GetRVASectionPrefix(module.Reader.PEHeaders, rva);
+ output.Write(" at {1}_{0:X8}", rva, sectionPrefix);
+ }
+
+ var defaultValue = fieldDefinition.GetDefaultValue();
+ if (!defaultValue.IsNil) {
+ output.Write(" = ");
+ WriteConstant(metadata, metadata.GetConstant(defaultValue));
+ }
+
+ return sectionPrefix;
+ }
+
char GetRVASectionPrefix(System.Reflection.PortableExecutable.PEHeaders headers, int rva)
{
int sectionIndex = headers.GetContainingSectionIndex(rva);
@@ -1215,8 +1233,30 @@ namespace ICSharpCode.Decompiler.Disassembler
{
var metadata = module.Metadata;
var propertyDefinition = metadata.GetPropertyDefinition(property);
- output.WriteReference(module, property, ".property", isDefinition: true);
- output.Write(" ");
+ PropertyAccessors accessors = DisassemblePropertyHeaderInternal(module, property, metadata, propertyDefinition);
+
+ OpenBlock(false);
+ WriteAttributes(module, propertyDefinition.GetCustomAttributes());
+ WriteNestedMethod(".get", module, accessors.Getter);
+ WriteNestedMethod(".set", module, accessors.Setter);
+ foreach (var method in accessors.Others) {
+ WriteNestedMethod(".other", module, method);
+ }
+ CloseBlock();
+ }
+
+ public void DisassemblePropertyHeader(PEFile module, PropertyDefinitionHandle property)
+ {
+ var metadata = module.Metadata;
+ var propertyDefinition = metadata.GetPropertyDefinition(property);
+ DisassemblePropertyHeaderInternal(module, property, metadata, propertyDefinition);
+ }
+
+ private PropertyAccessors DisassemblePropertyHeaderInternal(PEFile module, PropertyDefinitionHandle handle, MetadataReader metadata, PropertyDefinition propertyDefinition)
+ {
+ output.WriteReference(module, handle, ".property", isDefinition: true);
+ WriteMetadataToken(output, module, handle, MetadataTokens.GetToken(handle),
+ spaceAfter: true, spaceBefore: true, ShowMetadataTokens, ShowMetadataTokensInBase10);
WriteFlags(propertyDefinition.Attributes, propertyAttributes);
var accessors = propertyDefinition.GetAccessors();
var declaringType = metadata.GetMethodDefinition(accessors.GetAny()).GetDeclaringType();
@@ -1239,15 +1279,7 @@ namespace ICSharpCode.Decompiler.Disassembler
output.Unindent();
}
output.Write(')');
-
- OpenBlock(false);
- WriteAttributes(module, propertyDefinition.GetCustomAttributes());
- WriteNestedMethod(".get", module, accessors.Getter);
- WriteNestedMethod(".set", module, accessors.Setter);
- foreach (var method in accessors.Others) {
- WriteNestedMethod(".other", module, method);
- }
- CloseBlock();
+ return accessors;
}
void WriteNestedMethod(string keyword, PEFile module, MethodDefinitionHandle method)
@@ -1272,6 +1304,27 @@ namespace ICSharpCode.Decompiler.Disassembler
{
var eventDefinition = module.Metadata.GetEventDefinition(handle);
var accessors = eventDefinition.GetAccessors();
+ DisassembleEventHeaderInternal(module, handle, eventDefinition, accessors);
+ OpenBlock(false);
+ WriteAttributes(module, eventDefinition.GetCustomAttributes());
+ WriteNestedMethod(".addon", module, accessors.Adder);
+ WriteNestedMethod(".removeon", module, accessors.Remover);
+ WriteNestedMethod(".fire", module, accessors.Raiser);
+ foreach (var method in accessors.Others) {
+ WriteNestedMethod(".other", module, method);
+ }
+ CloseBlock();
+ }
+
+ public void DisassembleEventHeader(PEFile module, EventDefinitionHandle handle)
+ {
+ var eventDefinition = module.Metadata.GetEventDefinition(handle);
+ var accessors = eventDefinition.GetAccessors();
+ DisassembleEventHeaderInternal(module, handle, eventDefinition, accessors);
+ }
+
+ private void DisassembleEventHeaderInternal(PEFile module, EventDefinitionHandle handle, EventDefinition eventDefinition, EventAccessors accessors)
+ {
TypeDefinitionHandle declaringType;
if (!accessors.Adder.IsNil) {
declaringType = module.Metadata.GetMethodDefinition(accessors.Adder).GetDeclaringType();
@@ -1281,7 +1334,8 @@ namespace ICSharpCode.Decompiler.Disassembler
declaringType = module.Metadata.GetMethodDefinition(accessors.Raiser).GetDeclaringType();
}
output.WriteReference(module, handle, ".event", isDefinition: true);
- output.Write(" ");
+ WriteMetadataToken(output, module, handle, MetadataTokens.GetToken(handle),
+ spaceAfter: true, spaceBefore: true, ShowMetadataTokens, ShowMetadataTokensInBase10);
WriteFlags(eventDefinition.Attributes, eventAttributes);
var provider = new DisassemblerSignatureTypeProvider(module, output);
Action signature;
@@ -1302,15 +1356,6 @@ namespace ICSharpCode.Decompiler.Disassembler
signature(ILNameSyntax.TypeName);
output.Write(' ');
output.Write(DisassemblerHelpers.Escape(module.Metadata.GetString(eventDefinition.Name)));
- OpenBlock(false);
- WriteAttributes(module, eventDefinition.GetCustomAttributes());
- WriteNestedMethod(".addon", module, accessors.Adder);
- WriteNestedMethod(".removeon", module, accessors.Remover);
- WriteNestedMethod(".fire", module, accessors.Raiser);
- foreach (var method in accessors.Others) {
- WriteNestedMethod(".other", module, method);
- }
- CloseBlock();
}
#endregion
@@ -1352,30 +1397,9 @@ namespace ICSharpCode.Decompiler.Disassembler
public void DisassembleType(PEFile module, TypeDefinitionHandle type)
{
var typeDefinition = module.Metadata.GetTypeDefinition(type);
- output.WriteReference(module, type, ".class", isDefinition: true);
- output.Write(" ");
- if ((typeDefinition.Attributes & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Interface)
- output.Write("interface ");
- WriteEnum(typeDefinition.Attributes & TypeAttributes.VisibilityMask, typeVisibility);
- WriteEnum(typeDefinition.Attributes & TypeAttributes.LayoutMask, typeLayout);
- WriteEnum(typeDefinition.Attributes & TypeAttributes.StringFormatMask, typeStringFormat);
- const TypeAttributes masks = TypeAttributes.ClassSemanticsMask | TypeAttributes.VisibilityMask | TypeAttributes.LayoutMask | TypeAttributes.StringFormatMask;
- WriteFlags(typeDefinition.Attributes & ~masks, typeAttributes);
-
- output.Write(typeDefinition.GetDeclaringType().IsNil ? typeDefinition.GetFullTypeName(module.Metadata).ToILNameString() : DisassemblerHelpers.Escape(module.Metadata.GetString(typeDefinition.Name)));
GenericContext genericContext = new GenericContext(type, module);
- WriteTypeParameters(output, module, genericContext, typeDefinition.GetGenericParameters());
- output.MarkFoldStart(defaultCollapsed: !ExpandMemberDefinitions && isInType);
- output.WriteLine();
- EntityHandle baseType = typeDefinition.GetBaseTypeOrNil();
- if (!baseType.IsNil) {
- output.Indent();
- output.Write("extends ");
- baseType.WriteTo(module, output, genericContext, ILNameSyntax.TypeName);
- output.WriteLine();
- output.Unindent();
- }
+ DisassembleTypeHeaderInternal(module, type, typeDefinition, genericContext);
var interfaces = typeDefinition.GetInterfaceImplementations();
if (interfaces.Count > 0) {
@@ -1463,6 +1487,40 @@ namespace ICSharpCode.Decompiler.Disassembler
isInType = oldIsInType;
}
+ public void DisassembleTypeHeader(PEFile module, TypeDefinitionHandle type)
+ {
+ var typeDefinition = module.Metadata.GetTypeDefinition(type);
+ GenericContext genericContext = new GenericContext(type, module);
+ DisassembleTypeHeaderInternal(module, type, typeDefinition, genericContext);
+ }
+
+ private void DisassembleTypeHeaderInternal(PEFile module, TypeDefinitionHandle handle, TypeDefinition typeDefinition, GenericContext genericContext)
+ {
+ output.WriteReference(module, handle, ".class", isDefinition: true);
+ WriteMetadataToken(output, module, handle, MetadataTokens.GetToken(handle),
+ spaceAfter: true, spaceBefore: true, ShowMetadataTokens, ShowMetadataTokensInBase10); if ((typeDefinition.Attributes & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Interface)
+ output.Write("interface ");
+ WriteEnum(typeDefinition.Attributes & TypeAttributes.VisibilityMask, typeVisibility);
+ WriteEnum(typeDefinition.Attributes & TypeAttributes.LayoutMask, typeLayout);
+ WriteEnum(typeDefinition.Attributes & TypeAttributes.StringFormatMask, typeStringFormat);
+ const TypeAttributes masks = TypeAttributes.ClassSemanticsMask | TypeAttributes.VisibilityMask | TypeAttributes.LayoutMask | TypeAttributes.StringFormatMask;
+ WriteFlags(typeDefinition.Attributes & ~masks, typeAttributes);
+
+ output.Write(typeDefinition.GetDeclaringType().IsNil ? typeDefinition.GetFullTypeName(module.Metadata).ToILNameString() : DisassemblerHelpers.Escape(module.Metadata.GetString(typeDefinition.Name)));
+ WriteTypeParameters(output, module, genericContext, typeDefinition.GetGenericParameters());
+ output.MarkFoldStart(defaultCollapsed: !ExpandMemberDefinitions && isInType);
+ output.WriteLine();
+
+ EntityHandle baseType = typeDefinition.GetBaseTypeOrNil();
+ if (!baseType.IsNil) {
+ output.Indent();
+ output.Write("extends ");
+ baseType.WriteTo(module, output, genericContext, ILNameSyntax.TypeName);
+ output.WriteLine();
+ output.Unindent();
+ }
+ }
+
void WriteTypeParameters(ITextOutput output, PEFile module, GenericContext context, GenericParameterHandleCollection p)
{
if (p.Count > 0) {
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index d21930dbb..3d735ec42 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -64,6 +64,7 @@
+
@@ -384,6 +385,7 @@
+
diff --git a/ICSharpCode.Decompiler/IL/InstructionOutputExtensions.cs b/ICSharpCode.Decompiler/IL/InstructionOutputExtensions.cs
index 6ae5f8e62..b49739bda 100644
--- a/ICSharpCode.Decompiler/IL/InstructionOutputExtensions.cs
+++ b/ICSharpCode.Decompiler/IL/InstructionOutputExtensions.cs
@@ -44,7 +44,7 @@ namespace ICSharpCode.Decompiler.IL
output.Write(primitiveType.ToString().ToLowerInvariant());
}
- public static void WriteTo(this IType type, ITextOutput output, ILNameSyntax nameSyntax = ILNameSyntax.ShortTypeName)
+ public static void WriteTo(this IType type, ITextOutput output)
{
output.WriteReference(type, type.ReflectionName);
}
diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs
index 1db516ca1..7fd43ab93 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions.cs
@@ -4713,6 +4713,7 @@ namespace ICSharpCode.Decompiler.IL
clone.Indices.AddRange(this.Indices.Select(arg => (ILInstruction)arg.Clone()));
return clone;
}
+ public bool WithSystemIndex;
public bool DelayExceptions; // NullReferenceException/IndexOutOfBoundsException only occurs when the reference is dereferenced
public override StackType ResultType { get { return StackType.Ref; } }
/// Gets whether the 'readonly' prefix was applied to this instruction.
@@ -4729,6 +4730,8 @@ namespace ICSharpCode.Decompiler.IL
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
WriteILRange(output, options);
+ if (WithSystemIndex)
+ output.Write("withsystemindex.");
if (DelayExceptions)
output.Write("delayex.");
if (IsReadOnly)
@@ -4759,7 +4762,7 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as LdElema;
- return o != null && type.Equals(o.type) && this.array.PerformMatch(o.array, ref match) && Patterns.ListMatch.DoMatch(this.Indices, o.Indices, ref match) && DelayExceptions == o.DelayExceptions && IsReadOnly == o.IsReadOnly;
+ return o != null && type.Equals(o.type) && this.array.PerformMatch(o.array, ref match) && Patterns.ListMatch.DoMatch(this.Indices, o.Indices, ref match) && this.WithSystemIndex == o.WithSystemIndex && DelayExceptions == o.DelayExceptions && IsReadOnly == o.IsReadOnly;
}
}
}
diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt
index e18ce8d8e..69c2640eb 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.tt
+++ b/ICSharpCode.Decompiler/IL/Instructions.tt
@@ -278,6 +278,7 @@
CustomClassName("LdLen"), CustomArguments(("array", new[] { "O" })), CustomConstructor, CustomWriteTo, MayThrow),
new OpCode("ldelema", "Load address of array element.",
CustomClassName("LdElema"), HasTypeOperand, CustomChildren(new [] { new ArgumentInfo("array"), new ArgumentInfo("indices") { IsCollection = true } }, true),
+ BoolFlag("WithSystemIndex"),
MayThrowIfNotDelayed, ResultType("Ref"), SupportsReadonlyPrefix),
new OpCode("get.pinnable.reference", "Retrieves a pinnable reference for the input object." + Environment.NewLine
+ "The input must be an object reference (O)." + Environment.NewLine
@@ -1105,6 +1106,17 @@ protected override void Disconnected()
opCode.WriteOperand.Add("member.WriteTo(output);");
};
+ // Adds a member of type bool to the instruction.
+ static Action BoolFlag(string flagName)
+ {
+ return opCode => {
+ opCode.PerformMatchConditions.Add($"this.{flagName} == o.{flagName}");
+ opCode.Members.Add($"public bool {flagName};");
+ opCode.GenerateWriteTo = true;
+ opCode.WriteOpCodePrefix.Add($"if ({flagName}){Environment.NewLine}\toutput.Write(\"{flagName.ToLowerInvariant()}.\");");
+ };
+ }
+
// LoadConstant trait: the instruction loads a compile-time constant. Implies NoArguments.
static Action LoadConstant(string operandType)
{
diff --git a/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
index fec533c9e..72033be1a 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
@@ -135,7 +135,7 @@ namespace ICSharpCode.Decompiler.IL
WriteILRange(output, options);
if (ConstrainedTo != null) {
output.Write("constrained[");
- ConstrainedTo.WriteTo(output, ILNameSyntax.ShortTypeName);
+ ConstrainedTo.WriteTo(output);
output.Write("].");
}
if (IsTail)
diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
index c35c701ee..b7a96a8df 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
@@ -214,24 +214,29 @@ namespace ICSharpCode.Decompiler.IL
public void AddILRange(Interval newRange)
{
- if (this.ILRange.IsEmpty) {
- this.ILRange = newRange;
- return;
+ this.ILRange = CombineILRange(this.ILRange, newRange);
+ }
+
+ protected static Interval CombineILRange(Interval oldRange, Interval newRange)
+ {
+ if (oldRange.IsEmpty) {
+ return newRange;
}
if (newRange.IsEmpty) {
- return;
+ return oldRange;
}
- if (newRange.Start <= this.StartILOffset) {
- if (newRange.End < this.StartILOffset) {
- this.ILRange = newRange; // use the earlier range
+ if (newRange.Start <= oldRange.Start) {
+ if (newRange.End < oldRange.Start) {
+ return newRange; // use the earlier range
} else {
// join overlapping ranges
- this.ILRange = new Interval(newRange.Start, Math.Max(newRange.End, this.ILRange.End));
+ return new Interval(newRange.Start, Math.Max(newRange.End, oldRange.End));
}
- } else if (newRange.Start <= this.ILRange.End) {
+ } else if (newRange.Start <= oldRange.End) {
// join overlapping ranges
- this.ILRange = new Interval(this.StartILOffset, Math.Max(newRange.End, this.ILRange.End));
+ return new Interval(oldRange.Start, Math.Max(newRange.End, oldRange.End));
}
+ return oldRange;
}
public void AddILRange(ILInstruction sourceInstruction)
diff --git a/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs
index fe7753b2c..4e87bacc9 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs
@@ -19,6 +19,7 @@
using System;
using System.Diagnostics;
using System.Linq;
+using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL
{
@@ -175,6 +176,18 @@ namespace ICSharpCode.Decompiler.IL
output.Write(' ');
body.WriteTo(output, options);
}
+
+ ///
+ /// Gets the ILRange of the instructions at the start of the catch-block,
+ /// that take the exception object and store it in the exception variable slot.
+ /// Note: This range is empty, if Filter is not empty, i.e., ldloc 1.
+ ///
+ public Interval ExceptionSpecifierILRange { get; private set; }
+
+ public void AddExceptionSpecifierILRange(Interval newRange)
+ {
+ ExceptionSpecifierILRange = CombineILRange(ExceptionSpecifierILRange, newRange);
+ }
}
partial class TryFinally
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
index 061ee7721..97c17d1a8 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
@@ -17,10 +17,12 @@
// DEALINGS IN THE SOFTWARE.
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms
{
@@ -206,6 +208,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
base.VisitLdElema(inst);
CleanUpArrayIndices(inst.Indices);
+ if (IndexRangeTransform.HandleLdElema(inst, context))
+ return;
}
protected internal override void VisitNewArr(NewArr inst)
@@ -692,7 +696,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
TransformCatchWhen(inst, filterContainer.EntryPoint);
}
if (inst.Body is BlockContainer catchContainer)
- TransformCatchVariable(inst, catchContainer.EntryPoint);
+ TransformCatchVariable(inst, catchContainer.EntryPoint, isCatchBlock: true);
}
///
@@ -709,7 +713,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// }
/// }
///
- void TransformCatchVariable(TryCatchHandler handler, Block entryPoint)
+ void TransformCatchVariable(TryCatchHandler handler, Block entryPoint, bool isCatchBlock)
{
if (!handler.Variable.IsSingleDefinition || handler.Variable.LoadCount != 1)
return; // handle.Variable already has non-trivial uses
@@ -720,6 +724,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (inlinedUnboxAny.Type.Equals(handler.Variable.Type)) {
context.Step("TransformCatchVariable - remove inlined UnboxAny", inlinedUnboxAny);
inlinedUnboxAny.ReplaceWith(inlinedUnboxAny.Argument);
+ foreach (var range in inlinedUnboxAny.ILRanges)
+ handler.AddExceptionSpecifierILRange(range);
}
}
return;
@@ -746,6 +752,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
exceptionVar.Kind = VariableKind.ExceptionLocal;
exceptionVar.Type = handler.Variable.Type;
handler.Variable = exceptionVar;
+ if (isCatchBlock) {
+ foreach (var offset in entryPoint.Instructions[0].Descendants.SelectMany(o => o.ILRanges))
+ handler.AddExceptionSpecifierILRange(offset);
+ }
entryPoint.Instructions.RemoveAt(0);
}
@@ -754,7 +764,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
///
void TransformCatchWhen(TryCatchHandler handler, Block entryPoint)
{
- TransformCatchVariable(handler, entryPoint);
+ TransformCatchVariable(handler, entryPoint, isCatchBlock: false);
if (entryPoint.Instructions.Count == 1 && entryPoint.Instructions[0].MatchLeave(out _, out var condition)) {
context.Step("TransformCatchWhen", entryPoint.Instructions[0]);
handler.Filter = condition;
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
index 95fd133ba..ca20dbbb7 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
@@ -21,6 +21,7 @@ using System.Diagnostics;
using System.Linq;
using System.Reflection;
using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.TypeSystem.Implementation;
namespace ICSharpCode.Decompiler.IL.Transforms
{
@@ -469,6 +470,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (parent.SlotInfo == CompoundAssignmentInstruction.TargetSlot) {
return true;
}
+ if (((CallInstruction)parent).Method is SyntheticRangeIndexAccessor) {
+ return true;
+ }
+ break;
+ case OpCode.LdElema:
+ if (((LdElema)parent).WithSystemIndex) {
+ return true;
+ }
break;
}
// decide based on the top-level target instruction into which we are inlining:
diff --git a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs
new file mode 100644
index 000000000..2140c5416
--- /dev/null
+++ b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs
@@ -0,0 +1,596 @@
+// 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.Diagnostics;
+using System.Linq;
+using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.Decompiler.TypeSystem.Implementation;
+
+namespace ICSharpCode.Decompiler.IL.Transforms
+{
+ ///
+ /// Transform for the C# 8 System.Index / System.Range feature
+ ///
+ class IndexRangeTransform : IStatementTransform
+ {
+ ///
+ /// Called by expression transforms.
+ /// Handles the `array[System.Index]` cases.
+ ///
+ public static bool HandleLdElema(LdElema ldelema, ILTransformContext context)
+ {
+ if (!context.Settings.Ranges)
+ return false;
+ if (!ldelema.Array.MatchLdLoc(out ILVariable array))
+ return false;
+ if (ldelema.Indices.Count != 1)
+ return false; // the index/range feature doesn't support multi-dimensional arrays
+ var index = ldelema.Indices[0];
+ if (index is CallInstruction call && call.Method.Name == "GetOffset" && call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index)) {
+ // ldelema T(ldloc array, call GetOffset(..., ldlen.i4(ldloc array)))
+ // -> withsystemindex.ldelema T(ldloc array, ...)
+ if (call.Arguments.Count != 2)
+ return false;
+ if (!(call.Arguments[1].MatchLdLen(StackType.I4, out var arrayLoad) && arrayLoad.MatchLdLoc(array)))
+ return false;
+ context.Step("ldelema with System.Index", ldelema);
+ foreach (var node in call.Arguments[1].Descendants)
+ ldelema.AddILRange(node);
+ ldelema.AddILRange(call);
+ ldelema.WithSystemIndex = true;
+ // The method call had a `ref System.Index` argument for the this pointer, but we want a `System.Index` by-value.
+ ldelema.Indices[0] = new LdObj(call.Arguments[0], call.Method.DeclaringType);
+ return true;
+ } else if (index is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub && !bni.IsLifted && !bni.CheckForOverflow) {
+ // ldelema T(ldloc array, binary.sub.i4(ldlen.i4(ldloc array), ...))
+ // -> 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 indexMethods = new IndexMethods(context.TypeSystem);
+ if (!indexMethods.IsValid)
+ 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] = MakeIndex(IndexKind.FromEnd, bni.Right, indexMethods);
+ return true;
+ }
+
+ return false;
+ }
+
+ class IndexMethods
+ {
+ public readonly IMethod IndexCtor;
+ public readonly IMethod IndexImplicitConv;
+ public readonly IMethod RangeCtor;
+ public IType IndexType => IndexCtor?.DeclaringType;
+ public IType RangeType => RangeCtor?.DeclaringType;
+ public bool IsValid => IndexCtor != null && IndexImplicitConv != null && RangeCtor != null;
+
+ public readonly IMethod RangeStartAt;
+ public readonly IMethod RangeEndAt;
+ public readonly IMethod RangeGetAll;
+
+ 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.Count == 1 && 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;
+ }
+ }
+ foreach (var m in rangeType.GetMethods(m => m.Parameters.Count == 1)) {
+ if (m.Parameters.Count == 1 && m.Parameters[0].Type.IsKnownType(KnownTypeCode.Index)) {
+ if (m.Name == "StartAt")
+ this.RangeStartAt = m;
+ else if (m.Name == "EndAt")
+ this.RangeEndAt = m;
+ }
+ }
+ foreach (var p in rangeType.GetProperties(p => p.IsStatic && p.Name == "All")) {
+ this.RangeGetAll = p.Getter;
+ }
+ }
+ }
+
+ void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
+ {
+ 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, 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;
+ }
+ 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 (!(startOffsetVar.LoadInstructions.Single().Parent is CallInstruction call))
+ return;
+ if (call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter && call.Arguments.Count == 2) {
+ if (call.Method.AccessorOwner?.SymbolKind != SymbolKind.Indexer)
+ return;
+ if (call.Method.Parameters.Count != 1)
+ return;
+ } else if (call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Setter && call.Arguments.Count == 3) {
+ if (call.Method.AccessorOwner?.SymbolKind != SymbolKind.Indexer)
+ return;
+ if (call.Method.Parameters.Count != 2)
+ return;
+ } else if (IsSlicingMethod(call.Method)) {
+ TransformSlicing(sliceLengthWasMisdetectedAsStartOffset: true);
+ return;
+ } else {
+ 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]))
+ return;
+ }
+ if (!call.Method.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32))
+ return;
+ if (!MatchContainerVar(call.Arguments[0], ref containerVar))
+ return;
+ if (!call.Arguments[1].MatchLdLoc(startOffsetVar))
+ return;
+ var specialMethods = new IndexMethods(context.TypeSystem);
+ if (!specialMethods.IsValid)
+ return;
+ if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, slicing: false))
+ return;
+
+ context.Step($"{call.Method.Name} indexed with {startIndexKind}", call);
+ 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]);
+ 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(bool sliceLengthWasMisdetectedAsStartOffset = false)
+ {
+ ILVariable sliceLengthVar;
+ ILInstruction sliceLengthVarInit;
+ if (sliceLengthWasMisdetectedAsStartOffset) {
+ // Special case: when slicing without a start point, the slice length calculation is mis-detected as the start offset,
+ // and since it only has a single use, we end in TransformIndexing(), which then calls TransformSlicing
+ // on this code path.
+ sliceLengthVar = startOffsetVar;
+ sliceLengthVarInit = ((StLoc)sliceLengthVar.StoreInstructions.Single()).Value;
+ startOffsetVar = null;
+ startIndexLoad = new LdcI4(0);
+ startIndexKind = IndexKind.TheStart;
+ } else {
+ // 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 sliceLengthVar, out 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 (!IsSlicingMethod(call.Method))
+ return;
+ if (call.Arguments.Count != 3)
+ return;
+ if (!MatchContainerVar(call.Arguments[0], ref containerVar))
+ return;
+ if (startOffsetVar == null) {
+ Debug.Assert(startIndexKind == IndexKind.TheStart);
+ if (!call.Arguments[1].MatchLdcI4(0))
+ return;
+ } else {
+ 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.IsValid)
+ 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 if (startIndexKind == IndexKind.TheStart && endIndexKind == IndexKind.TheEnd && specialMethods.RangeGetAll != null) {
+ newCall.Arguments.Add(new Call(specialMethods.RangeGetAll));
+ } else if (startIndexKind == IndexKind.TheStart && specialMethods.RangeEndAt != null) {
+ var rangeCtorCall = new Call(specialMethods.RangeEndAt);
+ rangeCtorCall.Arguments.Add(MakeIndex(endIndexKind, endIndexLoad, specialMethods));
+ newCall.Arguments.Add(rangeCtorCall);
+ } else if (endIndexKind == IndexKind.TheEnd && specialMethods.RangeStartAt != null) {
+ var rangeCtorCall = new Call(specialMethods.RangeStartAt);
+ rangeCtorCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods));
+ newCall.Arguments.Add(rangeCtorCall);
+ } 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.AddILRange(call);
+ for (int i = startPos; i < pos; i++) {
+ newCall.AddILRange(block.Instructions[i]);
+ }
+ call.ReplaceWith(newCall);
+ block.Instructions.RemoveRange(startPos, pos - startPos);
+ }
+ }
+
+ static bool IsSlicingMethod(IMethod method)
+ {
+ if (method.IsExtensionMethod)
+ return false;
+ if (method.Parameters.Count != 2)
+ return false;
+ if (!method.Parameters.All(p => p.Type.IsKnownType(KnownTypeCode.Int32)))
+ return false;
+ return method.Name == "Slice"
+ || (method.Name == "Substring" && method.DeclaringType.IsKnownType(KnownTypeCode.String));
+ }
+
+ ///
+ /// Check that the number of uses of the containerLengthVar variable matches those expected in the pattern.
+ ///
+ private bool CheckContainerLengthVariableUseCount(ILVariable containerLengthVar, IndexKind startIndexKind, IndexKind endIndexKind = IndexKind.FromStart)
+ {
+ int expectedUses = 0;
+ if (startIndexKind != IndexKind.FromStart && startIndexKind != IndexKind.TheStart)
+ expectedUses += 1;
+ if (endIndexKind != IndexKind.FromStart && endIndexKind != IndexKind.TheStart)
+ expectedUses += 1;
+ if (containerLengthVar != null) {
+ return containerLengthVar.LoadCount == expectedUses;
+ } else {
+ return expectedUses <= 1; // can have one inline use
+ }
+ }
+
+ ///
+ /// Matches 'addressof System.Index(call get_Start/get_End(ldloca rangeVar))'
+ ///
+ 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 || indexKind == IndexKind.TheEnd) {
+ // 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 || indexKind == IndexKind.TheStart);
+ return new Call(specialMethods.IndexImplicitConv) { Arguments = { indexLoad } };
+ }
+ }
+
+ ///
+ /// Gets whether the C# compiler will call `container[int]` when using `container[Index]`.
+ ///
+ 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) {
+ var p = prop.Parameters[0];
+ if (p.Type.IsKnownType(KnownTypeCode.Int32)) {
+ 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") {
+ foundCountProperty = true;
+ }
+ }
+ if (slicing) {
+ return /* foundSlicingMethod && */ foundCountProperty && !foundRangeOverload;
+ } else {
+ return foundInt32Overload && foundCountProperty && !foundIndexOverload;
+ }
+ }
+
+ ///
+ /// Matches the instruction:
+ /// stloc containerLengthVar(call get_Length/get_Count(ldloc containerVar))
+ ///
+ static bool MatchContainerLengthStore(ILInstruction inst, out ILVariable lengthVar, ref ILVariable containerVar)
+ {
+ if (!inst.MatchStLoc(out lengthVar, out var init))
+ return false;
+ if (!(lengthVar.IsSingleDefinition && lengthVar.StackType == StackType.I4))
+ return false;
+ return MatchContainerLength(init, null, ref containerVar);
+ }
+
+ ///
+ /// If lengthVar is non-null, matches 'ldloc lengthVar'.
+ ///
+ /// Otherwise, matches the instruction:
+ /// call get_Length/get_Count(ldloc containerVar)
+ ///
+ static bool MatchContainerLength(ILInstruction init, ILVariable lengthVar, ref ILVariable containerVar)
+ {
+ if (lengthVar != null) {
+ Debug.Assert(containerVar != null);
+ return init.MatchLdLoc(lengthVar);
+ }
+ if (!(init is CallInstruction call))
+ return false;
+ if (call.ResultType != StackType.I4)
+ return false;
+ if (!(call.Method.IsAccessor && call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter))
+ return false;
+ if (!(call.Method.AccessorOwner is IProperty lengthProp))
+ return false;
+ if (lengthProp.Name == "Length") {
+ // OK, Length is preferred
+ } else if (lengthProp.Name == "Count") {
+ // Also works, but only if the type doesn't have "Length"
+ if (lengthProp.DeclaringType.GetProperties(p => p.Name == "Length").Any())
+ return false;
+ }
+ if (!lengthProp.ReturnType.IsKnownType(KnownTypeCode.Int32))
+ return false;
+ if (lengthProp.IsVirtual && call.OpCode != OpCode.CallVirt)
+ 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 inst.MatchLdLoc(containerVar) || inst.MatchLdLoca(containerVar);
+ } else {
+ return inst.MatchLdLoc(out containerVar) || inst.MatchLdLoca(out containerVar);
+ }
+ }
+
+ enum IndexKind
+ {
+ ///
+ /// indexLoad is an integer, from the start of the container
+ ///
+ FromStart,
+ ///
+ /// indexLoad is loading the address of a System.Index struct
+ ///
+ RefSystemIndex,
+ ///
+ /// indexLoad is an integer, from the end of the container
+ ///
+ FromEnd,
+ ///
+ /// Always equivalent to `0`, used for the start-index when slicing without a startpoint `a[..end]`
+ ///
+ TheStart,
+ ///
+ /// Always equivalent to `^0`, used for the end-index when slicing without an endpoint `a[start..]`
+ ///
+ TheEnd,
+ }
+
+ ///
+ /// Matches an instruction computing an offset:
+ /// call System.Index.GetOffset(indexLoad, ldloc containerLengthVar)
+ /// or
+ /// binary.sub.i4(ldloc containerLengthVar, indexLoad)
+ ///
+ /// Anything else not matching these patterns is interpreted as an `int` expression from the start of the container.
+ ///
+ static IndexKind MatchGetOffset(ILInstruction inst, out ILInstruction indexLoad,
+ ILVariable containerLengthVar, ref ILVariable containerVar)
+ {
+ indexLoad = inst;
+ if (MatchContainerLength(inst, containerLengthVar, ref containerVar)) {
+ indexLoad = new LdcI4(0);
+ return IndexKind.TheEnd;
+ } else if (inst is CallInstruction call) {
+ // call System.Index.GetOffset(indexLoad, ldloc containerLengthVar)
+ if (call.Method.Name != "GetOffset")
+ return IndexKind.FromStart;
+ if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index))
+ return IndexKind.FromStart;
+ if (call.Arguments.Count != 2)
+ return IndexKind.FromStart;
+ if (!MatchContainerLength(call.Arguments[1], containerLengthVar, ref containerVar))
+ return IndexKind.FromStart;
+ indexLoad = call.Arguments[0];
+ return IndexKind.RefSystemIndex;
+ } else if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub) {
+ if (bni.CheckForOverflow || bni.ResultType != StackType.I4 || bni.IsLifted)
+ return IndexKind.FromStart;
+ // binary.sub.i4(ldloc containerLengthVar, indexLoad)
+ if (!MatchContainerLength(bni.Left, containerLengthVar, ref containerVar))
+ return IndexKind.FromStart;
+ indexLoad = bni.Right;
+ return IndexKind.FromEnd;
+ } else {
+ return IndexKind.FromStart;
+ }
+ }
+
+ ///
+ /// Matches an instruction computing a slice length:
+ /// binary.sub.i4(call GetOffset(endIndexLoad, ldloc length), ldloc startOffset))
+ ///
+ 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 (startOffsetVar == null) {
+ // When slicing without explicit start point: `a[..endIndex]`
+ if (!bni.Right.MatchLdcI4(0))
+ return false;
+ } else {
+ if (!bni.Right.MatchLdLoc(startOffsetVar))
+ return false;
+ }
+ endIndexKind = MatchGetOffset(bni.Left, out endIndexLoad, containerLengthVar, ref containerVar);
+ return true;
+ } else if (startOffsetVar == null) {
+ // When slicing without explicit start point: `a[..endIndex]`, the compiler doesn't always emit the "- 0".
+ endIndexKind = MatchGetOffset(inst, out endIndexLoad, containerLengthVar, ref containerVar);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
index 37f8ffcd3..93d7f90f5 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
@@ -39,7 +39,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
readonly ILTransformContext context;
-
+
public NullPropagationTransform(ILTransformContext context)
{
this.context = context;
@@ -59,6 +59,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// nullable type, used by reference (comparison is 'call get_HasValue(ldloc(testedVar))')
///
NullableByReference,
+ ///
+ /// unconstrained generic type (see the pattern described in TransformNullPropagationOnUnconstrainedGenericExpression)
+ ///
+ UnconstrainedType,
}
///
@@ -142,18 +146,46 @@ namespace ICSharpCode.Decompiler.IL.Transforms
///
internal void RunStatements(Block block, int pos)
{
- var ifInst = block.Instructions[pos] as IfInstruction;
- if (ifInst == null || !ifInst.FalseInst.MatchNop())
- return;
- if (ifInst.Condition is Comp comp && comp.Kind == ComparisonKind.Inequality
- && comp.Left.MatchLdLoc(out var testedVar) && comp.Right.MatchLdNull()) {
- TryNullPropForVoidCall(testedVar, Mode.ReferenceType, ifInst.TrueInst as Block, ifInst);
- } else if (NullableLiftingTransform.MatchHasValueCall(ifInst.Condition, out ILInstruction arg)) {
- if (arg.MatchLdLoca(out testedVar)) {
- TryNullPropForVoidCall(testedVar, Mode.NullableByValue, ifInst.TrueInst as Block, ifInst);
- } else if (arg.MatchLdLoc(out testedVar)) {
- TryNullPropForVoidCall(testedVar, Mode.NullableByReference, ifInst.TrueInst as Block, ifInst);
+ if (block.Instructions[pos] is IfInstruction ifInst && ifInst.FalseInst.MatchNop()) {
+ if (ifInst.Condition is Comp comp && comp.Kind == ComparisonKind.Inequality
+ && comp.Left.MatchLdLoc(out var testedVar) && comp.Right.MatchLdNull()) {
+ TryNullPropForVoidCall(testedVar, Mode.ReferenceType, ifInst.TrueInst as Block, ifInst);
+ } else if (NullableLiftingTransform.MatchHasValueCall(ifInst.Condition, out ILInstruction arg)) {
+ if (arg.MatchLdLoca(out testedVar)) {
+ TryNullPropForVoidCall(testedVar, Mode.NullableByValue, ifInst.TrueInst as Block, ifInst);
+ } else if (arg.MatchLdLoc(out testedVar)) {
+ TryNullPropForVoidCall(testedVar, Mode.NullableByReference, ifInst.TrueInst as Block, ifInst);
+ }
+ }
+ }
+ if (TransformNullPropagationOnUnconstrainedGenericExpression(block, pos, out var testedVariable, out var nonNullInst, out var nullInst, out var endBlock)) {
+ var parentInstruction = nonNullInst.Parent;
+ var replacement = TryNullPropagation(testedVariable, nonNullInst, nullInst, Mode.UnconstrainedType);
+ if (replacement == null)
+ return;
+ context.Step("TransformNullPropagationOnUnconstrainedGenericExpression", block);
+ switch (parentInstruction) {
+ case StLoc stloc:
+ stloc.Value = replacement;
+ break;
+ case Leave leave:
+ leave.Value = replacement;
+ break;
+ default:
+ // if this ever happens, the pattern checked by TransformNullPropagationOnUnconstrainedGenericExpression
+ // has changed, but this part of the code was not properly adjusted.
+ throw new NotSupportedException();
+ }
+ // Remove the fallback conditions and blocks
+ block.Instructions.RemoveRange(pos + 1, 2);
+ // if the endBlock is only reachable through the current block,
+ // combine both blocks.
+ if (endBlock?.IncomingEdgeCount == 1) {
+ block.Instructions.AddRange(endBlock.Instructions);
+ block.Instructions.RemoveAt(pos + 2);
+ endBlock.Remove();
}
+ ILInlining.InlineIfPossible(block, pos, context);
}
}
@@ -261,6 +293,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case Mode.NullableByReference:
return NullableLiftingTransform.MatchGetValueOrDefault(inst, out ILInstruction arg)
&& arg.MatchLdLoc(testedVar);
+ case Mode.UnconstrainedType:
+ // unconstrained generic type (expect: ldloc(testedVar))
+ return inst.MatchLdLoc(testedVar);
default:
throw new ArgumentOutOfRangeException(nameof(mode));
}
@@ -288,6 +323,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
ILInstruction replacement;
switch (mode) {
case Mode.ReferenceType:
+ case Mode.UnconstrainedType:
// Wrap varLoad in nullable.unwrap:
replacement = new NullableUnwrap(varLoad.ResultType, varLoad, refInput: varLoad.ResultType == StackType.Ref);
break;
@@ -310,6 +346,147 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
oldParentChildren[oldChildIndex] = replacement;
}
+
+ // stloc target(targetInst)
+ // stloc defaultTemporary(default.value type)
+ // if (logic.not(comp.o(box `0(ldloc defaultTemporary) != ldnull))) Block fallbackBlock {
+ // stloc defaultTemporary(ldobj type(ldloc target))
+ // stloc target(ldloca defaultTemporary)
+ // if (comp.o(ldloc defaultTemporary == ldnull)) Block fallbackBlock2 {
+ // stloc resultTemporary(nullInst)
+ // br endBlock
+ // }
+ // }
+ // stloc resultTemporary(constrained[type].call_instruction(ldloc target, ...))
+ // br endBlock
+ // =>
+ // stloc resultTemporary(nullable.rewrap(constrained[type].call_instruction(nullable.unwrap(targetInst), ...)))
+ //
+ // -or-
+ //
+ // stloc target(targetInst)
+ // stloc defaultTemporary(default.value type)
+ // if (logic.not(comp.o(box `0(ldloc defaultTemporary) != ldnull))) Block fallbackBlock {
+ // stloc defaultTemporary(ldobj type(ldloc target))
+ // stloc target(ldloca defaultTemporary)
+ // if (comp.o(ldloc defaultTemporary == ldnull)) Block fallbackBlock2 {
+ // leave(nullInst)
+ // }
+ // }
+ // leave (constrained[type].call_instruction(ldloc target, ...))
+ // =>
+ // leave (nullable.rewrap(constrained[type].call_instruction(nullable.unwrap(targetInst), ...)))
+ private bool TransformNullPropagationOnUnconstrainedGenericExpression(Block block, int pos,
+ out ILVariable target, out ILInstruction nonNullInst, out ILInstruction nullInst, out Block endBlock)
+ {
+ target = null;
+ nonNullInst = null;
+ nullInst = null;
+ endBlock = null;
+ if (pos + 3 >= block.Instructions.Count)
+ return false;
+ // stloc target(...)
+ if (!block.Instructions[pos].MatchStLoc(out target, out _))
+ return false;
+ if (!(target.Kind == VariableKind.StackSlot && target.LoadCount == 2 && target.StoreCount == 2))
+ return false;
+ // stloc defaultTemporary(default.value type)
+ if (!(block.Instructions[pos + 1].MatchStLoc(out var defaultTemporary, out var defaultExpression) && defaultExpression.MatchDefaultValue(out var type)))
+ return false;
+ // In the above pattern the defaultTemporary variable is used two times in stloc and ldloc instructions and once in a ldloca instruction
+ if (!(defaultTemporary.Kind == VariableKind.Local && defaultTemporary.LoadCount == 2 && defaultTemporary.StoreCount == 2 && defaultTemporary.AddressCount == 1))
+ return false;
+ // if (logic.not(comp.o(box `0(ldloc defaultTemporary) != ldnull))) Block fallbackBlock
+ if (!(block.Instructions[pos + 2].MatchIfInstruction(out var condition, out var fallbackBlock1) && condition.MatchCompEqualsNull(out var arg) && arg.MatchLdLoc(defaultTemporary)))
+ return false;
+ if (!MatchStLocResultTemporary(block, pos, type, target, defaultTemporary, fallbackBlock1, out nonNullInst, out nullInst, out endBlock)
+ && !MatchLeaveResult(block, pos, type, target, defaultTemporary, fallbackBlock1, out nonNullInst, out nullInst))
+ return false;
+ return true;
+ }
+
+ // stloc resultTemporary(constrained[type].call_instruction(ldloc target, ...))
+ // br endBlock
+ private bool MatchStLocResultTemporary(Block block, int pos, IType type, ILVariable target, ILVariable defaultTemporary, ILInstruction fallbackBlock, out ILInstruction nonNullInst, out ILInstruction nullInst, out Block endBlock)
+ {
+ endBlock = null;
+ nonNullInst = null;
+ nullInst = null;
+
+ if (pos + 4 >= block.Instructions.Count)
+ return false;
+ // stloc resultTemporary(constrained[type].call_instruction(ldloc target, ...))
+ if (!(block.Instructions[pos + 3].MatchStLoc(out var resultTemporary, out nonNullInst)))
+ return false;
+ // br endBlock
+ if (!(block.Instructions[pos + 4].MatchBranch(out endBlock)))
+ return false;
+ // Analyze Block fallbackBlock
+ if (!(fallbackBlock is Block b && IsFallbackBlock(b, type, target, defaultTemporary, resultTemporary, endBlock, out nullInst)))
+ return false;
+
+ return true;
+ }
+
+ private bool MatchLeaveResult(Block block, int pos, IType type, ILVariable target, ILVariable defaultTemporary, ILInstruction fallbackBlock, out ILInstruction nonNullInst, out ILInstruction nullInst)
+ {
+ nonNullInst = null;
+ nullInst = null;
+
+ // leave (constrained[type].call_instruction(ldloc target, ...))
+ if (!(block.Instructions[pos + 3] is Leave leave && leave.IsLeavingFunction))
+ return false;
+ nonNullInst = leave.Value;
+ // Analyze Block fallbackBlock
+ if (!(fallbackBlock is Block b && IsFallbackBlock(b, type, target, defaultTemporary, null, leave.TargetContainer, out nullInst)))
+ return false;
+ return true;
+ }
+
+ // Block fallbackBlock {
+ // stloc defaultTemporary(ldobj type(ldloc target))
+ // stloc target(ldloca defaultTemporary)
+ // if (comp.o(ldloc defaultTemporary == ldnull)) Block fallbackBlock {
+ // stloc resultTemporary(ldnull)
+ // br endBlock
+ // }
+ // }
+ private bool IsFallbackBlock(Block block, IType type, ILVariable target, ILVariable defaultTemporary, ILVariable resultTemporary, ILInstruction endBlockOrLeaveContainer, out ILInstruction nullInst)
+ {
+ nullInst = null;
+ if (!(block.Instructions.Count == 3))
+ return false;
+ // stloc defaultTemporary(ldobj type(ldloc target))
+ if (!(block.Instructions[0].MatchStLoc(defaultTemporary, out var value)))
+ return false;
+ if (!(value.MatchLdObj(out var inst, out var t) && type.Equals(t) && inst.MatchLdLoc(target)))
+ return false;
+ // stloc target(ldloca defaultTemporary)
+ if (!(block.Instructions[1].MatchStLoc(target, out var defaultAddress) && defaultAddress.MatchLdLoca(defaultTemporary)))
+ return false;
+ // if (comp.o(ldloc defaultTemporary == ldnull)) Block fallbackBlock
+ if (!(block.Instructions[2].MatchIfInstruction(out var condition, out var tmp) && condition.MatchCompEqualsNull(out var arg) && arg.MatchLdLoc(defaultTemporary)))
+ return false;
+ // Block fallbackBlock {
+ // stloc resultTemporary(nullInst)
+ // br endBlock
+ // }
+ var fallbackInst = Block.Unwrap(tmp);
+ if (fallbackInst is Block fallbackBlock && endBlockOrLeaveContainer is Block endBlock) {
+ if (!(fallbackBlock.Instructions.Count == 2))
+ return false;
+ if (!fallbackBlock.Instructions[0].MatchStLoc(resultTemporary, out nullInst))
+ return false;
+ if (!fallbackBlock.Instructions[1].MatchBranch(endBlock))
+ return false;
+ } else {
+ if (!(fallbackInst is Leave fallbackLeave && endBlockOrLeaveContainer is BlockContainer leaveContainer
+ && fallbackLeave.TargetContainer == leaveContainer && !fallbackLeave.Value.MatchNop()))
+ return false;
+ nullInst = fallbackLeave.Value;
+ }
+ return true;
+ }
}
class NullPropagationStatementTransform : IStatementTransform
diff --git a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
index 850f50fc1..5f7473fb1 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
@@ -114,7 +114,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
value = ldFlda.Target;
}
if (value.OpCode != OpCode.LdLoca) {
- // GroupStores.HandleLoad() only detects ref-locals when they are directly initialized with ldloca
+ // GroupStores only handles ref-locals correctly when they are supported by GetAddressLoadForRefLocalUse(),
+ // which only works for ldflda*(ldloca)
return AddressUse.Unknown;
}
foreach (var load in stloc.Variable.LoadInstructions) {
@@ -132,14 +133,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// Address is passed to method.
// We'll assume the method only uses the address locally,
// unless we can see an address being returned from the method:
- if (call is NewObj) {
- if (call.Method.DeclaringType.IsByRefLike) {
+ IType returnType = (call is NewObj) ? call.Method.DeclaringType : call.Method.ReturnType;
+ if (returnType.IsByRefLike) {
+ // If the address is returned from the method, it check whether it's consumed immediately.
+ // This can still be fine, as long as we also check the consumer's other arguments for 'stloc targetVar'.
+ if (DetermineAddressUse(call, targetVar) != AddressUse.Immediate)
return AddressUse.Unknown;
- }
- } else {
- if (call.Method.ReturnType.IsByRefLike) {
- return AddressUse.Unknown;
- }
}
foreach (var p in call.Method.Parameters) {
// catch "out Span" and similar
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs
new file mode 100644
index 000000000..9d79e1b79
--- /dev/null
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs
@@ -0,0 +1,137 @@
+// 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.Collections.Generic;
+using ICSharpCode.Decompiler.Util;
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Diagnostics;
+using System.Linq;
+
+namespace ICSharpCode.Decompiler.TypeSystem.Implementation
+{
+ ///
+ /// Synthetic method representing a compiler-generated indexer
+ /// with the signature 'get_Item(System.Index)' or 'get_Item(System.Range)'.
+ /// Can also be a setter.
+ /// Used for the "Implicit Index support"/"Implicit Range support" for the C# 8 ranges feature.
+ ///
+ class SyntheticRangeIndexAccessor : IMethod
+ {
+ ///
+ /// The underlying method: `get_Item(int)`, `set_Item(int, T)` or `Slice(int, int)`.
+ ///
+ readonly IMethod underlyingMethod;
+ readonly IType indexOrRangeType;
+ readonly IReadOnlyList parameters;
+ readonly bool slicing;
+
+ 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();
+ parameters.Add(new DefaultParameter(indexOrRangeType, ""));
+ 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;
+
+ IReadOnlyList IMethod.TypeParameters => EmptyList.Instance;
+ IReadOnlyList IMethod.TypeArguments => EmptyList.Instance;
+
+ bool IMethod.IsExtensionMethod => false;
+ bool IMethod.IsLocalFunction => false;
+ bool IMethod.IsConstructor => false;
+ bool IMethod.IsDestructor => false;
+ bool IMethod.IsOperator => false;
+ bool IMethod.HasBody => underlyingMethod.HasBody;
+ bool IMethod.IsAccessor => underlyingMethod.IsAccessor;
+ IMember IMethod.AccessorOwner => underlyingMethod.AccessorOwner;
+ MethodSemanticsAttributes IMethod.AccessorKind => underlyingMethod.AccessorKind;
+ IMethod IMethod.ReducedFrom => underlyingMethod.ReducedFrom;
+ IReadOnlyList IParameterizedMember.Parameters => parameters;
+ IMember IMember.MemberDefinition => underlyingMethod.MemberDefinition;
+ IType IMember.ReturnType => underlyingMethod.ReturnType;
+ IEnumerable IMember.ExplicitlyImplementedInterfaceMembers => EmptyList.Instance;
+ bool IMember.IsExplicitInterfaceImplementation => false;
+ bool IMember.IsVirtual => underlyingMethod.IsVirtual;
+ bool IMember.IsOverride => underlyingMethod.IsOverride;
+ bool IMember.IsOverridable => underlyingMethod.IsOverridable;
+ TypeParameterSubstitution IMember.Substitution => underlyingMethod.Substitution;
+ EntityHandle IEntity.MetadataToken => underlyingMethod.MetadataToken;
+ public string Name => underlyingMethod.Name;
+ IType IEntity.DeclaringType => underlyingMethod.DeclaringType;
+ ITypeDefinition IEntity.DeclaringTypeDefinition => underlyingMethod.DeclaringTypeDefinition;
+ IModule IEntity.ParentModule => underlyingMethod.ParentModule;
+ Accessibility IEntity.Accessibility => underlyingMethod.Accessibility;
+ bool IEntity.IsStatic => underlyingMethod.IsStatic;
+ bool IEntity.IsAbstract => underlyingMethod.IsAbstract;
+ bool IEntity.IsSealed => underlyingMethod.IsSealed;
+ SymbolKind ISymbol.SymbolKind => SymbolKind.Method;
+ ICompilation ICompilationProvider.Compilation => underlyingMethod.Compilation;
+ string INamedElement.FullName => underlyingMethod.FullName;
+ string INamedElement.ReflectionName => underlyingMethod.ReflectionName;
+ string INamedElement.Namespace => underlyingMethod.Namespace;
+
+ public override bool Equals(object obj)
+ {
+ return obj is SyntheticRangeIndexAccessor g
+ && this.underlyingMethod.Equals(g.underlyingMethod)
+ && this.indexOrRangeType.Equals(g.indexOrRangeType)
+ && this.slicing == g.slicing;
+ }
+
+ public override int GetHashCode()
+ {
+ return underlyingMethod.GetHashCode() ^ indexOrRangeType.GetHashCode();
+ }
+
+ bool IMember.Equals(IMember obj, TypeVisitor typeNormalization)
+ {
+ return obj is SyntheticRangeIndexAccessor g
+ && this.underlyingMethod.Equals(g.underlyingMethod, typeNormalization)
+ && this.indexOrRangeType.AcceptVisitor(typeNormalization).Equals(g.indexOrRangeType.AcceptVisitor(typeNormalization));
+ }
+
+ IEnumerable IEntity.GetAttributes() => underlyingMethod.GetAttributes();
+
+ IEnumerable IMethod.GetReturnTypeAttributes() => underlyingMethod.GetReturnTypeAttributes();
+
+ IMethod IMethod.Specialize(TypeParameterSubstitution substitution)
+ {
+ return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType, slicing);
+ }
+
+ IMember IMember.Specialize(TypeParameterSubstitution substitution)
+ {
+ return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType, slicing);
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs b/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs
index 805f95b14..a6981f2b2 100644
--- a/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs
@@ -147,6 +147,10 @@ namespace ICSharpCode.Decompiler.TypeSystem
IAsyncEnumerableOfT,
/// System.Collections.Generic.IAsyncEnumerator{T}
IAsyncEnumeratorOfT,
+ /// System.Index
+ Index,
+ /// System.Range
+ Range
}
///
@@ -155,7 +159,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
[Serializable]
public sealed class KnownTypeReference : ITypeReference
{
- internal const int KnownTypeCodeCount = (int)KnownTypeCode.IAsyncEnumeratorOfT + 1;
+ internal const int KnownTypeCodeCount = (int)KnownTypeCode.Range + 1;
static readonly KnownTypeReference[] knownTypeReferences = new KnownTypeReference[KnownTypeCodeCount] {
null, // None
@@ -218,6 +222,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
new KnownTypeReference(KnownTypeCode.Unsafe, TypeKind.Class, "System.Runtime.CompilerServices", "Unsafe", 0),
new KnownTypeReference(KnownTypeCode.IAsyncEnumerableOfT, TypeKind.Interface, "System.Collections.Generic", "IAsyncEnumerable", 1),
new KnownTypeReference(KnownTypeCode.IAsyncEnumeratorOfT, TypeKind.Interface, "System.Collections.Generic", "IAsyncEnumerator", 1),
+ new KnownTypeReference(KnownTypeCode.Index, TypeKind.Struct, "System", "Index", 0),
+ new KnownTypeReference(KnownTypeCode.Range, TypeKind.Struct, "System", "Range", 0),
};
///
diff --git a/ILSpy.AddIn/ILSpy.AddIn.csproj b/ILSpy.AddIn/ILSpy.AddIn.csproj
index 5a96857ac..1d1a3cea9 100644
--- a/ILSpy.AddIn/ILSpy.AddIn.csproj
+++ b/ILSpy.AddIn/ILSpy.AddIn.csproj
@@ -117,12 +117,14 @@
-
+
-
-
-
-
+
+
+
+
+
+
diff --git a/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj b/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj
index e353abb93..c56ce82e4 100644
--- a/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj
+++ b/ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj
@@ -57,7 +57,7 @@
-
+
diff --git a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs
index 624cd2944..3bb34ee04 100644
--- a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs
+++ b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs
@@ -22,13 +22,18 @@ using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Linq;
using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
using System.Runtime.CompilerServices;
using Iced.Intel;
+
+using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.Decompiler;
+using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Solution;
using ICSharpCode.Decompiler.TypeSystem;
+using ICSharpCode.ILSpy.TextView;
using ILCompiler.Reflection.ReadyToRun;
namespace ICSharpCode.ILSpy.ReadyToRun
@@ -81,10 +86,12 @@ namespace ICSharpCode.ILSpy.ReadyToRun
.GroupBy(m => m.MethodHandle)
.ToDictionary(g => g.Key, g => g.ToArray());
}
+ bool showMetadataTokens = ILSpy.Options.DisplaySettingsPanel.CurrentDisplaySettings.ShowMetadataTokens;
+ bool showMetadataTokensInBase10 = ILSpy.Options.DisplaySettingsPanel.CurrentDisplaySettings.ShowMetadataTokensInBase10;
if (cacheEntry.methodMap.TryGetValue(method.MetadataToken, out var methods)) {
foreach (var readyToRunMethod in methods) {
foreach (RuntimeFunction runtimeFunction in readyToRunMethod.RuntimeFunctions) {
- Disassemble(output, reader, readyToRunMethod, runtimeFunction, bitness, (ulong)runtimeFunction.StartAddress);
+ Disassemble(method.ParentModule.PEFile, output, reader, readyToRunMethod, runtimeFunction, bitness, (ulong)runtimeFunction.StartAddress, showMetadataTokens, showMetadataTokensInBase10);
}
}
}
@@ -96,7 +103,7 @@ namespace ICSharpCode.ILSpy.ReadyToRun
output.WriteLine("; " + comment);
}
- private void Disassemble(ITextOutput output, ReadyToRunReader reader, ReadyToRunMethod readyToRunMethod, RuntimeFunction runtimeFunction, int bitness, ulong address)
+ private void Disassemble(PEFile currentFile, ITextOutput output, ReadyToRunReader reader, ReadyToRunMethod readyToRunMethod, RuntimeFunction runtimeFunction, int bitness, ulong address, bool showMetadataTokens, bool showMetadataTokensInBase10)
{
WriteCommentLine(output, readyToRunMethod.SignatureString);
byte[] codeBytes = new byte[runtimeFunction.Size];
@@ -128,14 +135,16 @@ namespace ICSharpCode.ILSpy.ReadyToRun
var tempOutput = new StringOutput();
foreach (var instr in instructions) {
int byteBaseIndex = (int)(instr.IP - address);
- foreach (var bound in runtimeFunction.DebugInfo.BoundsList) {
- if (bound.NativeOffset == byteBaseIndex) {
- if (bound.ILOffset == (uint)DebugInfoBoundsType.Prolog) {
- WriteCommentLine(output, "Prolog");
- } else if (bound.ILOffset == (uint)DebugInfoBoundsType.Epilog) {
- WriteCommentLine(output, "Epilog");
- } else {
- WriteCommentLine(output, $"IL_{bound.ILOffset:x4}");
+ if (runtimeFunction.DebugInfo != null) {
+ foreach (var bound in runtimeFunction.DebugInfo.BoundsList) {
+ if (bound.NativeOffset == byteBaseIndex) {
+ if (bound.ILOffset == (uint)DebugInfoBoundsType.Prolog) {
+ WriteCommentLine(output, "Prolog");
+ } else if (bound.ILOffset == (uint)DebugInfoBoundsType.Epilog) {
+ WriteCommentLine(output, "Epilog");
+ } else {
+ WriteCommentLine(output, $"IL_{bound.ILOffset:x4}");
+ }
}
}
}
@@ -149,11 +158,51 @@ namespace ICSharpCode.ILSpy.ReadyToRun
for (int i = 0; i < missingBytes; i++)
output.Write(" ");
output.Write(" ");
- output.WriteLine(tempOutput.ToStringAndReset());
+ output.Write(tempOutput.ToStringAndReset());
+ int importCellAddress = (int)instr.IPRelativeMemoryAddress;
+ if (instr.IsCallNearIndirect && reader.ImportCellNames.ContainsKey(importCellAddress)) {
+ output.Write(" ; ");
+ ReadyToRunSignature signature = reader.ImportSignatures[(int)instr.IPRelativeMemoryAddress];
+ switch(signature) {
+ case MethodDefEntrySignature methodDefSignature:
+ var methodDefToken = MetadataTokens.EntityHandle(unchecked((int)methodDefSignature.MethodDefToken));
+ if (showMetadataTokens) {
+ if (showMetadataTokensInBase10) {
+ output.WriteReference(currentFile, methodDefToken, $"({MetadataTokens.GetToken(methodDefToken)}) ", "metadata");
+ } else {
+ output.WriteReference(currentFile, methodDefToken, $"({MetadataTokens.GetToken(methodDefToken):X8}) ", "metadata");
+ }
+ }
+ methodDefToken.WriteTo(currentFile, output, Decompiler.Metadata.GenericContext.Empty);
+ break;
+ case MethodRefEntrySignature methodRefSignature:
+ var methodRefToken = MetadataTokens.EntityHandle(unchecked((int)methodRefSignature.MethodRefToken));
+ if (showMetadataTokens) {
+ if (showMetadataTokensInBase10) {
+ output.WriteReference(currentFile, methodRefToken, $"({MetadataTokens.GetToken(methodRefToken)}) ", "metadata");
+ } else {
+ output.WriteReference(currentFile, methodRefToken, $"({MetadataTokens.GetToken(methodRefToken):X8}) ", "metadata");
+ }
+ }
+ methodRefToken.WriteTo(currentFile, output, Decompiler.Metadata.GenericContext.Empty);
+ break;
+ default:
+ output.WriteLine(reader.ImportCellNames[importCellAddress]);
+ break;
+ }
+ output.WriteLine();
+ } else {
+ output.WriteLine();
+ }
}
output.WriteLine();
}
+ public override RichText GetRichTextTooltip(IEntity entity)
+ {
+ return Languages.ILLanguage.GetRichTextTooltip(entity);
+ }
+
private ReadyToRunReaderCacheEntry GetReader(LoadedAssembly assembly, PEFile module)
{
ReadyToRunReaderCacheEntry result;
diff --git a/ILSpy/Commands/DecompileInNewViewCommand.cs b/ILSpy/Commands/DecompileInNewViewCommand.cs
index 3069d7c6b..152d0f8dd 100644
--- a/ILSpy/Commands/DecompileInNewViewCommand.cs
+++ b/ILSpy/Commands/DecompileInNewViewCommand.cs
@@ -28,7 +28,7 @@ using ICSharpCode.ILSpy.TreeNodes;
namespace ICSharpCode.ILSpy.Commands
{
- [ExportContextMenuEntry(Header = nameof(Resources.DecompileToNewPanel), Icon = "images/Search", Category = nameof(Resources.Analyze), Order = 90)]
+ [ExportContextMenuEntry(Header = nameof(Resources.DecompileToNewPanel), InputGestureText = "MMB", Icon = "images/Search", Category = nameof(Resources.Analyze), Order = 90)]
internal sealed class DecompileInNewViewCommand : IContextMenuEntry
{
public bool IsVisible(TextViewContext context)
diff --git a/ILSpy/DecompilationOptions.cs b/ILSpy/DecompilationOptions.cs
index 840c1a5f4..b73bb8530 100644
--- a/ILSpy/DecompilationOptions.cs
+++ b/ILSpy/DecompilationOptions.cs
@@ -37,6 +37,12 @@ namespace ICSharpCode.ILSpy
/// Gets/Sets the directory into which the project is saved.
///
public string SaveAsProjectDirectory { get; set; }
+
+ ///
+ /// Gets/sets whether invalid identifiers should be escaped (and therefore the code be made compilable).
+ /// This setting is ignored in case is set.
+ ///
+ public bool EscapeInvalidIdentifiers { get; set; }
///
/// Gets the cancellation token that is used to abort the decompiler.
diff --git a/ILSpy/ILSpy.csproj b/ILSpy/ILSpy.csproj
index 201719e62..a371180ea 100644
--- a/ILSpy/ILSpy.csproj
+++ b/ILSpy/ILSpy.csproj
@@ -56,6 +56,8 @@
+
+
diff --git a/ILSpy/Languages/CSharpILMixedLanguage.cs b/ILSpy/Languages/CSharpILMixedLanguage.cs
index 20e058a76..6e986512a 100644
--- a/ILSpy/Languages/CSharpILMixedLanguage.cs
+++ b/ILSpy/Languages/CSharpILMixedLanguage.cs
@@ -47,12 +47,17 @@ namespace ICSharpCode.ILSpy
protected override ReflectionDisassembler CreateDisassembler(ITextOutput output, DecompilationOptions options)
{
- return new ReflectionDisassembler(output,
+ return new ReflectionDisassembler(output,
new MixedMethodBodyDisassembler(output, options) {
DetectControlStructure = detectControlStructure,
ShowSequencePoints = options.DecompilerSettings.ShowDebugInfo
},
- options.CancellationToken);
+ options.CancellationToken)
+ {
+ ShowMetadataTokens = Options.DisplaySettingsPanel.CurrentDisplaySettings.ShowMetadataTokens,
+ ShowMetadataTokensInBase10 = Options.DisplaySettingsPanel.CurrentDisplaySettings.ShowMetadataTokensInBase10,
+ ExpandMemberDefinitions = options.DecompilerSettings.ExpandMemberDefinitions
+ };
}
static CSharpDecompiler CreateDecompiler(PEFile module, DecompilationOptions options)
diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs
index 8e68ea5f8..4f40c308d 100644
--- a/ILSpy/Languages/CSharpLanguage.cs
+++ b/ILSpy/Languages/CSharpLanguage.cs
@@ -119,6 +119,9 @@ namespace ICSharpCode.ILSpy
decompiler.DebugInfoProvider = module.GetDebugInfoOrNull();
while (decompiler.AstTransforms.Count > transformCount)
decompiler.AstTransforms.RemoveAt(decompiler.AstTransforms.Count - 1);
+ if (options.EscapeInvalidIdentifiers) {
+ decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers());
+ }
return decompiler;
}
@@ -413,6 +416,9 @@ namespace ICSharpCode.ILSpy
CSharpDecompiler decompiler = new CSharpDecompiler(typeSystem, options.DecompilerSettings);
decompiler.CancellationToken = options.CancellationToken;
+ if (options.EscapeInvalidIdentifiers) {
+ decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers());
+ }
SyntaxTree st;
if (options.FullDecompilation) {
st = decompiler.DecompileWholeModuleAsSingleFile();
diff --git a/ILSpy/Languages/CSharpLexer.cs b/ILSpy/Languages/CSharpLexer.cs
index 832841462..e8e3a29ee 100644
--- a/ILSpy/Languages/CSharpLexer.cs
+++ b/ILSpy/Languages/CSharpLexer.cs
@@ -1,5 +1,20 @@
-// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
-// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
+//
+// 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;
diff --git a/ILSpy/Languages/ILLanguage.cs b/ILSpy/Languages/ILLanguage.cs
index 4160e5597..7e8365a7d 100644
--- a/ILSpy/Languages/ILLanguage.cs
+++ b/ILSpy/Languages/ILLanguage.cs
@@ -20,15 +20,14 @@ using System.Collections.Generic;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Disassembler;
using System.ComponentModel.Composition;
-using System.Reflection.PortableExecutable;
using System.Reflection.Metadata;
-using System.IO;
-using System.Reflection.Metadata.Ecma335;
using System.Linq;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
using ICSharpCode.Decompiler.Solution;
+using ICSharpCode.AvalonEdit.Highlighting;
+using ICSharpCode.ILSpy.TextView;
namespace ICSharpCode.ILSpy
{
@@ -178,5 +177,38 @@ namespace ICSharpCode.ILSpy
}
return null;
}
+
+ public override RichText GetRichTextTooltip(IEntity entity)
+ {
+ var output = new AvalonEditTextOutput() { IgnoreNewLineAndIndent = true };
+ var disasm = CreateDisassembler(output, new DecompilationOptions());
+ switch (entity.SymbolKind) {
+ case SymbolKind.TypeDefinition:
+ disasm.DisassembleTypeHeader(entity.ParentModule.PEFile, (TypeDefinitionHandle)entity.MetadataToken);
+ break;
+ case SymbolKind.Field:
+ disasm.DisassembleFieldHeader(entity.ParentModule.PEFile, (FieldDefinitionHandle)entity.MetadataToken);
+ break;
+ case SymbolKind.Property:
+ case SymbolKind.Indexer:
+ disasm.DisassemblePropertyHeader(entity.ParentModule.PEFile, (PropertyDefinitionHandle)entity.MetadataToken);
+ break;
+ case SymbolKind.Event:
+ disasm.DisassembleEventHeader(entity.ParentModule.PEFile, (EventDefinitionHandle)entity.MetadataToken);
+ break;
+ case SymbolKind.Method:
+ case SymbolKind.Operator:
+ case SymbolKind.Constructor:
+ case SymbolKind.Destructor:
+ case SymbolKind.Accessor:
+ disasm.DisassembleMethodHeader(entity.ParentModule.PEFile, (MethodDefinitionHandle)entity.MetadataToken);
+ break;
+ default:
+ output.Write(GetDisplayName(entity, true, true, true));
+ break;
+ }
+
+ return new DocumentHighlighter(output.GetDocument(), base.SyntaxHighlighting).HighlightLine(1).ToRichText();
+ }
}
}
diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs
index ef311f68a..a085fea59 100644
--- a/ILSpy/MainWindow.xaml.cs
+++ b/ILSpy/MainWindow.xaml.cs
@@ -35,6 +35,7 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Threading;
+
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.Metadata;
@@ -46,7 +47,13 @@ using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.TreeView;
+
+using Microsoft.NET.HostModel.AppHost;
+using Microsoft.NET.HostModel.Bundle;
using Microsoft.Win32;
+
+using Ookii.Dialogs.Wpf;
+
using OSVersionHelper;
using Xceed.Wpf.AvalonDock.Layout.Serialization;
@@ -868,7 +875,12 @@ namespace ICSharpCode.ILSpy
public void JumpToReference(object reference)
{
- JumpToReferenceAsync(reference).HandleExceptions();
+ JumpToReference(reference, inNewTabPage: false);
+ }
+
+ public void JumpToReference(object reference, bool inNewTabPage)
+ {
+ JumpToReferenceAsync(reference, inNewTabPage).HandleExceptions();
}
///
@@ -879,6 +891,11 @@ namespace ICSharpCode.ILSpy
/// The task will be marked as canceled if the decompilation is canceled.
///
public Task JumpToReferenceAsync(object reference)
+ {
+ return JumpToReferenceAsync(reference, inNewTabPage: false);
+ }
+
+ public Task JumpToReferenceAsync(object reference, bool inNewTabPage)
{
decompilationTask = TaskHelper.CompletedTask;
switch (reference) {
@@ -893,21 +910,22 @@ namespace ICSharpCode.ILSpy
foreach (var handler in protocolHandlers) {
var node = handler.Value.Resolve(protocol, file, unresolvedEntity.Handle, out bool newTabPage);
if (node != null) {
- SelectNode(node, newTabPage);
+ SelectNode(node, newTabPage || inNewTabPage);
return decompilationTask;
}
}
}
- if (MetadataTokenHelpers.TryAsEntityHandle(MetadataTokens.GetToken(unresolvedEntity.Handle)) != null) {
+ var possibleToken = MetadataTokenHelpers.TryAsEntityHandle(MetadataTokens.GetToken(unresolvedEntity.Handle));
+ if (possibleToken != null) {
var typeSystem = new DecompilerTypeSystem(file, file.GetAssemblyResolver(), TypeSystemOptions.Default | TypeSystemOptions.Uncached);
- reference = typeSystem.MainModule.ResolveEntity((EntityHandle)unresolvedEntity.Handle);
+ reference = typeSystem.MainModule.ResolveEntity(possibleToken.Value);
goto default;
}
break;
default:
ILSpyTreeNode treeNode = FindTreeNode(reference);
if (treeNode != null)
- SelectNode(treeNode);
+ SelectNode(treeNode, inNewTabPage);
break;
}
return decompilationTask;
@@ -994,15 +1012,55 @@ namespace ICSharpCode.ILSpy
}
break;
default:
- var asm = assemblyList.OpenAssembly(file);
- if (asm != null) {
- if (loadedAssemblies != null)
- loadedAssemblies.Add(asm);
- else {
- var node = assemblyListTreeNode.FindAssemblyNode(asm);
- if (node != null && focusNode) {
- AssemblyTreeView.SelectedItems.Add(node);
- lastNode = node;
+ if (IsAppBundle(file, out var headerOffset)) {
+ if (MessageBox.Show(this, Properties.Resources.OpenSelfContainedExecutableMessage, "ILSpy", MessageBoxButton.YesNo) == MessageBoxResult.No)
+ break;
+ var dialog = new VistaFolderBrowserDialog();
+ if (dialog.ShowDialog() != true)
+ break;
+ DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => {
+ var output = new AvalonEditTextOutput { Title = "Extracting " + file };
+ Stopwatch w = Stopwatch.StartNew();
+ output.WriteLine($"Extracting {file} to {dialog.SelectedPath}...");
+ var extractor = new Extractor(file, dialog.SelectedPath);
+ extractor.ExtractFiles();
+ output.WriteLine($"Done in {w.Elapsed}.");
+ return output;
+ }, ct)).Then(output => {
+ DockWorkspace.Instance.ShowText(output);
+
+ OpenFileDialog dlg = new OpenFileDialog();
+ dlg.Filter = ".NET assemblies|*.dll;*.exe;*.winmd";
+ dlg.Multiselect = true;
+ dlg.InitialDirectory = dialog.SelectedPath;
+ if (dlg.ShowDialog() == true) {
+ foreach (var item in dlg.FileNames) {
+ var asm = assemblyList.OpenAssembly(item);
+ if (asm != null) {
+ if (loadedAssemblies != null)
+ loadedAssemblies.Add(asm);
+ else {
+ var node = assemblyListTreeNode.FindAssemblyNode(asm);
+ if (node != null && focusNode) {
+ AssemblyTreeView.SelectedItems.Add(node);
+ lastNode = node;
+ }
+ }
+ }
+ }
+ }
+ }).HandleExceptions();
+ } else {
+ var asm = assemblyList.OpenAssembly(file);
+ if (asm != null) {
+ if (loadedAssemblies != null)
+ loadedAssemblies.Add(asm);
+ else {
+ var node = assemblyListTreeNode.FindAssemblyNode(asm);
+ if (node != null && focusNode) {
+ AssemblyTreeView.SelectedItems.Add(node);
+ lastNode = node;
+ }
}
}
}
@@ -1012,6 +1070,16 @@ namespace ICSharpCode.ILSpy
if (lastNode != null && focusNode)
AssemblyTreeView.FocusNode(lastNode);
}
+
+ bool IsAppBundle(string filename, out long bundleHeaderOffset)
+ {
+ try {
+ return HostWriter.IsBundle(filename, out bundleHeaderOffset);
+ } catch (Exception) {
+ bundleHeaderOffset = -1;
+ return false;
+ }
+ }
}
void RefreshCommandExecuted(object sender, ExecutedRoutedEventArgs e)
diff --git a/ILSpy/Properties/AssemblyInfo.template.cs b/ILSpy/Properties/AssemblyInfo.template.cs
index dd418e37e..5077cabf4 100644
--- a/ILSpy/Properties/AssemblyInfo.template.cs
+++ b/ILSpy/Properties/AssemblyInfo.template.cs
@@ -40,7 +40,7 @@ internal static class RevisionClass
public const string Minor = "0";
public const string Build = "0";
public const string Revision = "$INSERTREVISION$";
- public const string VersionName = "preview3";
+ public const string VersionName = "preview4";
public const string FullVersion = Major + "." + Minor + "." + Build + ".$INSERTREVISION$$INSERTBRANCHPOSTFIX$$INSERTVERSIONNAMEPOSTFIX$";
}
diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs
index c56eee03b..a99074936 100644
--- a/ILSpy/Properties/Resources.Designer.cs
+++ b/ILSpy/Properties/Resources.Designer.cs
@@ -947,6 +947,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to Ranges.
+ ///
+ public static string DecompilerSettings_Ranges {
+ get {
+ return ResourceManager.GetString("DecompilerSettings.Ranges", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Read-only methods.
///
@@ -1533,6 +1542,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to New Tab.
+ ///
+ public static string NewTab {
+ get {
+ return ResourceManager.GetString("NewTab", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Nuget Package Browser.
///
@@ -1605,6 +1623,21 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to You are trying to open a single-file executable (app bundle). In order to work with this type of program, ILSpy will
+ ///
+ ///(i) show you a dialog to select a folder to extract the bundle to
+ ///(ii) extract the assemblies (might take a few seconds)
+ ///(iii) show you a dialog to select one or more of those extracted assemblies to decompile
+ ///
+ ///Do you want to proceed?.
+ ///
+ public static string OpenSelfContainedExecutableMessage {
+ get {
+ return ResourceManager.GetString("OpenSelfContainedExecutableMessage", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Options.
///
diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx
index b3e3f01ff..a10920924 100644
--- a/ILSpy/Properties/Resources.resx
+++ b/ILSpy/Properties/Resources.resx
@@ -852,6 +852,9 @@ Do you want to continue?
Do you want to continue?
+
+ Ranges
+
Add preconfigured list...
@@ -867,4 +870,16 @@ Do you want to continue?
Aggressively perform Scalar Replacement Of Aggregates (SROA)
+
+ You are trying to open a single-file executable (app bundle). In order to work with this type of program, ILSpy will
+
+(i) show you a dialog to select a folder to extract the bundle to
+(ii) extract the assemblies (might take a few seconds)
+(iii) show you a dialog to select one or more of those extracted assemblies to decompile
+
+Do you want to proceed?
+
+
+ New Tab
+
\ No newline at end of file
diff --git a/ILSpy/TextView/AvalonEditTextOutput.cs b/ILSpy/TextView/AvalonEditTextOutput.cs
index 5c2bb7203..fdaa23f19 100644
--- a/ILSpy/TextView/AvalonEditTextOutput.cs
+++ b/ILSpy/TextView/AvalonEditTextOutput.cs
@@ -82,7 +82,9 @@ namespace ICSharpCode.ILSpy.TextView
public string IndentationString { get; set; } = "\t";
- public string Title { get; set; }
+ internal bool IgnoreNewLineAndIndent { get; set; }
+
+ public string Title { get; set; } = Properties.Resources.NewTab;
///
/// Gets/sets the that is displayed by this view.
@@ -177,16 +179,22 @@ namespace ICSharpCode.ILSpy.TextView
public void Indent()
{
+ if (IgnoreNewLineAndIndent)
+ return;
indent++;
}
public void Unindent()
{
+ if (IgnoreNewLineAndIndent)
+ return;
indent--;
}
void WriteIndent()
{
+ if (IgnoreNewLineAndIndent)
+ return;
Debug.Assert(textDocument == null);
if (needsIndent) {
needsIndent = false;
@@ -211,10 +219,14 @@ namespace ICSharpCode.ILSpy.TextView
public void WriteLine()
{
Debug.Assert(textDocument == null);
- b.AppendLine();
- needsIndent = true;
- lastLineStart = b.Length;
- lineNumber++;
+ if (IgnoreNewLineAndIndent) {
+ b.Append(' ');
+ } else {
+ b.AppendLine();
+ needsIndent = true;
+ lastLineStart = b.Length;
+ lineNumber++;
+ }
if (this.TextLength > LengthLimit) {
throw new OutputLengthExceededException();
}
diff --git a/ILSpy/TextView/BracketHighlightRenderer.cs b/ILSpy/TextView/BracketHighlightRenderer.cs
index 6a814b7c4..97080dc9e 100644
--- a/ILSpy/TextView/BracketHighlightRenderer.cs
+++ b/ILSpy/TextView/BracketHighlightRenderer.cs
@@ -1,5 +1,20 @@
-// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
-// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
+// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
+//
+// 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.Windows.Media;
diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs
index 5f2f18344..e26704dde 100644
--- a/ILSpy/TextView/DecompilerTextView.cs
+++ b/ILSpy/TextView/DecompilerTextView.cs
@@ -116,7 +116,7 @@ namespace ICSharpCode.ILSpy.TextView
InitializeComponent();
- this.referenceElementGenerator = new ReferenceElementGenerator(this.JumpToReference, this.IsLink);
+ this.referenceElementGenerator = new ReferenceElementGenerator(this.IsLink);
textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator);
this.uiElementGenerator = new UIElementGenerator();
this.bracketHighlightRenderer = new BracketHighlightRenderer(textEditor.TextArea.TextView);
@@ -820,7 +820,7 @@ namespace ICSharpCode.ILSpy.TextView
///
/// Jumps to the definition referred to by the .
///
- internal void JumpToReference(ReferenceSegment referenceSegment)
+ internal void JumpToReference(ReferenceSegment referenceSegment, bool openInNewTab)
{
object reference = referenceSegment.Reference;
if (referenceSegment.IsLocal) {
@@ -849,7 +849,7 @@ namespace ICSharpCode.ILSpy.TextView
return;
}
}
- MainWindow.Instance.JumpToReference(reference);
+ MainWindow.Instance.JumpToReference(reference, openInNewTab);
}
Point? mouseDownPos;
@@ -866,7 +866,7 @@ namespace ICSharpCode.ILSpy.TextView
Vector dragDistance = e.GetPosition(this) - mouseDownPos.Value;
if (Math.Abs(dragDistance.X) < SystemParameters.MinimumHorizontalDragDistance
&& Math.Abs(dragDistance.Y) < SystemParameters.MinimumVerticalDragDistance
- && e.ChangedButton == MouseButton.Left)
+ && (e.ChangedButton == MouseButton.Left || e.ChangedButton == MouseButton.Middle))
{
// click without moving mouse
var referenceSegment = GetReferenceSegmentAtMousePosition();
@@ -878,7 +878,7 @@ namespace ICSharpCode.ILSpy.TextView
// cursor position and the mouse position.
textEditor.TextArea.MouseSelectionMode = MouseSelectionMode.None;
- JumpToReference(referenceSegment);
+ JumpToReference(referenceSegment, e.ChangedButton == MouseButton.Middle || Keyboard.Modifiers.HasFlag(ModifierKeys.Shift));
}
}
}
@@ -952,6 +952,7 @@ namespace ICSharpCode.ILSpy.TextView
Thread thread = new Thread(new ThreadStart(
delegate {
try {
+ context.Options.EscapeInvalidIdentifiers = true;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
using (StreamWriter w = new StreamWriter(fileName)) {
diff --git a/ILSpy/TextView/ReferenceElementGenerator.cs b/ILSpy/TextView/ReferenceElementGenerator.cs
index 262f7d598..341dfcb04 100644
--- a/ILSpy/TextView/ReferenceElementGenerator.cs
+++ b/ILSpy/TextView/ReferenceElementGenerator.cs
@@ -28,7 +28,6 @@ namespace ICSharpCode.ILSpy.TextView
///
sealed class ReferenceElementGenerator : VisualLineElementGenerator
{
- readonly Action referenceClicked;
readonly Predicate isLink;
///
@@ -36,13 +35,10 @@ namespace ICSharpCode.ILSpy.TextView
///
public TextSegmentCollection References { get; set; }
- public ReferenceElementGenerator(Action referenceClicked, Predicate isLink)
+ public ReferenceElementGenerator(Predicate isLink)
{
- if (referenceClicked == null)
- throw new ArgumentNullException(nameof(referenceClicked));
if (isLink == null)
throw new ArgumentNullException(nameof(isLink));
- this.referenceClicked = referenceClicked;
this.isLink = isLink;
}
@@ -72,11 +68,6 @@ namespace ICSharpCode.ILSpy.TextView
}
return null;
}
-
- internal void JumpToReference(ReferenceSegment referenceSegment)
- {
- referenceClicked(referenceSegment);
- }
}
///
diff --git a/ILSpy/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs
index 26fda113b..b3463b956 100644
--- a/ILSpy/TreeNodes/ILSpyTreeNode.cs
+++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs
@@ -16,9 +16,12 @@
// 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.Specialized;
using System.ComponentModel;
using System.Linq;
+using System.Windows;
+using System.Windows.Threading;
using ICSharpCode.Decompiler;
using ICSharpCode.TreeView;
@@ -69,6 +72,12 @@ namespace ICSharpCode.ILSpy.TreeNodes
return false;
}
+ public override void ActivateItemSecondary(RoutedEventArgs e)
+ {
+ MainWindow.Instance.SelectNode(this, inNewTabPage: true);
+ MainWindow.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)MainWindow.Instance.RefreshDecompiledView);
+ }
+
///
/// Used to implement special save logic for some items.
/// This method is called on the main thread when only a single item is selected.
diff --git a/ILSpy/ViewModels/DebugStepsPaneModel.cs b/ILSpy/ViewModels/DebugStepsPaneModel.cs
index 7ea45bb6a..6741f2aa5 100644
--- a/ILSpy/ViewModels/DebugStepsPaneModel.cs
+++ b/ILSpy/ViewModels/DebugStepsPaneModel.cs
@@ -20,7 +20,9 @@ using System.Windows;
namespace ICSharpCode.ILSpy.ViewModels
{
+#if DEBUG
[ExportToolPane(ContentId = PaneContentId)]
+#endif
public class DebugStepsPaneModel : ToolPaneModel
{
public const string PaneContentId = "debugStepsPane";
diff --git a/ILSpy/ViewModels/TabPageModel.cs b/ILSpy/ViewModels/TabPageModel.cs
index d54566d3f..faa4c7f83 100644
--- a/ILSpy/ViewModels/TabPageModel.cs
+++ b/ILSpy/ViewModels/TabPageModel.cs
@@ -28,6 +28,11 @@ namespace ICSharpCode.ILSpy.ViewModels
{
private readonly Dictionary languageVersionHistory = new Dictionary();
+ public TabPageModel()
+ {
+ this.Title = Properties.Resources.NewTab;
+ }
+
private Language language;
public Language Language {
get => language;
diff --git a/SharpTreeView/SharpTreeNode.cs b/SharpTreeView/SharpTreeNode.cs
index 3bffb065b..4606e0b16 100644
--- a/SharpTreeView/SharpTreeNode.cs
+++ b/SharpTreeView/SharpTreeNode.cs
@@ -24,6 +24,7 @@ using System.Windows;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Windows.Media;
+using System.Windows.Input;
namespace ICSharpCode.TreeView
{
@@ -680,7 +681,14 @@ namespace ICSharpCode.TreeView
public virtual void ActivateItem(RoutedEventArgs e)
{
}
-
+
+ ///
+ /// Gets called when the item is clicked with the middle mouse button.
+ ///
+ public virtual void ActivateItemSecondary(RoutedEventArgs e)
+ {
+ }
+
public override string ToString()
{
// used for keyboard navigation
diff --git a/SharpTreeView/SharpTreeViewItem.cs b/SharpTreeView/SharpTreeViewItem.cs
index 354b58917..dc6e85964 100644
--- a/SharpTreeView/SharpTreeViewItem.cs
+++ b/SharpTreeView/SharpTreeViewItem.cs
@@ -119,8 +119,17 @@ namespace ICSharpCode.TreeView
}
}
+ protected override void OnMouseUp(MouseButtonEventArgs e)
+ {
+ if (e.ChangedButton == MouseButton.Middle) {
+ Node.ActivateItemSecondary(e);
+ } else {
+ base.OnMouseUp(e);
+ }
+ }
+
#endregion
-
+
#region Drag and Drop
protected override void OnDragEnter(DragEventArgs e)