Browse Source

Merge branch 'master' into fix-1981

pull/2005/head
Siegfried Pammer 6 years ago committed by GitHub
parent
commit
0880ea4086
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 6
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  3. 281
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs
  4. 52
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs
  5. 5
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  6. 48
      ICSharpCode.Decompiler/CSharp/CallBuilder.cs
  7. 15
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  8. 7
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  9. 3
      ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertMissingTokensDecorator.cs
  10. 142
      ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
  11. 45
      ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs
  12. 1
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  13. 10
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs
  14. 8
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/UnaryOperatorExpression.cs
  15. 92
      ICSharpCode.Decompiler/CSharp/Syntax/Statements/TryCatchStatement.cs
  16. 7
      ICSharpCode.Decompiler/CSharp/Transforms/ReplaceMethodCallsWithOperators.cs
  17. 20
      ICSharpCode.Decompiler/DecompilerSettings.cs
  18. 212
      ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs
  19. 2
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  20. 2
      ICSharpCode.Decompiler/IL/InstructionOutputExtensions.cs
  21. 5
      ICSharpCode.Decompiler/IL/Instructions.cs
  22. 12
      ICSharpCode.Decompiler/IL/Instructions.tt
  23. 2
      ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs
  24. 25
      ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
  25. 13
      ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs
  26. 16
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  27. 9
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  28. 596
      ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs
  29. 183
      ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
  30. 15
      ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
  31. 137
      ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs
  32. 8
      ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs
  33. 2
      ILSpy.AddIn/ILSpy.AddIn.csproj
  34. 2
      ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj
  35. 55
      ILSpy.ReadyToRun/ReadyToRunLanguage.cs
  36. 2
      ILSpy/Commands/DecompileInNewViewCommand.cs
  37. 6
      ILSpy/DecompilationOptions.cs
  38. 2
      ILSpy/ILSpy.csproj
  39. 7
      ILSpy/Languages/CSharpILMixedLanguage.cs
  40. 6
      ILSpy/Languages/CSharpLanguage.cs
  41. 19
      ILSpy/Languages/CSharpLexer.cs
  42. 38
      ILSpy/Languages/ILLanguage.cs
  43. 78
      ILSpy/MainWindow.xaml.cs
  44. 2
      ILSpy/Properties/AssemblyInfo.template.cs
  45. 33
      ILSpy/Properties/Resources.Designer.cs
  46. 15
      ILSpy/Properties/Resources.resx
  47. 14
      ILSpy/TextView/AvalonEditTextOutput.cs
  48. 19
      ILSpy/TextView/BracketHighlightRenderer.cs
  49. 11
      ILSpy/TextView/DecompilerTextView.cs
  50. 11
      ILSpy/TextView/ReferenceElementGenerator.cs
  51. 9
      ILSpy/TreeNodes/ILSpyTreeNode.cs
  52. 2
      ILSpy/ViewModels/DebugStepsPaneModel.cs
  53. 5
      ILSpy/ViewModels/TabPageModel.cs
  54. 8
      SharpTreeView/SharpTreeNode.cs
  55. 9
      SharpTreeView/SharpTreeViewItem.cs

1
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -90,6 +90,7 @@
<Compile Include="DisassemblerPrettyTestRunner.cs" /> <Compile Include="DisassemblerPrettyTestRunner.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" /> <Compile Include="TestCases\Correctness\StringConcat.cs" />
<Compile Include="TestCases\ILPretty\Issue1681.cs" /> <Compile Include="TestCases\ILPretty\Issue1681.cs" />
<None Include="TestCases\Pretty\IndexRangeTest.cs" />
<None Include="TestCases\ILPretty\Issue1918.cs" /> <None Include="TestCases\ILPretty\Issue1918.cs" />
<Compile Include="TestCases\ILPretty\Issue1922.cs" /> <Compile Include="TestCases\ILPretty\Issue1922.cs" />
<Compile Include="TestCases\Ugly\NoLocalFunctions.cs" /> <Compile Include="TestCases\Ugly\NoLocalFunctions.cs" />

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -90,6 +90,12 @@ namespace ICSharpCode.Decompiler.Tests
RunForLibrary(asmOptions: AssemblerOptions.UseDebug); RunForLibrary(asmOptions: AssemblerOptions.UseDebug);
} }
[Test]
public void IndexRangeTest([ValueSource(nameof(dotnetCoreOnlyOptions))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}
[Test] [Test]
public void InlineAssignmentTest([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) public void InlineAssignmentTest([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{ {

281
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<int> GetList()
{
throw null;
}
public static Span<int> 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));
}
}
}

52
ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs

@ -69,6 +69,48 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
} }
} }
private class Container<T1, T2>
{
public GenericStruct<T1, T2> Other;
}
private struct GenericStruct<T1, T2>
{
public T1 Field1;
public T2 Field2;
public Container<T1, T2> 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 public interface ITest
{ {
int Int(); int Int();
@ -243,12 +285,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return t?.Int(); return t?.Int();
} }
// See also: https://github.com/icsharpcode/ILSpy/issues/1050 private static int? GenericRefUnconstrainedInt<T>(ref T t) where T : ITest
// The C# compiler generates pretty weird code in this case. {
//private static int? GenericRefUnconstrainedInt<T>(ref T t) where T : ITest return t?.Int();
//{ }
// return t?.Int();
//}
private static int? GenericRefClassConstraintInt<T>(ref T t) where T : class, ITest private static int? GenericRefClassConstraintInt<T>(ref T t) where T : class, ITest
{ {

5
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -150,7 +150,8 @@ namespace ICSharpCode.Decompiler.CSharp
new TransformCollectionAndObjectInitializers(), new TransformCollectionAndObjectInitializers(),
new TransformExpressionTrees(), new TransformExpressionTrees(),
new NamedArgumentTransform(), new NamedArgumentTransform(),
new UserDefinedLogicTransform() new UserDefinedLogicTransform(),
new IndexRangeTransform()
), ),
} }
}, },
@ -365,7 +366,7 @@ namespace ICSharpCode.Decompiler.CSharp
static bool IsAnonymousMethodCacheField(SRM.FieldDefinition field, MetadataReader metadata) static bool IsAnonymousMethodCacheField(SRM.FieldDefinition field, MetadataReader metadata)
{ {
var name = metadata.GetString(field.Name); 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) static bool IsClosureType(SRM.TypeDefinition type, MetadataReader metadata)

48
ICSharpCode.Decompiler/CSharp/CallBuilder.cs

@ -253,6 +253,12 @@ namespace ICSharpCode.Decompiler.CSharp
argumentList.ExpectedParameters = method.Parameters.ToArray(); argumentList.ExpectedParameters = method.Parameters.ToArray();
} }
if (settings.Ranges) {
if (HandleRangeConstruction(out var result, callOpCode, method, target, argumentList)) {
return result;
}
}
if (callOpCode == OpCode.NewObj) { if (callOpCode == OpCode.NewObj) {
return HandleConstructorCall(expectedTargetDetails, target.ResolveResult, method, argumentList); 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) return Build(call.OpCode, call.Method, arguments, argumentToParameterMap, call.ConstrainedTo)
.WithILInstruction(call).WithILInstruction(block); .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;
}
} }
} }

15
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -2244,14 +2244,23 @@ namespace ICSharpCode.Decompiler.CSharp
arrayType = new ArrayType(compilation, inst.Type, inst.Indices.Count); arrayType = new ArrayType(compilation, inst.Type, inst.Indices.Count);
arrayExpr = arrayExpr.ConvertTo(arrayType, this); arrayExpr = arrayExpr.ConvertTo(arrayType, this);
} }
TranslatedExpression expr = new IndexerExpression( 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) arrayExpr, inst.Indices.Select(i => TranslateArrayIndex(i).Expression)
).WithILInstruction(inst).WithRR(new ResolveResult(arrayType.ElementType)); );
}
TranslatedExpression expr = indexerExpr.WithILInstruction(inst).WithRR(new ResolveResult(arrayType.ElementType));
return new DirectionExpression(FieldDirection.Ref, expr) return new DirectionExpression(FieldDirection.Ref, expr)
.WithoutILInstruction().WithRR(new ByReferenceResolveResult(expr.Type, ReferenceKind.Ref)); .WithoutILInstruction().WithRR(new ByReferenceResolveResult(expr.Type, ReferenceKind.Ref));
} }
TranslatedExpression TranslateArrayIndex(ILInstruction i) TranslatedExpression TranslateArrayIndex(ILInstruction i, bool expectSystemIndex = false)
{ {
var input = Translate(i); var input = Translate(i);
KnownTypeCode targetType; KnownTypeCode targetType;

7
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -696,6 +696,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
case BinaryOperatorType.NullCoalescing: case BinaryOperatorType.NullCoalescing:
spacePolicy = true; spacePolicy = true;
break; break;
case BinaryOperatorType.Range:
spacePolicy = false;
break;
default: default:
throw new NotSupportedException("Invalid value for BinaryOperatorType"); throw new NotSupportedException("Invalid value for BinaryOperatorType");
} }
@ -1812,11 +1815,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
Space(); Space();
WriteKeyword(CatchClause.WhenKeywordRole); WriteKeyword(CatchClause.WhenKeywordRole);
Space(policy.SpaceBeforeIfParentheses); Space(policy.SpaceBeforeIfParentheses);
LPar(); WriteToken(CatchClause.CondLPar);
Space(policy.SpacesWithinIfParentheses); Space(policy.SpacesWithinIfParentheses);
catchClause.Condition.AcceptVisitor(this); catchClause.Condition.AcceptVisitor(this);
Space(policy.SpacesWithinIfParentheses); Space(policy.SpacesWithinIfParentheses);
RPar(); WriteToken(CatchClause.CondRPar);
} }
WriteBlock(catchClause.Body, policy.StatementBraceStyle); WriteBlock(catchClause.Body, policy.StatementBraceStyle);
EndNode(catchClause); EndNode(catchClause);

3
ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertMissingTokensDecorator.cs

@ -46,6 +46,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
} else if (node is Comment comment) { } else if (node is Comment comment) {
comment.SetStartLocation(locationProvider.Location); comment.SetStartLocation(locationProvider.Location);
} }
if (node is ErrorExpression error) {
error.Location = locationProvider.Location;
}
base.StartNode(node); base.StartNode(node);
} }

142
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 // 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 // software and associated documentation files (the "Software"), to deal in the Software
@ -36,25 +36,39 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
/// </summary> /// </summary>
public bool InsertParenthesesForReadability { get; set; } public bool InsertParenthesesForReadability { get; set; }
const int Primary = 17; enum PrecedenceLevel
const int NullableRewrap = 16; {
const int QueryOrLambda = 15; // Higher integer value = higher precedence.
const int Unary = 14; Assignment,
const int RelationalAndTypeTesting = 10; Conditional, // ?:
const int Equality = 9; NullCoalescing, // ??
const int Conditional = 2; ConditionalOr, // ||
const int Assignment = 1; ConditionalAnd, // &&
BitwiseOr, // |
ExclusiveOr, // binary ^
BitwiseAnd, // binary &
Equality, // == !=
RelationalAndTypeTesting, // < <= > >= is
Shift, // << >>
Additive, // binary + -
Multiplicative, // * / %
Range, // ..
Unary,
QueryOrLambda,
NullableRewrap,
Primary
}
/// <summary> /// <summary>
/// Gets the row number in the C# 4.0 spec operator precedence table. /// Gets the row number in the C# 4.0 spec operator precedence table.
/// </summary> /// </summary>
static int GetPrecedence(Expression expr) static PrecedenceLevel GetPrecedence(Expression expr)
{ {
// Note: the operator precedence table on MSDN is incorrect // Note: the operator precedence table on MSDN is incorrect
if (expr is QueryExpression) { if (expr is QueryExpression) {
// Not part of the table in the C# spec, but we need to ensure that queries within // Not part of the table in the C# spec, but we need to ensure that queries within
// primary expressions get parenthesized. // primary expressions get parenthesized.
return QueryOrLambda; return PrecedenceLevel.QueryOrLambda;
} }
if (expr is UnaryOperatorExpression uoe) { if (expr is UnaryOperatorExpression uoe) {
switch (uoe.Operator) { switch (uoe.Operator) {
@ -62,81 +76,83 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
case UnaryOperatorType.PostIncrement: case UnaryOperatorType.PostIncrement:
case UnaryOperatorType.NullConditional: case UnaryOperatorType.NullConditional:
case UnaryOperatorType.SuppressNullableWarning: case UnaryOperatorType.SuppressNullableWarning:
return Primary; return PrecedenceLevel.Primary;
case UnaryOperatorType.NullConditionalRewrap: case UnaryOperatorType.NullConditionalRewrap:
return NullableRewrap; return PrecedenceLevel.NullableRewrap;
case UnaryOperatorType.IsTrue: case UnaryOperatorType.IsTrue:
return Conditional; return PrecedenceLevel.Conditional;
default: default:
return Unary; return PrecedenceLevel.Unary;
} }
} }
if (expr is CastExpression) if (expr is CastExpression)
return Unary; return PrecedenceLevel.Unary;
if (expr is PrimitiveExpression primitive) { if (expr is PrimitiveExpression primitive) {
var value = primitive.Value; var value = primitive.Value;
if (value is int i && i < 0) if (value is int i && i < 0)
return Unary; return PrecedenceLevel.Unary;
if (value is long l && l < 0) if (value is long l && l < 0)
return Unary; return PrecedenceLevel.Unary;
if (value is float f && f < 0) if (value is float f && f < 0)
return Unary; return PrecedenceLevel.Unary;
if (value is double d && d < 0) if (value is double d && d < 0)
return Unary; return PrecedenceLevel.Unary;
if (value is decimal de && de < 0) if (value is decimal de && de < 0)
return Unary; return PrecedenceLevel.Unary;
return PrecedenceLevel.Primary;
} }
BinaryOperatorExpression boe = expr as BinaryOperatorExpression; if (expr is BinaryOperatorExpression boe) {
if (boe != null) {
switch (boe.Operator) { switch (boe.Operator) {
case BinaryOperatorType.Range:
return PrecedenceLevel.Range;
case BinaryOperatorType.Multiply: case BinaryOperatorType.Multiply:
case BinaryOperatorType.Divide: case BinaryOperatorType.Divide:
case BinaryOperatorType.Modulus: case BinaryOperatorType.Modulus:
return 13; // multiplicative return PrecedenceLevel.Multiplicative;
case BinaryOperatorType.Add: case BinaryOperatorType.Add:
case BinaryOperatorType.Subtract: case BinaryOperatorType.Subtract:
return 12; // additive return PrecedenceLevel.Additive;
case BinaryOperatorType.ShiftLeft: case BinaryOperatorType.ShiftLeft:
case BinaryOperatorType.ShiftRight: case BinaryOperatorType.ShiftRight:
return 11; return PrecedenceLevel.Shift;
case BinaryOperatorType.GreaterThan: case BinaryOperatorType.GreaterThan:
case BinaryOperatorType.GreaterThanOrEqual: case BinaryOperatorType.GreaterThanOrEqual:
case BinaryOperatorType.LessThan: case BinaryOperatorType.LessThan:
case BinaryOperatorType.LessThanOrEqual: case BinaryOperatorType.LessThanOrEqual:
return RelationalAndTypeTesting; return PrecedenceLevel.RelationalAndTypeTesting;
case BinaryOperatorType.Equality: case BinaryOperatorType.Equality:
case BinaryOperatorType.InEquality: case BinaryOperatorType.InEquality:
return Equality; return PrecedenceLevel.Equality;
case BinaryOperatorType.BitwiseAnd: case BinaryOperatorType.BitwiseAnd:
return 8; return PrecedenceLevel.BitwiseAnd;
case BinaryOperatorType.ExclusiveOr: case BinaryOperatorType.ExclusiveOr:
return 7; return PrecedenceLevel.ExclusiveOr;
case BinaryOperatorType.BitwiseOr: case BinaryOperatorType.BitwiseOr:
return 6; return PrecedenceLevel.BitwiseOr;
case BinaryOperatorType.ConditionalAnd: case BinaryOperatorType.ConditionalAnd:
return 5; return PrecedenceLevel.ConditionalAnd;
case BinaryOperatorType.ConditionalOr: case BinaryOperatorType.ConditionalOr:
return 4; return PrecedenceLevel.ConditionalOr;
case BinaryOperatorType.NullCoalescing: case BinaryOperatorType.NullCoalescing:
return 3; return PrecedenceLevel.NullCoalescing;
default: default:
throw new NotSupportedException("Invalid value for BinaryOperatorType"); throw new NotSupportedException("Invalid value for BinaryOperatorType");
} }
} }
if (expr is IsExpression || expr is AsExpression) if (expr is IsExpression || expr is AsExpression)
return RelationalAndTypeTesting; return PrecedenceLevel.RelationalAndTypeTesting;
if (expr is ConditionalExpression || expr is DirectionExpression) if (expr is ConditionalExpression || expr is DirectionExpression)
return Conditional; return PrecedenceLevel.Conditional;
if (expr is AssignmentExpression || expr is LambdaExpression) if (expr is AssignmentExpression || expr is LambdaExpression)
return Assignment; return PrecedenceLevel.Assignment;
// anything else: primary expression // anything else: primary expression
return Primary; return PrecedenceLevel.Primary;
} }
/// <summary> /// <summary>
/// Parenthesizes the expression if it does not have the minimum required precedence. /// Parenthesizes the expression if it does not have the minimum required precedence.
/// </summary> /// </summary>
static void ParenthesizeIfRequired(Expression expr, int minimumPrecedence) static void ParenthesizeIfRequired(Expression expr, PrecedenceLevel minimumPrecedence)
{ {
if (GetPrecedence(expr) < minimumPrecedence) { if (GetPrecedence(expr) < minimumPrecedence) {
Parenthesize(expr); Parenthesize(expr);
@ -151,25 +167,25 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// Primary expressions // Primary expressions
public override void VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression) public override void VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression)
{ {
ParenthesizeIfRequired(memberReferenceExpression.Target, Primary); ParenthesizeIfRequired(memberReferenceExpression.Target, PrecedenceLevel.Primary);
base.VisitMemberReferenceExpression(memberReferenceExpression); base.VisitMemberReferenceExpression(memberReferenceExpression);
} }
public override void VisitPointerReferenceExpression(PointerReferenceExpression pointerReferenceExpression) public override void VisitPointerReferenceExpression(PointerReferenceExpression pointerReferenceExpression)
{ {
ParenthesizeIfRequired(pointerReferenceExpression.Target, Primary); ParenthesizeIfRequired(pointerReferenceExpression.Target, PrecedenceLevel.Primary);
base.VisitPointerReferenceExpression(pointerReferenceExpression); base.VisitPointerReferenceExpression(pointerReferenceExpression);
} }
public override void VisitInvocationExpression(InvocationExpression invocationExpression) public override void VisitInvocationExpression(InvocationExpression invocationExpression)
{ {
ParenthesizeIfRequired(invocationExpression.Target, Primary); ParenthesizeIfRequired(invocationExpression.Target, PrecedenceLevel.Primary);
base.VisitInvocationExpression(invocationExpression); base.VisitInvocationExpression(invocationExpression);
} }
public override void VisitIndexerExpression(IndexerExpression indexerExpression) public override void VisitIndexerExpression(IndexerExpression indexerExpression)
{ {
ParenthesizeIfRequired(indexerExpression.Target, Primary); ParenthesizeIfRequired(indexerExpression.Target, PrecedenceLevel.Primary);
ArrayCreateExpression ace = indexerExpression.Target as ArrayCreateExpression; ArrayCreateExpression ace = indexerExpression.Target as ArrayCreateExpression;
if (ace != null && (InsertParenthesesForReadability || ace.Initializer.IsNull)) { if (ace != null && (InsertParenthesesForReadability || ace.Initializer.IsNull)) {
// require parentheses for "(new int[1])[0]" // 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. // Even in readability mode, don't parenthesize casts of casts.
if (!(castExpression.Expression is CastExpression)) { 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 // 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. // "(int)-1" is fine, but "(A)-b" is not a cast.
@ -255,14 +271,14 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// Binary Operators // Binary Operators
public override void VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression) public override void VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression)
{ {
int precedence = GetPrecedence(binaryOperatorExpression); PrecedenceLevel precedence = GetPrecedence(binaryOperatorExpression);
if (binaryOperatorExpression.Operator == BinaryOperatorType.NullCoalescing) { if (binaryOperatorExpression.Operator == BinaryOperatorType.NullCoalescing) {
if (InsertParenthesesForReadability) { if (InsertParenthesesForReadability) {
ParenthesizeIfRequired(binaryOperatorExpression.Left, NullableRewrap); ParenthesizeIfRequired(binaryOperatorExpression.Left, PrecedenceLevel.NullableRewrap);
if (GetBinaryOperatorType(binaryOperatorExpression.Right) == BinaryOperatorType.NullCoalescing) { if (GetBinaryOperatorType(binaryOperatorExpression.Right) == BinaryOperatorType.NullCoalescing) {
ParenthesizeIfRequired(binaryOperatorExpression.Right, precedence); ParenthesizeIfRequired(binaryOperatorExpression.Right, precedence);
} else { } else {
ParenthesizeIfRequired(binaryOperatorExpression.Right, NullableRewrap); ParenthesizeIfRequired(binaryOperatorExpression.Right, PrecedenceLevel.NullableRewrap);
} }
} else { } else {
// ?? is right-associative // ?? is right-associative
@ -270,10 +286,10 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
ParenthesizeIfRequired(binaryOperatorExpression.Right, precedence); ParenthesizeIfRequired(binaryOperatorExpression.Right, precedence);
} }
} else { } else {
if (InsertParenthesesForReadability && precedence < Equality) { if (InsertParenthesesForReadability && precedence < PrecedenceLevel.Equality) {
// In readable mode, boost the priority of the left-hand side if the operator // 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. // 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) { if (GetBinaryOperatorType(binaryOperatorExpression.Left) == binaryOperatorExpression.Operator) {
ParenthesizeIfRequired(binaryOperatorExpression.Left, precedence); ParenthesizeIfRequired(binaryOperatorExpression.Left, precedence);
} else { } else {
@ -309,9 +325,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{ {
if (InsertParenthesesForReadability) { if (InsertParenthesesForReadability) {
// few people know the precedence of 'is', so always put parentheses in nice-looking mode. // 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 { } else {
ParenthesizeIfRequired(isExpression.Expression, RelationalAndTypeTesting); ParenthesizeIfRequired(isExpression.Expression, PrecedenceLevel.RelationalAndTypeTesting);
} }
base.VisitIsExpression(isExpression); base.VisitIsExpression(isExpression);
} }
@ -320,9 +336,9 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{ {
if (InsertParenthesesForReadability) { if (InsertParenthesesForReadability) {
// few people know the precedence of 'as', so always put parentheses in nice-looking mode. // 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 { } else {
ParenthesizeIfRequired(asExpression.Expression, RelationalAndTypeTesting); ParenthesizeIfRequired(asExpression.Expression, PrecedenceLevel.RelationalAndTypeTesting);
} }
base.VisitAsExpression(asExpression); base.VisitAsExpression(asExpression);
} }
@ -341,13 +357,13 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
// Only ((a ? b : c) ? d : e) strictly needs the additional parentheses // Only ((a ? b : c) ? d : e) strictly needs the additional parentheses
if (InsertParenthesesForReadability && !IsConditionalRefExpression(conditionalExpression)) { if (InsertParenthesesForReadability && !IsConditionalRefExpression(conditionalExpression)) {
// Precedence of ?: can be confusing; so always put parentheses in nice-looking mode. // Precedence of ?: can be confusing; so always put parentheses in nice-looking mode.
ParenthesizeIfRequired(conditionalExpression.Condition, NullableRewrap); ParenthesizeIfRequired(conditionalExpression.Condition, PrecedenceLevel.NullableRewrap);
ParenthesizeIfRequired(conditionalExpression.TrueExpression, NullableRewrap); ParenthesizeIfRequired(conditionalExpression.TrueExpression, PrecedenceLevel.NullableRewrap);
ParenthesizeIfRequired(conditionalExpression.FalseExpression, NullableRewrap); ParenthesizeIfRequired(conditionalExpression.FalseExpression, PrecedenceLevel.NullableRewrap);
} else { } else {
ParenthesizeIfRequired(conditionalExpression.Condition, Conditional + 1); ParenthesizeIfRequired(conditionalExpression.Condition, PrecedenceLevel.Conditional + 1);
ParenthesizeIfRequired(conditionalExpression.TrueExpression, Conditional); ParenthesizeIfRequired(conditionalExpression.TrueExpression, PrecedenceLevel.Conditional);
ParenthesizeIfRequired(conditionalExpression.FalseExpression, Conditional); ParenthesizeIfRequired(conditionalExpression.FalseExpression, PrecedenceLevel.Conditional);
} }
base.VisitConditionalExpression(conditionalExpression); base.VisitConditionalExpression(conditionalExpression);
} }
@ -361,11 +377,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public override void VisitAssignmentExpression(AssignmentExpression assignmentExpression) public override void VisitAssignmentExpression(AssignmentExpression assignmentExpression)
{ {
// assignment is right-associative // assignment is right-associative
ParenthesizeIfRequired(assignmentExpression.Left, Assignment + 1); ParenthesizeIfRequired(assignmentExpression.Left, PrecedenceLevel.Assignment + 1);
if (InsertParenthesesForReadability && !(assignmentExpression.Right is DirectionExpression)) { if (InsertParenthesesForReadability && !(assignmentExpression.Right is DirectionExpression)) {
ParenthesizeIfRequired(assignmentExpression.Right, RelationalAndTypeTesting + 1); ParenthesizeIfRequired(assignmentExpression.Right, PrecedenceLevel.RelationalAndTypeTesting + 1);
} else { } else {
ParenthesizeIfRequired(assignmentExpression.Right, Assignment); ParenthesizeIfRequired(assignmentExpression.Right, PrecedenceLevel.Assignment);
} }
base.VisitAssignmentExpression(assignmentExpression); base.VisitAssignmentExpression(assignmentExpression);
} }
@ -394,7 +410,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public override void VisitNamedExpression (NamedExpression namedExpression) public override void VisitNamedExpression (NamedExpression namedExpression)
{ {
if (InsertParenthesesForReadability) { if (InsertParenthesesForReadability) {
ParenthesizeIfRequired(namedExpression.Expression, RelationalAndTypeTesting + 1); ParenthesizeIfRequired(namedExpression.Expression, PrecedenceLevel.RelationalAndTypeTesting + 1);
} }
base.VisitNamedExpression (namedExpression); base.VisitNamedExpression (namedExpression);
} }

45
ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs

@ -113,7 +113,13 @@ namespace ICSharpCode.Decompiler.CSharp
ILInstruction blockContainer = blockStatement.Annotations.OfType<ILInstruction>().FirstOrDefault(); ILInstruction blockContainer = blockStatement.Annotations.OfType<ILInstruction>().FirstOrDefault();
if (blockContainer != null) { if (blockContainer != null) {
StartSequencePoint(blockStatement.LBraceToken); 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 // 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; int intervalEnd = intervalStart + 1;
@ -256,12 +262,15 @@ namespace ICSharpCode.Decompiler.CSharp
foreachStatement.InExpression.AcceptVisitor(this); foreachStatement.InExpression.AcceptVisitor(this);
AddToSequencePoint(foreachInfo.GetEnumeratorCall); AddToSequencePoint(foreachInfo.GetEnumeratorCall);
EndSequencePoint(foreachStatement.InExpression.StartLocation, foreachStatement.InExpression.EndLocation); EndSequencePoint(foreachStatement.InExpression.StartLocation, foreachStatement.InExpression.EndLocation);
StartSequencePoint(foreachStatement); StartSequencePoint(foreachStatement);
AddToSequencePoint(foreachInfo.MoveNextCall); AddToSequencePoint(foreachInfo.MoveNextCall);
EndSequencePoint(foreachStatement.InToken.StartLocation, foreachStatement.InToken.EndLocation); EndSequencePoint(foreachStatement.InToken.StartLocation, foreachStatement.InToken.EndLocation);
StartSequencePoint(foreachStatement); StartSequencePoint(foreachStatement);
AddToSequencePoint(foreachInfo.GetCurrentCall); AddToSequencePoint(foreachInfo.GetCurrentCall);
EndSequencePoint(foreachStatement.VariableType.StartLocation, foreachStatement.VariableNameToken.EndLocation); EndSequencePoint(foreachStatement.VariableType.StartLocation, foreachStatement.VariableNameToken.EndLocation);
VisitAsSequencePoint(foreachStatement.EmbeddedStatement); VisitAsSequencePoint(foreachStatement.EmbeddedStatement);
} }
@ -310,6 +319,33 @@ namespace ICSharpCode.Decompiler.CSharp
VisitAsSequencePoint(fixedStatement.EmbeddedStatement); 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<TryCatchHandler>();
if (tryCatchHandler != null && !tryCatchHandler.ExceptionSpecifierILRange.IsEmpty) {
StartSequencePoint(catchClause.CatchToken);
var function = tryCatchHandler.Ancestors.OfType<ILFunction>().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);
}
/// <summary> /// <summary>
/// Start a new C# statement = new sequence point. /// Start a new C# statement = new sequence point.
/// </summary> /// </summary>
@ -339,6 +375,13 @@ namespace ICSharpCode.Decompiler.CSharp
current = outerStates.Pop(); current = outerStates.Pop();
} }
void AddToSequencePointRaw(ILFunction function, IEnumerable<Interval> ranges)
{
current.Intervals.AddRange(ranges);
Debug.Assert(current.Function == null || current.Function == function);
current.Function = function;
}
/// <summary> /// <summary>
/// Add the ILAst instruction associated with the AstNode to the sequence point. /// 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). /// Also add all its ILAst sub-instructions (unless they were already added to another sequence point).

1
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -363,6 +363,7 @@ namespace ICSharpCode.Decompiler.CSharp
tryCatch.TryBlock = ConvertAsBlock(inst.TryBlock); tryCatch.TryBlock = ConvertAsBlock(inst.TryBlock);
foreach (var handler in inst.Handlers) { foreach (var handler in inst.Handlers) {
var catchClause = new CatchClause(); var catchClause = new CatchClause();
catchClause.AddAnnotation(handler);
var v = handler.Variable; var v = handler.Variable;
if (v != null) { if (v != null) {
catchClause.AddAnnotation(new ILVariableResolveResult(v, v.Type)); catchClause.AddAnnotation(new ILVariableResolveResult(v, v.Type));

10
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 ShiftLeftRole = new TokenRole ("<<");
public readonly static TokenRole ShiftRightRole = new TokenRole (">>"); public readonly static TokenRole ShiftRightRole = new TokenRole (">>");
public readonly static TokenRole NullCoalescingRole = new TokenRole ("??"); public readonly static TokenRole NullCoalescingRole = new TokenRole ("??");
public readonly static TokenRole RangeRole = new TokenRole ("..");
public readonly static Role<Expression> LeftRole = new Role<Expression>("Left", Expression.Null); public readonly static Role<Expression> LeftRole = new Role<Expression>("Left", Expression.Null);
public readonly static Role<Expression> RightRole = new Role<Expression>("Right", Expression.Null); public readonly static Role<Expression> RightRole = new Role<Expression>("Right", Expression.Null);
@ -151,6 +152,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return ShiftRightRole; return ShiftRightRole;
case BinaryOperatorType.NullCoalescing: case BinaryOperatorType.NullCoalescing:
return NullCoalescingRole; return NullCoalescingRole;
case BinaryOperatorType.Range:
return RangeRole;
default: default:
throw new NotSupportedException("Invalid value for BinaryOperatorType"); throw new NotSupportedException("Invalid value for BinaryOperatorType");
} }
@ -197,6 +200,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return ExpressionType.RightShift; return ExpressionType.RightShift;
case BinaryOperatorType.NullCoalescing: case BinaryOperatorType.NullCoalescing:
return ExpressionType.Coalesce; return ExpressionType.Coalesce;
case BinaryOperatorType.Range:
return ExpressionType.Extension;
default: default:
throw new NotSupportedException("Invalid value for BinaryOperatorType"); throw new NotSupportedException("Invalid value for BinaryOperatorType");
} }
@ -255,6 +260,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
ShiftRight, ShiftRight,
/// <summary>left ?? right</summary> /// <summary>left ?? right</summary>
NullCoalescing NullCoalescing,
/// <summary>left .. right</summary>
/// <remarks>left and right are optional = may be Expression.Null</remarks>
Range
} }
} }

8
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 AwaitRole = new TokenRole ("await");
public readonly static TokenRole NullConditionalRole = new TokenRole ("?"); public readonly static TokenRole NullConditionalRole = new TokenRole ("?");
public readonly static TokenRole SuppressNullableWarningRole = new TokenRole ("!"); public readonly static TokenRole SuppressNullableWarningRole = new TokenRole ("!");
public readonly static TokenRole IndexFromEndRole = new TokenRole ("^");
public UnaryOperatorExpression() public UnaryOperatorExpression()
{ {
@ -122,6 +123,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return null; // no syntax return null; // no syntax
case UnaryOperatorType.SuppressNullableWarning: case UnaryOperatorType.SuppressNullableWarning:
return SuppressNullableWarningRole; return SuppressNullableWarningRole;
case UnaryOperatorType.IndexFromEnd:
return IndexFromEndRole;
default: default:
throw new NotSupportedException("Invalid value for UnaryOperatorType"); throw new NotSupportedException("Invalid value for UnaryOperatorType");
} }
@ -150,6 +153,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
case UnaryOperatorType.AddressOf: case UnaryOperatorType.AddressOf:
case UnaryOperatorType.Await: case UnaryOperatorType.Await:
case UnaryOperatorType.SuppressNullableWarning: case UnaryOperatorType.SuppressNullableWarning:
case UnaryOperatorType.IndexFromEnd:
return ExpressionType.Extension; return ExpressionType.Extension;
default: default:
throw new NotSupportedException("Invalid value for UnaryOperatorType"); throw new NotSupportedException("Invalid value for UnaryOperatorType");
@ -206,5 +210,9 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// C# 8 postfix ! operator (dammit operator) /// C# 8 postfix ! operator (dammit operator)
/// </summary> /// </summary>
SuppressNullableWarning, SuppressNullableWarning,
/// <summary>
/// C# 8 prefix ^ operator
/// </summary>
IndexFromEnd,
} }
} }

92
ICSharpCode.Decompiler/CSharp/Syntax/Statements/TryCatchStatement.cs

@ -32,47 +32,47 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// </summary> /// </summary>
public class TryCatchStatement : Statement public class TryCatchStatement : Statement
{ {
public static readonly TokenRole TryKeywordRole = new TokenRole ("try"); public static readonly TokenRole TryKeywordRole = new TokenRole("try");
public static readonly Role<BlockStatement> TryBlockRole = new Role<BlockStatement>("TryBlock", BlockStatement.Null); public static readonly Role<BlockStatement> TryBlockRole = new Role<BlockStatement>("TryBlock", BlockStatement.Null);
public static readonly Role<CatchClause> CatchClauseRole = new Role<CatchClause>("CatchClause", CatchClause.Null); public static readonly Role<CatchClause> CatchClauseRole = new Role<CatchClause>("CatchClause", CatchClause.Null);
public static readonly TokenRole FinallyKeywordRole = new TokenRole ("finally"); public static readonly TokenRole FinallyKeywordRole = new TokenRole("finally");
public static readonly Role<BlockStatement> FinallyBlockRole = new Role<BlockStatement>("FinallyBlock", BlockStatement.Null); public static readonly Role<BlockStatement> FinallyBlockRole = new Role<BlockStatement>("FinallyBlock", BlockStatement.Null);
public CSharpTokenNode TryToken { public CSharpTokenNode TryToken {
get { return GetChildByRole (TryKeywordRole); } get { return GetChildByRole(TryKeywordRole); }
} }
public BlockStatement TryBlock { public BlockStatement TryBlock {
get { return GetChildByRole (TryBlockRole); } get { return GetChildByRole(TryBlockRole); }
set { SetChildByRole (TryBlockRole, value); } set { SetChildByRole(TryBlockRole, value); }
} }
public AstNodeCollection<CatchClause> CatchClauses { public AstNodeCollection<CatchClause> CatchClauses {
get { return GetChildrenByRole (CatchClauseRole); } get { return GetChildrenByRole(CatchClauseRole); }
} }
public CSharpTokenNode FinallyToken { public CSharpTokenNode FinallyToken {
get { return GetChildByRole (FinallyKeywordRole); } get { return GetChildByRole(FinallyKeywordRole); }
} }
public BlockStatement FinallyBlock { public BlockStatement FinallyBlock {
get { return GetChildByRole (FinallyBlockRole); } get { return GetChildByRole(FinallyBlockRole); }
set { SetChildByRole (FinallyBlockRole, value); } 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<T> (IAstVisitor<T> visitor) public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{ {
return visitor.VisitTryCatchStatement (this); return visitor.VisitTryCatchStatement(this);
} }
public override S AcceptVisitor<T, S> (IAstVisitor<T, S> visitor, T data) public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{ {
return visitor.VisitTryCatchStatement (this, data); return visitor.VisitTryCatchStatement(this, data);
} }
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)
@ -87,12 +87,14 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// </summary> /// </summary>
public class CatchClause : AstNode public class CatchClause : AstNode
{ {
public static readonly TokenRole CatchKeywordRole = new TokenRole ("catch"); public static readonly TokenRole CatchKeywordRole = new TokenRole("catch");
public static readonly TokenRole WhenKeywordRole = new TokenRole ("when"); public static readonly TokenRole WhenKeywordRole = new TokenRole("when");
public static readonly Role<Expression> ConditionRole = Roles.Condition; public static readonly Role<Expression> ConditionRole = Roles.Condition;
public static readonly TokenRole CondLPar = new TokenRole("(");
public static readonly TokenRole CondRPar = new TokenRole(")");
#region Null #region Null
public new static readonly CatchClause Null = new NullCatchClause (); public new static readonly CatchClause Null = new NullCatchClause();
sealed class NullCatchClause : CatchClause sealed class NullCatchClause : CatchClause
{ {
@ -102,17 +104,17 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
} }
} }
public override void AcceptVisitor (IAstVisitor visitor) public override void AcceptVisitor(IAstVisitor visitor)
{ {
visitor.VisitNullNode(this); visitor.VisitNullNode(this);
} }
public override T AcceptVisitor<T> (IAstVisitor<T> visitor) public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{ {
return visitor.VisitNullNode(this); return visitor.VisitNullNode(this);
} }
public override S AcceptVisitor<T, S> (IAstVisitor<T, S> visitor, T data) public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{ {
return visitor.VisitNullNode(this, data); return visitor.VisitNullNode(this, data);
} }
@ -143,12 +145,12 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
get { return NodeType.Pattern; } get { return NodeType.Pattern; }
} }
public override void AcceptVisitor (IAstVisitor visitor) public override void AcceptVisitor(IAstVisitor visitor)
{ {
visitor.VisitPatternPlaceholder(this, child); visitor.VisitPatternPlaceholder(this, child);
} }
public override T AcceptVisitor<T> (IAstVisitor<T> visitor) public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{ {
return visitor.VisitPatternPlaceholder(this, child); return visitor.VisitPatternPlaceholder(this, child);
} }
@ -177,31 +179,31 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
} }
public CSharpTokenNode CatchToken { public CSharpTokenNode CatchToken {
get { return GetChildByRole (CatchKeywordRole); } get { return GetChildByRole(CatchKeywordRole); }
} }
public CSharpTokenNode LParToken { public CSharpTokenNode LParToken {
get { return GetChildByRole (Roles.LPar); } get { return GetChildByRole(Roles.LPar); }
} }
public AstType Type { public AstType Type {
get { return GetChildByRole (Roles.Type); } get { return GetChildByRole(Roles.Type); }
set { SetChildByRole (Roles.Type, value); } set { SetChildByRole(Roles.Type, value); }
} }
public string VariableName { public string VariableName {
get { return GetChildByRole (Roles.Identifier).Name; } get { return GetChildByRole(Roles.Identifier).Name; }
set { set {
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
SetChildByRole (Roles.Identifier, null); SetChildByRole(Roles.Identifier, null);
else else
SetChildByRole (Roles.Identifier, Identifier.Create (value)); SetChildByRole(Roles.Identifier, Identifier.Create(value));
} }
} }
public Identifier VariableNameToken { public Identifier VariableNameToken {
get { get {
return GetChildByRole (Roles.Identifier); return GetChildByRole(Roles.Identifier);
} }
set { set {
SetChildByRole(Roles.Identifier, value); SetChildByRole(Roles.Identifier, value);
@ -209,11 +211,15 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
} }
public CSharpTokenNode RParToken { public CSharpTokenNode RParToken {
get { return GetChildByRole (Roles.RPar); } get { return GetChildByRole(Roles.RPar); }
} }
public CSharpTokenNode WhenToken { public CSharpTokenNode WhenToken {
get { return GetChildByRole (WhenKeywordRole); } get { return GetChildByRole(WhenKeywordRole); }
}
public CSharpTokenNode CondLParToken {
get { return GetChildByRole(CondLPar); }
} }
public Expression Condition { public Expression Condition {
@ -221,24 +227,28 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
set { SetChildByRole(ConditionRole, value); } set { SetChildByRole(ConditionRole, value); }
} }
public CSharpTokenNode CondRParToken {
get { return GetChildByRole(CondRPar); }
}
public BlockStatement Body { public BlockStatement Body {
get { return GetChildByRole (Roles.Body); } get { return GetChildByRole(Roles.Body); }
set { SetChildByRole (Roles.Body, value); } 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<T> (IAstVisitor<T> visitor) public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{ {
return visitor.VisitCatchClause (this); return visitor.VisitCatchClause(this);
} }
public override S AcceptVisitor<T, S> (IAstVisitor<T, S> visitor, T data) public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{ {
return visitor.VisitCatchClause (this, data); return visitor.VisitCatchClause(this, data);
} }
protected internal override bool DoMatch(AstNode other, PatternMatching.Match match) protected internal override bool DoMatch(AstNode other, PatternMatching.Match match)

7
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()))); invocationExpression.ReplaceWith(new ObjectCreateExpression(context.TypeSystemAstBuilder.ConvertType(method.TypeArguments.First())));
} }
break; 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); BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(method.Name);

20
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -112,12 +112,13 @@ namespace ICSharpCode.Decompiler
asyncUsingAndForEachStatement = false; asyncUsingAndForEachStatement = false;
asyncEnumerator = false; asyncEnumerator = false;
staticLocalFunctions = false; staticLocalFunctions = false;
ranges = false;
} }
} }
public CSharp.LanguageVersion GetMinimumRequiredVersion() public CSharp.LanguageVersion GetMinimumRequiredVersion()
{ {
if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions) if (nullableReferenceTypes || readOnlyMethods || asyncEnumerator || asyncUsingAndForEachStatement || staticLocalFunctions || ranges)
return CSharp.LanguageVersion.CSharp8_0; return CSharp.LanguageVersion.CSharp8_0;
if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers || patternBasedFixedStatement) if (introduceUnmanagedConstraint || tupleComparisons || stackAllocInitializers || patternBasedFixedStatement)
return CSharp.LanguageVersion.CSharp7_3; return CSharp.LanguageVersion.CSharp7_3;
@ -1100,6 +1101,23 @@ namespace ICSharpCode.Decompiler
} }
} }
bool ranges = true;
/// <summary>
/// Gets/Sets whether C# 8.0 static local functions should be transformed.
/// </summary>
[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; bool nullableReferenceTypes = true;
/// <summary> /// <summary>

212
ICSharpCode.Decompiler/Disassembler/ReflectionDisassembler.cs

@ -302,6 +302,8 @@ namespace ICSharpCode.Decompiler.Disassembler
if (spaceAfter) { if (spaceAfter) {
output.Write(' '); output.Write(' ');
} }
} else if (spaceBefore && spaceAfter) {
output.Write(' ');
} }
} }
@ -1112,42 +1114,11 @@ namespace ICSharpCode.Decompiler.Disassembler
{ FieldAttributes.NotSerialized, "notserialized" }, { FieldAttributes.NotSerialized, "notserialized" },
}; };
public void DisassembleField(PEFile module, FieldDefinitionHandle field) public void DisassembleField(PEFile module, FieldDefinitionHandle handle)
{ {
var metadata = module.Metadata; var metadata = module.Metadata;
var fieldDefinition = metadata.GetFieldDefinition(field); var fieldDefinition = metadata.GetFieldDefinition(handle);
output.WriteReference(module, field, ".field ", isDefinition: true); char sectionPrefix = DisassembleFieldHeaderInternal(module, handle, metadata, fieldDefinition);
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));
}
output.WriteLine(); output.WriteLine();
var attributes = fieldDefinition.GetCustomAttributes(); var attributes = fieldDefinition.GetCustomAttributes();
if (attributes.Count > 0) { 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) char GetRVASectionPrefix(System.Reflection.PortableExecutable.PEHeaders headers, int rva)
{ {
int sectionIndex = headers.GetContainingSectionIndex(rva); int sectionIndex = headers.GetContainingSectionIndex(rva);
@ -1215,8 +1233,30 @@ namespace ICSharpCode.Decompiler.Disassembler
{ {
var metadata = module.Metadata; var metadata = module.Metadata;
var propertyDefinition = metadata.GetPropertyDefinition(property); var propertyDefinition = metadata.GetPropertyDefinition(property);
output.WriteReference(module, property, ".property", isDefinition: true); PropertyAccessors accessors = DisassemblePropertyHeaderInternal(module, property, metadata, propertyDefinition);
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();
}
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); WriteFlags(propertyDefinition.Attributes, propertyAttributes);
var accessors = propertyDefinition.GetAccessors(); var accessors = propertyDefinition.GetAccessors();
var declaringType = metadata.GetMethodDefinition(accessors.GetAny()).GetDeclaringType(); var declaringType = metadata.GetMethodDefinition(accessors.GetAny()).GetDeclaringType();
@ -1239,15 +1279,7 @@ namespace ICSharpCode.Decompiler.Disassembler
output.Unindent(); output.Unindent();
} }
output.Write(')'); output.Write(')');
return accessors;
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();
} }
void WriteNestedMethod(string keyword, PEFile module, MethodDefinitionHandle method) void WriteNestedMethod(string keyword, PEFile module, MethodDefinitionHandle method)
@ -1272,6 +1304,27 @@ namespace ICSharpCode.Decompiler.Disassembler
{ {
var eventDefinition = module.Metadata.GetEventDefinition(handle); var eventDefinition = module.Metadata.GetEventDefinition(handle);
var accessors = eventDefinition.GetAccessors(); 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; TypeDefinitionHandle declaringType;
if (!accessors.Adder.IsNil) { if (!accessors.Adder.IsNil) {
declaringType = module.Metadata.GetMethodDefinition(accessors.Adder).GetDeclaringType(); declaringType = module.Metadata.GetMethodDefinition(accessors.Adder).GetDeclaringType();
@ -1281,7 +1334,8 @@ namespace ICSharpCode.Decompiler.Disassembler
declaringType = module.Metadata.GetMethodDefinition(accessors.Raiser).GetDeclaringType(); declaringType = module.Metadata.GetMethodDefinition(accessors.Raiser).GetDeclaringType();
} }
output.WriteReference(module, handle, ".event", isDefinition: true); 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); WriteFlags(eventDefinition.Attributes, eventAttributes);
var provider = new DisassemblerSignatureTypeProvider(module, output); var provider = new DisassemblerSignatureTypeProvider(module, output);
Action<ILNameSyntax> signature; Action<ILNameSyntax> signature;
@ -1302,15 +1356,6 @@ namespace ICSharpCode.Decompiler.Disassembler
signature(ILNameSyntax.TypeName); signature(ILNameSyntax.TypeName);
output.Write(' '); output.Write(' ');
output.Write(DisassemblerHelpers.Escape(module.Metadata.GetString(eventDefinition.Name))); 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 #endregion
@ -1352,30 +1397,9 @@ namespace ICSharpCode.Decompiler.Disassembler
public void DisassembleType(PEFile module, TypeDefinitionHandle type) public void DisassembleType(PEFile module, TypeDefinitionHandle type)
{ {
var typeDefinition = module.Metadata.GetTypeDefinition(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); GenericContext genericContext = new GenericContext(type, module);
WriteTypeParameters(output, module, genericContext, typeDefinition.GetGenericParameters());
output.MarkFoldStart(defaultCollapsed: !ExpandMemberDefinitions && isInType);
output.WriteLine();
EntityHandle baseType = typeDefinition.GetBaseTypeOrNil(); DisassembleTypeHeaderInternal(module, type, typeDefinition, genericContext);
if (!baseType.IsNil) {
output.Indent();
output.Write("extends ");
baseType.WriteTo(module, output, genericContext, ILNameSyntax.TypeName);
output.WriteLine();
output.Unindent();
}
var interfaces = typeDefinition.GetInterfaceImplementations(); var interfaces = typeDefinition.GetInterfaceImplementations();
if (interfaces.Count > 0) { if (interfaces.Count > 0) {
@ -1463,6 +1487,40 @@ namespace ICSharpCode.Decompiler.Disassembler
isInType = oldIsInType; 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) void WriteTypeParameters(ITextOutput output, PEFile module, GenericContext context, GenericParameterHandleCollection p)
{ {
if (p.Count > 0) { if (p.Count > 0) {

2
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -64,6 +64,7 @@
<Compile Include="CSharp\OutputVisitor\GenericGrammarAmbiguityVisitor.cs" /> <Compile Include="CSharp\OutputVisitor\GenericGrammarAmbiguityVisitor.cs" />
<Compile Include="CSharp\RequiredNamespaceCollector.cs" /> <Compile Include="CSharp\RequiredNamespaceCollector.cs" />
<Compile Include="CSharp\SequencePointBuilder.cs" /> <Compile Include="CSharp\SequencePointBuilder.cs" />
<Compile Include="IL\Transforms\IndexRangeTransform.cs" />
<Compile Include="CSharp\TranslatedStatement.cs" /> <Compile Include="CSharp\TranslatedStatement.cs" />
<Compile Include="DebugInfo\KnownGuids.cs" /> <Compile Include="DebugInfo\KnownGuids.cs" />
<Compile Include="Disassembler\DisassemblerSignatureTypeProvider.cs" /> <Compile Include="Disassembler\DisassemblerSignatureTypeProvider.cs" />
@ -384,6 +385,7 @@
<Compile Include="TypeSystem\Implementation\MetadataTypeDefinition.cs" /> <Compile Include="TypeSystem\Implementation\MetadataTypeDefinition.cs" />
<Compile Include="TypeSystem\Implementation\MetadataTypeParameter.cs" /> <Compile Include="TypeSystem\Implementation\MetadataTypeParameter.cs" />
<Compile Include="TypeSystem\Implementation\SpecializedParameter.cs" /> <Compile Include="TypeSystem\Implementation\SpecializedParameter.cs" />
<Compile Include="TypeSystem\Implementation\SyntheticRangeIndexer.cs" />
<Compile Include="TypeSystem\Implementation\ThreeState.cs" /> <Compile Include="TypeSystem\Implementation\ThreeState.cs" />
<Compile Include="TypeSystem\MetadataModule.cs" /> <Compile Include="TypeSystem\MetadataModule.cs" />
<Compile Include="TypeSystem\ModifiedType.cs" /> <Compile Include="TypeSystem\ModifiedType.cs" />

2
ICSharpCode.Decompiler/IL/InstructionOutputExtensions.cs

@ -44,7 +44,7 @@ namespace ICSharpCode.Decompiler.IL
output.Write(primitiveType.ToString().ToLowerInvariant()); 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); output.WriteReference(type, type.ReflectionName);
} }

5
ICSharpCode.Decompiler/IL/Instructions.cs

@ -4713,6 +4713,7 @@ namespace ICSharpCode.Decompiler.IL
clone.Indices.AddRange(this.Indices.Select(arg => (ILInstruction)arg.Clone())); clone.Indices.AddRange(this.Indices.Select(arg => (ILInstruction)arg.Clone()));
return clone; return clone;
} }
public bool WithSystemIndex;
public bool DelayExceptions; // NullReferenceException/IndexOutOfBoundsException only occurs when the reference is dereferenced public bool DelayExceptions; // NullReferenceException/IndexOutOfBoundsException only occurs when the reference is dereferenced
public override StackType ResultType { get { return StackType.Ref; } } public override StackType ResultType { get { return StackType.Ref; } }
/// <summary>Gets whether the 'readonly' prefix was applied to this instruction.</summary> /// <summary>Gets whether the 'readonly' prefix was applied to this instruction.</summary>
@ -4729,6 +4730,8 @@ namespace ICSharpCode.Decompiler.IL
public override void WriteTo(ITextOutput output, ILAstWritingOptions options) public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{ {
WriteILRange(output, options); WriteILRange(output, options);
if (WithSystemIndex)
output.Write("withsystemindex.");
if (DelayExceptions) if (DelayExceptions)
output.Write("delayex."); output.Write("delayex.");
if (IsReadOnly) if (IsReadOnly)
@ -4759,7 +4762,7 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match) protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{ {
var o = other as LdElema; 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;
} }
} }
} }

12
ICSharpCode.Decompiler/IL/Instructions.tt

@ -278,6 +278,7 @@
CustomClassName("LdLen"), CustomArguments(("array", new[] { "O" })), CustomConstructor, CustomWriteTo, MayThrow), CustomClassName("LdLen"), CustomArguments(("array", new[] { "O" })), CustomConstructor, CustomWriteTo, MayThrow),
new OpCode("ldelema", "Load address of array element.", new OpCode("ldelema", "Load address of array element.",
CustomClassName("LdElema"), HasTypeOperand, CustomChildren(new [] { new ArgumentInfo("array"), new ArgumentInfo("indices") { IsCollection = true } }, true), CustomClassName("LdElema"), HasTypeOperand, CustomChildren(new [] { new ArgumentInfo("array"), new ArgumentInfo("indices") { IsCollection = true } }, true),
BoolFlag("WithSystemIndex"),
MayThrowIfNotDelayed, ResultType("Ref"), SupportsReadonlyPrefix), MayThrowIfNotDelayed, ResultType("Ref"), SupportsReadonlyPrefix),
new OpCode("get.pinnable.reference", "Retrieves a pinnable reference for the input object." + Environment.NewLine 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 + "The input must be an object reference (O)." + Environment.NewLine
@ -1105,6 +1106,17 @@ protected override void Disconnected()
opCode.WriteOperand.Add("member.WriteTo(output);"); opCode.WriteOperand.Add("member.WriteTo(output);");
}; };
// Adds a member of type bool to the instruction.
static Action<OpCode> 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. // LoadConstant trait: the instruction loads a compile-time constant. Implies NoArguments.
static Action<OpCode> LoadConstant(string operandType) static Action<OpCode> LoadConstant(string operandType)
{ {

2
ICSharpCode.Decompiler/IL/Instructions/CallInstruction.cs

@ -135,7 +135,7 @@ namespace ICSharpCode.Decompiler.IL
WriteILRange(output, options); WriteILRange(output, options);
if (ConstrainedTo != null) { if (ConstrainedTo != null) {
output.Write("constrained["); output.Write("constrained[");
ConstrainedTo.WriteTo(output, ILNameSyntax.ShortTypeName); ConstrainedTo.WriteTo(output);
output.Write("]."); output.Write("].");
} }
if (IsTail) if (IsTail)

25
ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs

@ -214,24 +214,29 @@ namespace ICSharpCode.Decompiler.IL
public void AddILRange(Interval newRange) public void AddILRange(Interval newRange)
{ {
if (this.ILRange.IsEmpty) { this.ILRange = CombineILRange(this.ILRange, newRange);
this.ILRange = newRange; }
return;
protected static Interval CombineILRange(Interval oldRange, Interval newRange)
{
if (oldRange.IsEmpty) {
return newRange;
} }
if (newRange.IsEmpty) { if (newRange.IsEmpty) {
return; return oldRange;
} }
if (newRange.Start <= this.StartILOffset) { if (newRange.Start <= oldRange.Start) {
if (newRange.End < this.StartILOffset) { if (newRange.End < oldRange.Start) {
this.ILRange = newRange; // use the earlier range return newRange; // use the earlier range
} else { } else {
// join overlapping ranges // 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 // 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) public void AddILRange(ILInstruction sourceInstruction)

13
ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs

@ -19,6 +19,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL namespace ICSharpCode.Decompiler.IL
{ {
@ -175,6 +176,18 @@ namespace ICSharpCode.Decompiler.IL
output.Write(' '); output.Write(' ');
body.WriteTo(output, options); body.WriteTo(output, options);
} }
/// <summary>
/// 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.
/// </summary>
public Interval ExceptionSpecifierILRange { get; private set; }
public void AddExceptionSpecifierILRange(Interval newRange)
{
ExceptionSpecifierILRange = CombineILRange(ExceptionSpecifierILRange, newRange);
}
} }
partial class TryFinally partial class TryFinally

16
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -17,10 +17,12 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms namespace ICSharpCode.Decompiler.IL.Transforms
{ {
@ -206,6 +208,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
base.VisitLdElema(inst); base.VisitLdElema(inst);
CleanUpArrayIndices(inst.Indices); CleanUpArrayIndices(inst.Indices);
if (IndexRangeTransform.HandleLdElema(inst, context))
return;
} }
protected internal override void VisitNewArr(NewArr inst) protected internal override void VisitNewArr(NewArr inst)
@ -692,7 +696,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
TransformCatchWhen(inst, filterContainer.EntryPoint); TransformCatchWhen(inst, filterContainer.EntryPoint);
} }
if (inst.Body is BlockContainer catchContainer) if (inst.Body is BlockContainer catchContainer)
TransformCatchVariable(inst, catchContainer.EntryPoint); TransformCatchVariable(inst, catchContainer.EntryPoint, isCatchBlock: true);
} }
/// <summary> /// <summary>
@ -709,7 +713,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// } /// }
/// } /// }
/// </summary> /// </summary>
void TransformCatchVariable(TryCatchHandler handler, Block entryPoint) void TransformCatchVariable(TryCatchHandler handler, Block entryPoint, bool isCatchBlock)
{ {
if (!handler.Variable.IsSingleDefinition || handler.Variable.LoadCount != 1) if (!handler.Variable.IsSingleDefinition || handler.Variable.LoadCount != 1)
return; // handle.Variable already has non-trivial uses return; // handle.Variable already has non-trivial uses
@ -720,6 +724,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (inlinedUnboxAny.Type.Equals(handler.Variable.Type)) { if (inlinedUnboxAny.Type.Equals(handler.Variable.Type)) {
context.Step("TransformCatchVariable - remove inlined UnboxAny", inlinedUnboxAny); context.Step("TransformCatchVariable - remove inlined UnboxAny", inlinedUnboxAny);
inlinedUnboxAny.ReplaceWith(inlinedUnboxAny.Argument); inlinedUnboxAny.ReplaceWith(inlinedUnboxAny.Argument);
foreach (var range in inlinedUnboxAny.ILRanges)
handler.AddExceptionSpecifierILRange(range);
} }
} }
return; return;
@ -746,6 +752,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
exceptionVar.Kind = VariableKind.ExceptionLocal; exceptionVar.Kind = VariableKind.ExceptionLocal;
exceptionVar.Type = handler.Variable.Type; exceptionVar.Type = handler.Variable.Type;
handler.Variable = exceptionVar; handler.Variable = exceptionVar;
if (isCatchBlock) {
foreach (var offset in entryPoint.Instructions[0].Descendants.SelectMany(o => o.ILRanges))
handler.AddExceptionSpecifierILRange(offset);
}
entryPoint.Instructions.RemoveAt(0); entryPoint.Instructions.RemoveAt(0);
} }
@ -754,7 +764,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary> /// </summary>
void TransformCatchWhen(TryCatchHandler handler, Block entryPoint) 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)) { if (entryPoint.Instructions.Count == 1 && entryPoint.Instructions[0].MatchLeave(out _, out var condition)) {
context.Step("TransformCatchWhen", entryPoint.Instructions[0]); context.Step("TransformCatchWhen", entryPoint.Instructions[0]);
handler.Filter = condition; handler.Filter = condition;

9
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -21,6 +21,7 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
namespace ICSharpCode.Decompiler.IL.Transforms namespace ICSharpCode.Decompiler.IL.Transforms
{ {
@ -469,6 +470,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (parent.SlotInfo == CompoundAssignmentInstruction.TargetSlot) { if (parent.SlotInfo == CompoundAssignmentInstruction.TargetSlot) {
return true; return true;
} }
if (((CallInstruction)parent).Method is SyntheticRangeIndexAccessor) {
return true;
}
break;
case OpCode.LdElema:
if (((LdElema)parent).WithSystemIndex) {
return true;
}
break; break;
} }
// decide based on the top-level target instruction into which we are inlining: // decide based on the top-level target instruction into which we are inlining:

596
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
{
/// <summary>
/// Transform for the C# 8 System.Index / System.Range feature
/// </summary>
class IndexRangeTransform : IStatementTransform
{
/// <summary>
/// Called by expression transforms.
/// Handles the `array[System.Index]` cases.
/// </summary>
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));
}
/// <summary>
/// Check that the number of uses of the containerLengthVar variable matches those expected in the pattern.
/// </summary>
private bool CheckContainerLengthVariableUseCount(ILVariable containerLengthVar, IndexKind startIndexKind, IndexKind endIndexKind = IndexKind.FromStart)
{
int expectedUses = 0;
if (startIndexKind != IndexKind.FromStart && 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
}
}
/// <summary>
/// Matches 'addressof System.Index(call get_Start/get_End(ldloca rangeVar))'
/// </summary>
static bool MatchIndexFromRange(IndexKind indexKind, ILInstruction indexLoad, ILVariable rangeVar, string accessorName)
{
if (indexKind != IndexKind.RefSystemIndex)
return false;
if (!(indexLoad is AddressOf addressOf))
return false;
if (!addressOf.Type.IsKnownType(KnownTypeCode.Index))
return false;
if (!(addressOf.Value is Call call))
return false;
if (call.Method.Name != accessorName)
return false;
if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Range))
return false;
if (call.Arguments.Count != 1)
return false;
return call.Arguments[0].MatchLdLoca(rangeVar);
}
static ILInstruction MakeIndex(IndexKind indexKind, ILInstruction indexLoad, IndexMethods specialMethods)
{
if (indexKind == IndexKind.RefSystemIndex) {
// stloc containerLengthVar(call get_Length/get_Count(ldloc container))
// stloc startOffsetVar(call GetOffset(startIndexLoad, ldloc length))
// complex_expr(call get_Item(ldloc container, ldloc startOffsetVar))
// -->
// complex_expr(call get_Item(ldloc container, ldobj startIndexLoad))
return new LdObj(indexLoad, specialMethods.IndexType);
} else if (indexKind == IndexKind.FromEnd || 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 } };
}
}
/// <summary>
/// Gets whether the C# compiler will call `container[int]` when using `container[Index]`.
/// </summary>
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;
}
}
/// <summary>
/// Matches the instruction:
/// stloc containerLengthVar(call get_Length/get_Count(ldloc containerVar))
/// </summary>
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);
}
/// <summary>
/// If lengthVar is non-null, matches 'ldloc lengthVar'.
///
/// Otherwise, matches the instruction:
/// call get_Length/get_Count(ldloc containerVar)
/// </summary>
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
{
/// <summary>
/// indexLoad is an integer, from the start of the container
/// </summary>
FromStart,
/// <summary>
/// indexLoad is loading the address of a System.Index struct
/// </summary>
RefSystemIndex,
/// <summary>
/// indexLoad is an integer, from the end of the container
/// </summary>
FromEnd,
/// <summary>
/// Always equivalent to `0`, used for the start-index when slicing without a startpoint `a[..end]`
/// </summary>
TheStart,
/// <summary>
/// Always equivalent to `^0`, used for the end-index when slicing without an endpoint `a[start..]`
/// </summary>
TheEnd,
}
/// <summary>
/// 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.
/// </summary>
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;
}
}
/// <summary>
/// Matches an instruction computing a slice length:
/// binary.sub.i4(call GetOffset(endIndexLoad, ldloc length), ldloc startOffset))
/// </summary>
static bool MatchSliceLength(ILInstruction inst, out IndexKind endIndexKind, out ILInstruction endIndexLoad, ILVariable containerLengthVar, ref ILVariable containerVar, ILVariable startOffsetVar)
{
endIndexKind = default;
endIndexLoad = default;
if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub) {
if (bni.CheckForOverflow || bni.ResultType != StackType.I4 || bni.IsLifted)
return false;
if (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;
}
}
}
}

183
ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs

@ -59,6 +59,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// nullable type, used by reference (comparison is 'call get_HasValue(ldloc(testedVar))') /// nullable type, used by reference (comparison is 'call get_HasValue(ldloc(testedVar))')
/// </summary> /// </summary>
NullableByReference, NullableByReference,
/// <summary>
/// unconstrained generic type (see the pattern described in TransformNullPropagationOnUnconstrainedGenericExpression)
/// </summary>
UnconstrainedType,
} }
/// <summary> /// <summary>
@ -142,9 +146,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary> /// </summary>
internal void RunStatements(Block block, int pos) internal void RunStatements(Block block, int pos)
{ {
var ifInst = block.Instructions[pos] as IfInstruction; if (block.Instructions[pos] is IfInstruction ifInst && ifInst.FalseInst.MatchNop()) {
if (ifInst == null || !ifInst.FalseInst.MatchNop())
return;
if (ifInst.Condition is Comp comp && comp.Kind == ComparisonKind.Inequality if (ifInst.Condition is Comp comp && comp.Kind == ComparisonKind.Inequality
&& comp.Left.MatchLdLoc(out var testedVar) && comp.Right.MatchLdNull()) { && comp.Left.MatchLdLoc(out var testedVar) && comp.Right.MatchLdNull()) {
TryNullPropForVoidCall(testedVar, Mode.ReferenceType, ifInst.TrueInst as Block, ifInst); TryNullPropForVoidCall(testedVar, Mode.ReferenceType, ifInst.TrueInst as Block, ifInst);
@ -156,6 +158,36 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
} }
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);
}
}
void TryNullPropForVoidCall(ILVariable testedVar, Mode mode, Block body, IfInstruction ifInst) void TryNullPropForVoidCall(ILVariable testedVar, Mode mode, Block body, IfInstruction ifInst)
{ {
@ -261,6 +293,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case Mode.NullableByReference: case Mode.NullableByReference:
return NullableLiftingTransform.MatchGetValueOrDefault(inst, out ILInstruction arg) return NullableLiftingTransform.MatchGetValueOrDefault(inst, out ILInstruction arg)
&& arg.MatchLdLoc(testedVar); && arg.MatchLdLoc(testedVar);
case Mode.UnconstrainedType:
// unconstrained generic type (expect: ldloc(testedVar))
return inst.MatchLdLoc(testedVar);
default: default:
throw new ArgumentOutOfRangeException(nameof(mode)); throw new ArgumentOutOfRangeException(nameof(mode));
} }
@ -288,6 +323,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
ILInstruction replacement; ILInstruction replacement;
switch (mode) { switch (mode) {
case Mode.ReferenceType: case Mode.ReferenceType:
case Mode.UnconstrainedType:
// Wrap varLoad in nullable.unwrap: // Wrap varLoad in nullable.unwrap:
replacement = new NullableUnwrap(varLoad.ResultType, varLoad, refInput: varLoad.ResultType == StackType.Ref); replacement = new NullableUnwrap(varLoad.ResultType, varLoad, refInput: varLoad.ResultType == StackType.Ref);
break; break;
@ -310,6 +346,147 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
oldParentChildren[oldChildIndex] = replacement; 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 class NullPropagationStatementTransform : IStatementTransform

15
ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs

@ -114,7 +114,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
value = ldFlda.Target; value = ldFlda.Target;
} }
if (value.OpCode != OpCode.LdLoca) { 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; return AddressUse.Unknown;
} }
foreach (var load in stloc.Variable.LoadInstructions) { foreach (var load in stloc.Variable.LoadInstructions) {
@ -132,15 +133,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// Address is passed to method. // Address is passed to method.
// We'll assume the method only uses the address locally, // We'll assume the method only uses the address locally,
// unless we can see an address being returned from the method: // unless we can see an address being returned from the method:
if (call is NewObj) { IType returnType = (call is NewObj) ? call.Method.DeclaringType : call.Method.ReturnType;
if (call.Method.DeclaringType.IsByRefLike) { 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; return AddressUse.Unknown;
} }
} else {
if (call.Method.ReturnType.IsByRefLike) {
return AddressUse.Unknown;
}
}
foreach (var p in call.Method.Parameters) { foreach (var p in call.Method.Parameters) {
// catch "out Span<int>" and similar // catch "out Span<int>" and similar
if (p.Type.SkipModifiers() is ByReferenceType brt && brt.ElementType.IsByRefLike) if (p.Type.SkipModifiers() is ByReferenceType brt && brt.ElementType.IsByRefLike)

137
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
{
/// <summary>
/// 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.
/// </summary>
class SyntheticRangeIndexAccessor : IMethod
{
/// <summary>
/// The underlying method: `get_Item(int)`, `set_Item(int, T)` or `Slice(int, int)`.
/// </summary>
readonly IMethod underlyingMethod;
readonly IType indexOrRangeType;
readonly IReadOnlyList<IParameter> 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<IParameter>();
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<ITypeParameter> IMethod.TypeParameters => EmptyList<ITypeParameter>.Instance;
IReadOnlyList<IType> IMethod.TypeArguments => EmptyList<IType>.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<IParameter> IParameterizedMember.Parameters => parameters;
IMember IMember.MemberDefinition => underlyingMethod.MemberDefinition;
IType IMember.ReturnType => underlyingMethod.ReturnType;
IEnumerable<IMember> IMember.ExplicitlyImplementedInterfaceMembers => EmptyList<IMember>.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<IAttribute> IEntity.GetAttributes() => underlyingMethod.GetAttributes();
IEnumerable<IAttribute> 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);
}
}
}

8
ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs

@ -147,6 +147,10 @@ namespace ICSharpCode.Decompiler.TypeSystem
IAsyncEnumerableOfT, IAsyncEnumerableOfT,
/// <summary><c>System.Collections.Generic.IAsyncEnumerator{T}</c></summary> /// <summary><c>System.Collections.Generic.IAsyncEnumerator{T}</c></summary>
IAsyncEnumeratorOfT, IAsyncEnumeratorOfT,
/// <summary><c>System.Index</c></summary>
Index,
/// <summary><c>System.Range</c></summary>
Range
} }
/// <summary> /// <summary>
@ -155,7 +159,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
[Serializable] [Serializable]
public sealed class KnownTypeReference : ITypeReference 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] { static readonly KnownTypeReference[] knownTypeReferences = new KnownTypeReference[KnownTypeCodeCount] {
null, // None null, // None
@ -218,6 +222,8 @@ namespace ICSharpCode.Decompiler.TypeSystem
new KnownTypeReference(KnownTypeCode.Unsafe, TypeKind.Class, "System.Runtime.CompilerServices", "Unsafe", 0), 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.IAsyncEnumerableOfT, TypeKind.Interface, "System.Collections.Generic", "IAsyncEnumerable", 1),
new KnownTypeReference(KnownTypeCode.IAsyncEnumeratorOfT, TypeKind.Interface, "System.Collections.Generic", "IAsyncEnumerator", 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),
}; };
/// <summary> /// <summary>

2
ILSpy.AddIn/ILSpy.AddIn.csproj

@ -123,6 +123,8 @@
<AdditionalDependencies Include="$(ILSpyBuildPath)Iced.dll" /> <AdditionalDependencies Include="$(ILSpyBuildPath)Iced.dll" />
<AdditionalDependencies Include="$(ILSpyBuildPath)ILCompiler.Reflection.ReadyToRun.dll" /> <AdditionalDependencies Include="$(ILSpyBuildPath)ILCompiler.Reflection.ReadyToRun.dll" />
<AdditionalDependencies Include="$(ILSpyBuildPath)ILSpy.ReadyToRun.Plugin.dll" /> <AdditionalDependencies Include="$(ILSpyBuildPath)ILSpy.ReadyToRun.Plugin.dll" />
<AdditionalDependencies Include="$(ILSpyBuildPath)Microsoft.NET.HostModel.dll" />
<AdditionalDependencies Include="$(ILSpyBuildPath)Ookii.Dialogs.Wpf.dll" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

2
ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj

@ -57,7 +57,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Iced" Version="1.6.0" /> <PackageReference Include="Iced" Version="1.6.0" />
<PackageReference Include="ILCompiler.Reflection.ReadyToRun" Version="1.0.7-alpha" /> <PackageReference Include="ILCompiler.Reflection.ReadyToRun" Version="1.0.8-alpha" />
</ItemGroup> </ItemGroup>
<Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" /> <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" />

55
ILSpy.ReadyToRun/ReadyToRunLanguage.cs

@ -22,13 +22,18 @@ using System.ComponentModel.Composition;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection.Metadata; using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Iced.Intel; using Iced.Intel;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Solution; using ICSharpCode.Decompiler.Solution;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpy.TextView;
using ILCompiler.Reflection.ReadyToRun; using ILCompiler.Reflection.ReadyToRun;
namespace ICSharpCode.ILSpy.ReadyToRun namespace ICSharpCode.ILSpy.ReadyToRun
@ -81,10 +86,12 @@ namespace ICSharpCode.ILSpy.ReadyToRun
.GroupBy(m => m.MethodHandle) .GroupBy(m => m.MethodHandle)
.ToDictionary(g => g.Key, g => g.ToArray()); .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)) { if (cacheEntry.methodMap.TryGetValue(method.MetadataToken, out var methods)) {
foreach (var readyToRunMethod in methods) { foreach (var readyToRunMethod in methods) {
foreach (RuntimeFunction runtimeFunction in readyToRunMethod.RuntimeFunctions) { 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); 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); WriteCommentLine(output, readyToRunMethod.SignatureString);
byte[] codeBytes = new byte[runtimeFunction.Size]; byte[] codeBytes = new byte[runtimeFunction.Size];
@ -128,6 +135,7 @@ namespace ICSharpCode.ILSpy.ReadyToRun
var tempOutput = new StringOutput(); var tempOutput = new StringOutput();
foreach (var instr in instructions) { foreach (var instr in instructions) {
int byteBaseIndex = (int)(instr.IP - address); int byteBaseIndex = (int)(instr.IP - address);
if (runtimeFunction.DebugInfo != null) {
foreach (var bound in runtimeFunction.DebugInfo.BoundsList) { foreach (var bound in runtimeFunction.DebugInfo.BoundsList) {
if (bound.NativeOffset == byteBaseIndex) { if (bound.NativeOffset == byteBaseIndex) {
if (bound.ILOffset == (uint)DebugInfoBoundsType.Prolog) { if (bound.ILOffset == (uint)DebugInfoBoundsType.Prolog) {
@ -139,6 +147,7 @@ namespace ICSharpCode.ILSpy.ReadyToRun
} }
} }
} }
}
formatter.Format(instr, tempOutput); formatter.Format(instr, tempOutput);
output.Write(instr.IP.ToString("X16")); output.Write(instr.IP.ToString("X16"));
output.Write(" "); output.Write(" ");
@ -149,9 +158,49 @@ namespace ICSharpCode.ILSpy.ReadyToRun
for (int i = 0; i < missingBytes; i++) for (int i = 0; i < missingBytes; i++)
output.Write(" "); output.Write(" ");
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(); 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) private ReadyToRunReaderCacheEntry GetReader(LoadedAssembly assembly, PEFile module)

2
ILSpy/Commands/DecompileInNewViewCommand.cs

@ -28,7 +28,7 @@ using ICSharpCode.ILSpy.TreeNodes;
namespace ICSharpCode.ILSpy.Commands 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 internal sealed class DecompileInNewViewCommand : IContextMenuEntry
{ {
public bool IsVisible(TextViewContext context) public bool IsVisible(TextViewContext context)

6
ILSpy/DecompilationOptions.cs

@ -38,6 +38,12 @@ namespace ICSharpCode.ILSpy
/// </summary> /// </summary>
public string SaveAsProjectDirectory { get; set; } public string SaveAsProjectDirectory { get; set; }
/// <summary>
/// Gets/sets whether invalid identifiers should be escaped (and therefore the code be made compilable).
/// This setting is ignored in case <see cref="SaveAsProjectDirectory"/> is set.
/// </summary>
public bool EscapeInvalidIdentifiers { get; set; }
/// <summary> /// <summary>
/// Gets the cancellation token that is used to abort the decompiler. /// Gets the cancellation token that is used to abort the decompiler.
/// </summary> /// </summary>

2
ILSpy/ILSpy.csproj

@ -56,6 +56,8 @@
<PackageReference Include="Mono.Cecil" Version="0.10.3" /> <PackageReference Include="Mono.Cecil" Version="0.10.3" />
<PackageReference Include="OSVersionHelper" Version="1.0.11" /> <PackageReference Include="OSVersionHelper" Version="1.0.11" />
<PackageReference Include="DataGridExtensions" Version="2.1.1" /> <PackageReference Include="DataGridExtensions" Version="2.1.1" />
<PackageReference Include="Microsoft.NET.HostModel" Version="3.1.4" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="1.1.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

7
ILSpy/Languages/CSharpILMixedLanguage.cs

@ -52,7 +52,12 @@ namespace ICSharpCode.ILSpy
DetectControlStructure = detectControlStructure, DetectControlStructure = detectControlStructure,
ShowSequencePoints = options.DecompilerSettings.ShowDebugInfo 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) static CSharpDecompiler CreateDecompiler(PEFile module, DecompilationOptions options)

6
ILSpy/Languages/CSharpLanguage.cs

@ -119,6 +119,9 @@ namespace ICSharpCode.ILSpy
decompiler.DebugInfoProvider = module.GetDebugInfoOrNull(); decompiler.DebugInfoProvider = module.GetDebugInfoOrNull();
while (decompiler.AstTransforms.Count > transformCount) while (decompiler.AstTransforms.Count > transformCount)
decompiler.AstTransforms.RemoveAt(decompiler.AstTransforms.Count - 1); decompiler.AstTransforms.RemoveAt(decompiler.AstTransforms.Count - 1);
if (options.EscapeInvalidIdentifiers) {
decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers());
}
return decompiler; return decompiler;
} }
@ -413,6 +416,9 @@ namespace ICSharpCode.ILSpy
CSharpDecompiler decompiler = new CSharpDecompiler(typeSystem, options.DecompilerSettings); CSharpDecompiler decompiler = new CSharpDecompiler(typeSystem, options.DecompilerSettings);
decompiler.CancellationToken = options.CancellationToken; decompiler.CancellationToken = options.CancellationToken;
if (options.EscapeInvalidIdentifiers) {
decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers());
}
SyntaxTree st; SyntaxTree st;
if (options.FullDecompilation) { if (options.FullDecompilation) {
st = decompiler.DecompileWholeModuleAsSingleFile(); st = decompiler.DecompileWholeModuleAsSingleFile();

19
ILSpy/Languages/CSharpLexer.cs

@ -1,5 +1,20 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) //
// 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;
using System.Collections.Generic; using System.Collections.Generic;

38
ILSpy/Languages/ILLanguage.cs

@ -20,15 +20,14 @@ using System.Collections.Generic;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Disassembler; using ICSharpCode.Decompiler.Disassembler;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Reflection.PortableExecutable;
using System.Reflection.Metadata; using System.Reflection.Metadata;
using System.IO;
using System.Reflection.Metadata.Ecma335;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util; using ICSharpCode.Decompiler.Util;
using ICSharpCode.Decompiler.Solution; using ICSharpCode.Decompiler.Solution;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.ILSpy.TextView;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
@ -178,5 +177,38 @@ namespace ICSharpCode.ILSpy
} }
return null; 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();
}
} }
} }

78
ILSpy/MainWindow.xaml.cs

@ -35,6 +35,7 @@ using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Navigation; using System.Windows.Navigation;
using System.Windows.Threading; using System.Windows.Threading;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Documentation; using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Metadata;
@ -46,7 +47,13 @@ using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.TreeView; using ICSharpCode.TreeView;
using Microsoft.NET.HostModel.AppHost;
using Microsoft.NET.HostModel.Bundle;
using Microsoft.Win32; using Microsoft.Win32;
using Ookii.Dialogs.Wpf;
using OSVersionHelper; using OSVersionHelper;
using Xceed.Wpf.AvalonDock.Layout.Serialization; using Xceed.Wpf.AvalonDock.Layout.Serialization;
@ -868,7 +875,12 @@ namespace ICSharpCode.ILSpy
public void JumpToReference(object reference) public void JumpToReference(object reference)
{ {
JumpToReferenceAsync(reference).HandleExceptions(); JumpToReference(reference, inNewTabPage: false);
}
public void JumpToReference(object reference, bool inNewTabPage)
{
JumpToReferenceAsync(reference, inNewTabPage).HandleExceptions();
} }
/// <summary> /// <summary>
@ -879,6 +891,11 @@ namespace ICSharpCode.ILSpy
/// The task will be marked as canceled if the decompilation is canceled. /// The task will be marked as canceled if the decompilation is canceled.
/// </returns> /// </returns>
public Task JumpToReferenceAsync(object reference) public Task JumpToReferenceAsync(object reference)
{
return JumpToReferenceAsync(reference, inNewTabPage: false);
}
public Task JumpToReferenceAsync(object reference, bool inNewTabPage)
{ {
decompilationTask = TaskHelper.CompletedTask; decompilationTask = TaskHelper.CompletedTask;
switch (reference) { switch (reference) {
@ -893,21 +910,22 @@ namespace ICSharpCode.ILSpy
foreach (var handler in protocolHandlers) { foreach (var handler in protocolHandlers) {
var node = handler.Value.Resolve(protocol, file, unresolvedEntity.Handle, out bool newTabPage); var node = handler.Value.Resolve(protocol, file, unresolvedEntity.Handle, out bool newTabPage);
if (node != null) { if (node != null) {
SelectNode(node, newTabPage); SelectNode(node, newTabPage || inNewTabPage);
return decompilationTask; 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); 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; goto default;
} }
break; break;
default: default:
ILSpyTreeNode treeNode = FindTreeNode(reference); ILSpyTreeNode treeNode = FindTreeNode(reference);
if (treeNode != null) if (treeNode != null)
SelectNode(treeNode); SelectNode(treeNode, inNewTabPage);
break; break;
} }
return decompilationTask; return decompilationTask;
@ -994,6 +1012,45 @@ namespace ICSharpCode.ILSpy
} }
break; break;
default: default:
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<AvalonEditTextOutput>.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); var asm = assemblyList.OpenAssembly(file);
if (asm != null) { if (asm != null) {
if (loadedAssemblies != null) if (loadedAssemblies != null)
@ -1006,12 +1063,23 @@ namespace ICSharpCode.ILSpy
} }
} }
} }
}
break; break;
} }
if (lastNode != null && focusNode) if (lastNode != null && focusNode)
AssemblyTreeView.FocusNode(lastNode); 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) void RefreshCommandExecuted(object sender, ExecutedRoutedEventArgs e)

2
ILSpy/Properties/AssemblyInfo.template.cs

@ -40,7 +40,7 @@ internal static class RevisionClass
public const string Minor = "0"; public const string Minor = "0";
public const string Build = "0"; public const string Build = "0";
public const string Revision = "$INSERTREVISION$"; 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$"; public const string FullVersion = Major + "." + Minor + "." + Build + ".$INSERTREVISION$$INSERTBRANCHPOSTFIX$$INSERTVERSIONNAMEPOSTFIX$";
} }

33
ILSpy/Properties/Resources.Designer.cs generated

@ -947,6 +947,15 @@ namespace ICSharpCode.ILSpy.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Ranges.
/// </summary>
public static string DecompilerSettings_Ranges {
get {
return ResourceManager.GetString("DecompilerSettings.Ranges", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Read-only methods. /// Looks up a localized string similar to Read-only methods.
/// </summary> /// </summary>
@ -1533,6 +1542,15 @@ namespace ICSharpCode.ILSpy.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to New Tab.
/// </summary>
public static string NewTab {
get {
return ResourceManager.GetString("NewTab", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Nuget Package Browser. /// Looks up a localized string similar to Nuget Package Browser.
/// </summary> /// </summary>
@ -1605,6 +1623,21 @@ namespace ICSharpCode.ILSpy.Properties {
} }
} }
/// <summary>
/// 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?.
/// </summary>
public static string OpenSelfContainedExecutableMessage {
get {
return ResourceManager.GetString("OpenSelfContainedExecutableMessage", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Options. /// Looks up a localized string similar to Options.
/// </summary> /// </summary>

15
ILSpy/Properties/Resources.resx

@ -852,6 +852,9 @@ Do you want to continue?</value>
Do you want to continue?</value> Do you want to continue?</value>
</data> </data>
<data name="DecompilerSettings.Ranges" xml:space="preserve">
<value>Ranges</value>
</data>
<data name="AddPreconfiguredList" xml:space="preserve"> <data name="AddPreconfiguredList" xml:space="preserve">
<value>Add preconfigured list...</value> <value>Add preconfigured list...</value>
</data> </data>
@ -867,4 +870,16 @@ Do you want to continue?</value>
<data name="DecompilerSettings.AggressiveScalarReplacementOfAggregates" xml:space="preserve"> <data name="DecompilerSettings.AggressiveScalarReplacementOfAggregates" xml:space="preserve">
<value>Aggressively perform Scalar Replacement Of Aggregates (SROA)</value> <value>Aggressively perform Scalar Replacement Of Aggregates (SROA)</value>
</data> </data>
<data name="OpenSelfContainedExecutableMessage" xml:space="preserve">
<value>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?</value>
</data>
<data name="NewTab" xml:space="preserve">
<value>New Tab</value>
</data>
</root> </root>

14
ILSpy/TextView/AvalonEditTextOutput.cs

@ -82,7 +82,9 @@ namespace ICSharpCode.ILSpy.TextView
public string IndentationString { get; set; } = "\t"; public string IndentationString { get; set; } = "\t";
public string Title { get; set; } internal bool IgnoreNewLineAndIndent { get; set; }
public string Title { get; set; } = Properties.Resources.NewTab;
/// <summary> /// <summary>
/// Gets/sets the <see cref="Uri"/> that is displayed by this view. /// Gets/sets the <see cref="Uri"/> that is displayed by this view.
@ -177,16 +179,22 @@ namespace ICSharpCode.ILSpy.TextView
public void Indent() public void Indent()
{ {
if (IgnoreNewLineAndIndent)
return;
indent++; indent++;
} }
public void Unindent() public void Unindent()
{ {
if (IgnoreNewLineAndIndent)
return;
indent--; indent--;
} }
void WriteIndent() void WriteIndent()
{ {
if (IgnoreNewLineAndIndent)
return;
Debug.Assert(textDocument == null); Debug.Assert(textDocument == null);
if (needsIndent) { if (needsIndent) {
needsIndent = false; needsIndent = false;
@ -211,10 +219,14 @@ namespace ICSharpCode.ILSpy.TextView
public void WriteLine() public void WriteLine()
{ {
Debug.Assert(textDocument == null); Debug.Assert(textDocument == null);
if (IgnoreNewLineAndIndent) {
b.Append(' ');
} else {
b.AppendLine(); b.AppendLine();
needsIndent = true; needsIndent = true;
lastLineStart = b.Length; lastLineStart = b.Length;
lineNumber++; lineNumber++;
}
if (this.TextLength > LengthLimit) { if (this.TextLength > LengthLimit) {
throw new OutputLengthExceededException(); throw new OutputLengthExceededException();
} }

19
ILSpy/TextView/BracketHighlightRenderer.cs

@ -1,5 +1,20 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) //
// 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;
using System.Windows.Media; using System.Windows.Media;

11
ILSpy/TextView/DecompilerTextView.cs

@ -116,7 +116,7 @@ namespace ICSharpCode.ILSpy.TextView
InitializeComponent(); InitializeComponent();
this.referenceElementGenerator = new ReferenceElementGenerator(this.JumpToReference, this.IsLink); this.referenceElementGenerator = new ReferenceElementGenerator(this.IsLink);
textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator); textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator);
this.uiElementGenerator = new UIElementGenerator(); this.uiElementGenerator = new UIElementGenerator();
this.bracketHighlightRenderer = new BracketHighlightRenderer(textEditor.TextArea.TextView); this.bracketHighlightRenderer = new BracketHighlightRenderer(textEditor.TextArea.TextView);
@ -820,7 +820,7 @@ namespace ICSharpCode.ILSpy.TextView
/// <summary> /// <summary>
/// Jumps to the definition referred to by the <see cref="ReferenceSegment"/>. /// Jumps to the definition referred to by the <see cref="ReferenceSegment"/>.
/// </summary> /// </summary>
internal void JumpToReference(ReferenceSegment referenceSegment) internal void JumpToReference(ReferenceSegment referenceSegment, bool openInNewTab)
{ {
object reference = referenceSegment.Reference; object reference = referenceSegment.Reference;
if (referenceSegment.IsLocal) { if (referenceSegment.IsLocal) {
@ -849,7 +849,7 @@ namespace ICSharpCode.ILSpy.TextView
return; return;
} }
} }
MainWindow.Instance.JumpToReference(reference); MainWindow.Instance.JumpToReference(reference, openInNewTab);
} }
Point? mouseDownPos; Point? mouseDownPos;
@ -866,7 +866,7 @@ namespace ICSharpCode.ILSpy.TextView
Vector dragDistance = e.GetPosition(this) - mouseDownPos.Value; Vector dragDistance = e.GetPosition(this) - mouseDownPos.Value;
if (Math.Abs(dragDistance.X) < SystemParameters.MinimumHorizontalDragDistance if (Math.Abs(dragDistance.X) < SystemParameters.MinimumHorizontalDragDistance
&& Math.Abs(dragDistance.Y) < SystemParameters.MinimumVerticalDragDistance && Math.Abs(dragDistance.Y) < SystemParameters.MinimumVerticalDragDistance
&& e.ChangedButton == MouseButton.Left) && (e.ChangedButton == MouseButton.Left || e.ChangedButton == MouseButton.Middle))
{ {
// click without moving mouse // click without moving mouse
var referenceSegment = GetReferenceSegmentAtMousePosition(); var referenceSegment = GetReferenceSegmentAtMousePosition();
@ -878,7 +878,7 @@ namespace ICSharpCode.ILSpy.TextView
// cursor position and the mouse position. // cursor position and the mouse position.
textEditor.TextArea.MouseSelectionMode = MouseSelectionMode.None; 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( Thread thread = new Thread(new ThreadStart(
delegate { delegate {
try { try {
context.Options.EscapeInvalidIdentifiers = true;
Stopwatch stopwatch = new Stopwatch(); Stopwatch stopwatch = new Stopwatch();
stopwatch.Start(); stopwatch.Start();
using (StreamWriter w = new StreamWriter(fileName)) { using (StreamWriter w = new StreamWriter(fileName)) {

11
ILSpy/TextView/ReferenceElementGenerator.cs

@ -28,7 +28,6 @@ namespace ICSharpCode.ILSpy.TextView
/// </summary> /// </summary>
sealed class ReferenceElementGenerator : VisualLineElementGenerator sealed class ReferenceElementGenerator : VisualLineElementGenerator
{ {
readonly Action<ReferenceSegment> referenceClicked;
readonly Predicate<ReferenceSegment> isLink; readonly Predicate<ReferenceSegment> isLink;
/// <summary> /// <summary>
@ -36,13 +35,10 @@ namespace ICSharpCode.ILSpy.TextView
/// </summary> /// </summary>
public TextSegmentCollection<ReferenceSegment> References { get; set; } public TextSegmentCollection<ReferenceSegment> References { get; set; }
public ReferenceElementGenerator(Action<ReferenceSegment> referenceClicked, Predicate<ReferenceSegment> isLink) public ReferenceElementGenerator(Predicate<ReferenceSegment> isLink)
{ {
if (referenceClicked == null)
throw new ArgumentNullException(nameof(referenceClicked));
if (isLink == null) if (isLink == null)
throw new ArgumentNullException(nameof(isLink)); throw new ArgumentNullException(nameof(isLink));
this.referenceClicked = referenceClicked;
this.isLink = isLink; this.isLink = isLink;
} }
@ -72,11 +68,6 @@ namespace ICSharpCode.ILSpy.TextView
} }
return null; return null;
} }
internal void JumpToReference(ReferenceSegment referenceSegment)
{
referenceClicked(referenceSegment);
}
} }
/// <summary> /// <summary>

9
ILSpy/TreeNodes/ILSpyTreeNode.cs

@ -16,9 +16,12 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Windows;
using System.Windows.Threading;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using ICSharpCode.TreeView; using ICSharpCode.TreeView;
@ -69,6 +72,12 @@ namespace ICSharpCode.ILSpy.TreeNodes
return false; return false;
} }
public override void ActivateItemSecondary(RoutedEventArgs e)
{
MainWindow.Instance.SelectNode(this, inNewTabPage: true);
MainWindow.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)MainWindow.Instance.RefreshDecompiledView);
}
/// <summary> /// <summary>
/// Used to implement special save logic for some items. /// Used to implement special save logic for some items.
/// This method is called on the main thread when only a single item is selected. /// This method is called on the main thread when only a single item is selected.

2
ILSpy/ViewModels/DebugStepsPaneModel.cs

@ -20,7 +20,9 @@ using System.Windows;
namespace ICSharpCode.ILSpy.ViewModels namespace ICSharpCode.ILSpy.ViewModels
{ {
#if DEBUG
[ExportToolPane(ContentId = PaneContentId)] [ExportToolPane(ContentId = PaneContentId)]
#endif
public class DebugStepsPaneModel : ToolPaneModel public class DebugStepsPaneModel : ToolPaneModel
{ {
public const string PaneContentId = "debugStepsPane"; public const string PaneContentId = "debugStepsPane";

5
ILSpy/ViewModels/TabPageModel.cs

@ -28,6 +28,11 @@ namespace ICSharpCode.ILSpy.ViewModels
{ {
private readonly Dictionary<Language, LanguageVersion> languageVersionHistory = new Dictionary<Language, LanguageVersion>(); private readonly Dictionary<Language, LanguageVersion> languageVersionHistory = new Dictionary<Language, LanguageVersion>();
public TabPageModel()
{
this.Title = Properties.Resources.NewTab;
}
private Language language; private Language language;
public Language Language { public Language Language {
get => language; get => language;

8
SharpTreeView/SharpTreeNode.cs

@ -24,6 +24,7 @@ using System.Windows;
using System.ComponentModel; using System.ComponentModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Input;
namespace ICSharpCode.TreeView namespace ICSharpCode.TreeView
{ {
@ -681,6 +682,13 @@ namespace ICSharpCode.TreeView
{ {
} }
/// <summary>
/// Gets called when the item is clicked with the middle mouse button.
/// </summary>
public virtual void ActivateItemSecondary(RoutedEventArgs e)
{
}
public override string ToString() public override string ToString()
{ {
// used for keyboard navigation // used for keyboard navigation

9
SharpTreeView/SharpTreeViewItem.cs

@ -119,6 +119,15 @@ namespace ICSharpCode.TreeView
} }
} }
protected override void OnMouseUp(MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Middle) {
Node.ActivateItemSecondary(e);
} else {
base.OnMouseUp(e);
}
}
#endregion #endregion
#region Drag and Drop #region Drag and Drop

Loading…
Cancel
Save