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