From 010abebcc979aea24f6991cd63cd8976b864085c Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 7 Mar 2020 23:00:19 +0100 Subject: [PATCH 01/44] Fix #1050: Implement TransformNullPropagationOnUnconstrainedGenericExpression --- .../IL/Transforms/NullPropagationTransform.cs | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs index 37f8ffcd3..d019f9b28 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs @@ -319,6 +319,152 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!context.Settings.NullPropagation) return; new NullPropagationTransform(context).RunStatements(block, pos); + if (TransformNullPropagationOnUnconstrainedGenericExpression(block, pos, out var target, out var value, out var rewrapPoint, out var endBlock)) { + context.Step("TransformNullPropagationOnUnconstrainedGenericExpression", block); + // A successful match was found: + // 1. The 'target' instruction, that is, the instruction where the actual 'null-propagating' call happens: + // .Call() is replaced with ?.Call() + // 2. The 'value' instruction, that is, the instruction that produces the value: + // It is inlined at the location of 'target'. + // 3. The 'rewrapPoint' instruction is an ancestor of the call that is used as location for the NullableRewrap instruction, + // if the expression does not yet contain a NullableRewrap. + + // First try to find a NullableRewrap instruction + bool needsRewrap = true; + var tmp = target; + while (needsRewrap && tmp != null) { + if (tmp is NullableRewrap) { + needsRewrap = false; + break; + } + tmp = tmp.Parent; + } + // Remove the fallback conditions and blocks + block.Instructions.RemoveRange(pos, 3); + // inline value and wrap it in a NullableUnwrap instruction to produce 'value?'. + target.ReplaceWith(new NullableUnwrap(value.ResultType, value, refInput: true)); + // if the ancestors do not yet have a NullableRewrap instruction + // insert it at the 'rewrapPoint'. + if (needsRewrap) { + var siblings = rewrapPoint.Parent.Children; + int index = rewrapPoint.ChildIndex; + siblings[index] = new NullableRewrap(rewrapPoint); + } + // 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 + 1); + endBlock.Remove(); + } + } + } + + // stloc valueTemporary(valueExpression) + // stloc defaultTemporary(default.value type) + // if (logic.not(comp.o(box `0(ldloc defaultTemporary) != ldnull))) Block fallbackBlock { + // stloc defaultTemporary(ldobj type(ldloc valueTemporary)) + // stloc valueTemporary(ldloca defaultTemporary) + // if (comp.o(ldloc defaultTemporary == ldnull)) Block fallbackBlock2 { + // stloc resultTemporary(ldnull) + // br endBlock + // } + // } + // stloc resultTemporary(constrained[type].call_instruction(ldloc valueTemporary, ...)) + // br endBlock + // => + // stloc resultTemporary(nullable.rewrap(constrained[type].call_instruction(nullable.unwrap(valueExpression), ...))) + private bool TransformNullPropagationOnUnconstrainedGenericExpression(Block block, int pos, + out ILInstruction target, out ILInstruction value, out ILInstruction rewrapPoint, out Block endBlock) + { + target = null; + value = null; + rewrapPoint = null; + endBlock = null; + if (pos + 4 >= block.Instructions.Count) + return false; + // stloc valueTemporary(valueExpression) + if (!(block.Instructions[pos].MatchStLoc(out var valueTemporary, out value))) + return false; + if (!(valueTemporary.Kind == VariableKind.StackSlot && valueTemporary.LoadCount == 2 && valueTemporary.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; + // Some variables are reused by the compiler therefore we cannot check for absolute numbers. See for example ValueTuple`8.ToString + // LoadCount must be == StoreCount and LoadCount must be 2x AddressCount. + // Note: if successful, this transform removes exactly 2 store, 2 load and 1 addressof instruction. + if (!(defaultTemporary.Kind == VariableKind.Local && defaultTemporary.LoadCount == defaultTemporary.StoreCount && defaultTemporary.AddressCount * 2 == defaultTemporary.StoreCount)) + 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; + // stloc resultTemporary(constrained[type].call_instruction(ldloc valueTemporary, ...)) + if (!(block.Instructions[pos + 3].MatchStLoc(out var resultTemporary, out rewrapPoint))) + return false; + var loadInCall = FindLoadInExpression(valueTemporary, rewrapPoint); + if (!(loadInCall != null && loadInCall.Ancestors.OfType().FirstOrDefault() is CallInstruction call)) + return false; + if (!(call.Arguments.Count > 0 && loadInCall.IsDescendantOf(call.Arguments[0]))) + return false; + // br IL_0035 + if (!(block.Instructions[pos + 4].MatchBranch(out endBlock))) + return false; + // Analyze Block fallbackBlock + if (!(fallbackBlock1 is Block b && IsFallbackBlock(b, type, valueTemporary, defaultTemporary, resultTemporary, endBlock))) + return false; + target = call.Arguments[0]; + return true; + + ILInstruction FindLoadInExpression(ILVariable variable, ILInstruction expression) + { + foreach (var load in variable.LoadInstructions) { + if (load.IsDescendantOf(expression)) + return load; + } + return null; + } + } + + // Block fallbackBlock { + // stloc defaultTemporary(ldobj type(ldloc valueTemporary)) + // stloc valueTemporary(ldloca defaultTemporary) + // if (comp.o(ldloc defaultTemporary == ldnull)) Block fallbackBlock { + // stloc resultTemporary(ldnull) + // br endBlock + // } + // } + private bool IsFallbackBlock(Block block, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILVariable resultTemporary, Block endBlock) + { + if (!(block.Instructions.Count == 3)) + return false; + // stloc defaultTemporary(ldobj type(ldloc valueTemporary)) + if (!(block.Instructions[0].MatchStLoc(defaultTemporary, out var valueExpression))) + return false; + if (!(valueExpression.MatchLdObj(out var target, out var t) && type.Equals(t) && target.MatchLdLoc(valueTemporary))) + return false; + // stloc valueTemporary(ldloca defaultTemporary) + if (!(block.Instructions[1].MatchStLoc(valueTemporary, 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) && tmp is Block fallbackBlock && condition.MatchCompEqualsNull(out var arg) && arg.MatchLdLoc(defaultTemporary))) + return false; + // Block fallbackBlock { + // stloc resultTemporary(ldnull) + // br endBlock + // } + if (!(fallbackBlock.Instructions.Count == 2)) + return false; + if (!(fallbackBlock.Instructions[0].MatchStLoc(resultTemporary, out var defaultValue) && MatchDefaultValueOrLdNull(defaultValue))) + return false; + if (!fallbackBlock.Instructions[1].MatchBranch(endBlock)) + return false; + return true; + } + + private bool MatchDefaultValueOrLdNull(ILInstruction inst) + { + return inst.MatchLdNull() || inst.MatchDefaultValue(out _); } } } From a7d1d8fad7756ecad7eb5a87ba4eac8ecaccb002 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 14 Mar 2020 21:27:52 +0100 Subject: [PATCH 02/44] TransformNullPropagationOnUnconstrainedGenericExpression: handle pattern that uses leave instructions instead of stloc into a temporary. --- .../TestCases/Pretty/NullPropagation.cs | 21 +++- .../IL/Transforms/NullPropagationTransform.cs | 110 ++++++++++++++---- 2 files changed, 103 insertions(+), 28 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs index 6d2191833..33c4617de 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs @@ -69,6 +69,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + private struct GenericStruct + { + public T1 Field1; + public T2 Field2; + + public override string ToString() + { + return "(" + Field1?.ToString() + ", " + Field2?.ToString() + ")"; + } + } + public interface ITest { int Int(); @@ -243,12 +254,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/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs index d019f9b28..98fcd8cf1 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs @@ -343,16 +343,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms block.Instructions.RemoveRange(pos, 3); // inline value and wrap it in a NullableUnwrap instruction to produce 'value?'. target.ReplaceWith(new NullableUnwrap(value.ResultType, value, refInput: true)); + + var siblings = rewrapPoint.Parent.Children; + int index = rewrapPoint.ChildIndex; + // remove Nullable-ctor, if necessary + if (NullableLiftingTransform.MatchNullableCtor(rewrapPoint, out var utype, out var arg) && arg.InferType(context.TypeSystem).Equals(utype)) { + rewrapPoint = arg; + } // if the ancestors do not yet have a NullableRewrap instruction // insert it at the 'rewrapPoint'. if (needsRewrap) { - var siblings = rewrapPoint.Parent.Children; - int index = rewrapPoint.ChildIndex; siblings[index] = new NullableRewrap(rewrapPoint); } - // If the endBlock is only reachable through the current block, + // if the endBlock is only reachable through the current block, // combine both blocks. - if (endBlock.IncomingEdgeCount == 1) { + if (endBlock?.IncomingEdgeCount == 1) { block.Instructions.AddRange(endBlock.Instructions); block.Instructions.RemoveAt(pos + 1); endBlock.Remove(); @@ -374,6 +379,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms // br endBlock // => // stloc resultTemporary(nullable.rewrap(constrained[type].call_instruction(nullable.unwrap(valueExpression), ...))) + // + // -or- + // + // stloc valueTemporary(valueExpression) + // stloc defaultTemporary(default.value type) + // if (logic.not(comp.o(box `0(ldloc defaultTemporary) != ldnull))) Block fallbackBlock { + // stloc defaultTemporary(ldobj type(ldloc valueTemporary)) + // stloc valueTemporary(ldloca defaultTemporary) + // if (comp.o(ldloc defaultTemporary == ldnull)) Block fallbackBlock2 { + // leave(ldnull) + // } + // } + // leave (constrained[type].call_instruction(ldloc valueTemporary, ...)) + // => + // leave (nullable.rewrap(constrained[type].call_instruction(nullable.unwrap(valueExpression), ...))) private bool TransformNullPropagationOnUnconstrainedGenericExpression(Block block, int pos, out ILInstruction target, out ILInstruction value, out ILInstruction rewrapPoint, out Block endBlock) { @@ -381,7 +401,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms value = null; rewrapPoint = null; endBlock = null; - if (pos + 4 >= block.Instructions.Count) + if (pos + 3 >= block.Instructions.Count) return false; // stloc valueTemporary(valueExpression) if (!(block.Instructions[pos].MatchStLoc(out var valueTemporary, out value))) @@ -399,13 +419,30 @@ namespace ICSharpCode.Decompiler.IL.Transforms // 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, valueTemporary, defaultTemporary, fallbackBlock1, out rewrapPoint, out var call, out endBlock) && !MatchLeaveResult(block, pos, type, valueTemporary, defaultTemporary, fallbackBlock1, out rewrapPoint, out call)) + return false; + target = call.Arguments[0]; + return true; + } + + // stloc resultTemporary(constrained[type].call_instruction(ldloc valueTemporary, ...)) + // br IL_0035 + private bool MatchStLocResultTemporary(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock1, out ILInstruction rewrapPoint, out CallInstruction call, out Block endBlock) + { + call = null; + endBlock = null; + rewrapPoint = null; + + if (pos + 4 >= block.Instructions.Count) + return false; + // stloc resultTemporary(constrained[type].call_instruction(ldloc valueTemporary, ...)) if (!(block.Instructions[pos + 3].MatchStLoc(out var resultTemporary, out rewrapPoint))) return false; var loadInCall = FindLoadInExpression(valueTemporary, rewrapPoint); - if (!(loadInCall != null && loadInCall.Ancestors.OfType().FirstOrDefault() is CallInstruction call)) + if (!(loadInCall != null && loadInCall.Ancestors.OfType().FirstOrDefault() is CallInstruction c)) return false; - if (!(call.Arguments.Count > 0 && loadInCall.IsDescendantOf(call.Arguments[0]))) + if (!(c.Arguments.Count > 0 && loadInCall.IsDescendantOf(c.Arguments[0]))) return false; // br IL_0035 if (!(block.Instructions[pos + 4].MatchBranch(out endBlock))) @@ -413,17 +450,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms // Analyze Block fallbackBlock if (!(fallbackBlock1 is Block b && IsFallbackBlock(b, type, valueTemporary, defaultTemporary, resultTemporary, endBlock))) return false; - target = call.Arguments[0]; + + call = c; return true; + } - ILInstruction FindLoadInExpression(ILVariable variable, ILInstruction expression) - { - foreach (var load in variable.LoadInstructions) { - if (load.IsDescendantOf(expression)) - return load; - } - return null; + private bool MatchLeaveResult(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock, out ILInstruction rewrapPoint, out CallInstruction call) + { + call = null; + rewrapPoint = null; + + // leave (constrained[type].call_instruction(ldloc valueTemporary, ...)) + if (!(block.Instructions[pos + 3] is Leave leave && leave.IsLeavingFunction)) + return false; + rewrapPoint = leave.Value; + var loadInCall = FindLoadInExpression(valueTemporary, rewrapPoint); + if (!(loadInCall != null && loadInCall.Ancestors.OfType().FirstOrDefault() is CallInstruction c)) + return false; + if (!(c.Arguments.Count > 0 && loadInCall.IsDescendantOf(c.Arguments[0]))) + return false; + // Analyze Block fallbackBlock + if (!(fallbackBlock is Block b && IsFallbackBlock(b, type, valueTemporary, defaultTemporary, null, leave.TargetContainer))) + return false; + + call = c; + return true; + } + + private ILInstruction FindLoadInExpression(ILVariable variable, ILInstruction expression) + { + foreach (var load in variable.LoadInstructions) { + if (load.IsDescendantOf(expression)) + return load; } + return null; } // Block fallbackBlock { @@ -434,7 +494,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms // br endBlock // } // } - private bool IsFallbackBlock(Block block, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILVariable resultTemporary, Block endBlock) + private bool IsFallbackBlock(Block block, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILVariable resultTemporary, ILInstruction endBlockOrLeaveContainer) { if (!(block.Instructions.Count == 3)) return false; @@ -447,18 +507,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!(block.Instructions[1].MatchStLoc(valueTemporary, 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) && tmp is Block fallbackBlock && condition.MatchCompEqualsNull(out var arg) && arg.MatchLdLoc(defaultTemporary))) + 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(ldnull) // br endBlock // } - if (!(fallbackBlock.Instructions.Count == 2)) - return false; - if (!(fallbackBlock.Instructions[0].MatchStLoc(resultTemporary, out var defaultValue) && MatchDefaultValueOrLdNull(defaultValue))) - return false; - if (!fallbackBlock.Instructions[1].MatchBranch(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 var defaultValue) && MatchDefaultValueOrLdNull(defaultValue))) + return false; + if (!fallbackBlock.Instructions[1].MatchBranch(endBlock)) + return false; + } else if (!(fallbackInst is Leave fallbackLeave && endBlockOrLeaveContainer is BlockContainer leaveContainer + && fallbackLeave.TargetContainer == leaveContainer && MatchDefaultValueOrLdNull(fallbackLeave.Value))) return false; + return true; } From c293613a5666bd3b77517028c4bf8eccf76c3a45 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 8 Apr 2020 09:54:41 +0200 Subject: [PATCH 03/44] Add ExceptionSpecifierILRange to TryCatchHandler --- .../IL/Instructions/ILInstruction.cs | 25 +++++++++++-------- .../IL/Instructions/TryInstruction.cs | 12 +++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) 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..4b519af43 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,17 @@ 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. + /// + public Interval ExceptionSpecifierILRange { get; private set; } + + public void AddExceptionSpecifierILRange(Interval newRange) + { + ExceptionSpecifierILRange = CombineILRange(ExceptionSpecifierILRange, newRange); + } } partial class TryFinally From e029266d40c04230931921fc668a499ffbf08bfb Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 8 Apr 2020 09:55:52 +0200 Subject: [PATCH 04/44] Fix tokens used for catch-when clause. --- .../OutputVisitor/CSharpOutputVisitor.cs | 4 +- .../Syntax/Statements/TryCatchStatement.cs | 166 ++++++++++-------- 2 files changed, 90 insertions(+), 80 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index faa67d21c..4eea34baa 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1812,11 +1812,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/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; From ba5c645257515a52211e528206903ad8f025d995 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 8 Apr 2020 09:56:27 +0200 Subject: [PATCH 05/44] Add TryCatchHandler annotation to CatchClause --- .../CSharp/SequencePointBuilder.cs | 33 +++++++++++++++++++ .../CSharp/StatementBuilder.cs | 1 + .../IL/Transforms/ExpressionTransforms.cs | 6 ++-- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs index 7c7be5344..37589c1c4 100644 --- a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs @@ -289,6 +289,32 @@ 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) { + StartSequencePoint(catchClause.CatchToken); + var function = catchClause.Ancestors.OfType().FirstOrDefault(); + AddToSequencePointRaw(function, new[] { catchClause.Annotation().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); + } + StartSequencePoint(catchClause); + catchClause.Body.AcceptVisitor(this); + EndSequencePoint(catchClause.StartLocation, catchClause.EndLocation); + } + /// /// Start a new C# statement = new sequence point. /// @@ -318,6 +344,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 ae6eb6c7d..488311f88 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/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 061ee7721..7d9d411f1 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -692,7 +692,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 +709,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 @@ -754,7 +754,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; From 4db41f69dbbbe0cd50d8ddd3090741ebd7dfc091 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 8 Apr 2020 12:13:21 +0200 Subject: [PATCH 06/44] Fix #1919: Use unmapped IL offsets at the start of a catch-block for the 'exception specifier' sequence point. --- .../CSharp/SequencePointBuilder.cs | 21 +++++++++++++------ .../IL/Transforms/ExpressionTransforms.cs | 9 ++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs index f9de2f1d9..5e65f5679 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.ILRanges.First().Start; + } // 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; @@ -321,17 +327,20 @@ namespace ICSharpCode.Decompiler.CSharp public override void VisitCatchClause(CatchClause catchClause) { + StartSequencePoint(catchClause); if (catchClause.Condition.IsNull) { - StartSequencePoint(catchClause.CatchToken); - var function = catchClause.Ancestors.OfType().FirstOrDefault(); - AddToSequencePointRaw(function, new[] { catchClause.Annotation().ExceptionSpecifierILRange }); - EndSequencePoint(catchClause.CatchToken.StartLocation, catchClause.RParToken.IsNull ? catchClause.CatchToken.EndLocation : catchClause.RParToken.EndLocation); + var tryCatchHandler = catchClause.Annotation(); + if (!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); } - StartSequencePoint(catchClause); catchClause.Body.AcceptVisitor(this); EndSequencePoint(catchClause.StartLocation, catchClause.EndLocation); } diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 7d9d411f1..c299eda0b 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 { @@ -711,6 +713,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// void TransformCatchVariable(TryCatchHandler handler, Block entryPoint, bool isCatchBlock) { + List ilOffsets = null; if (!handler.Variable.IsSingleDefinition || handler.Variable.LoadCount != 1) return; // handle.Variable already has non-trivial uses if (!entryPoint.Instructions[0].MatchStLoc(out var exceptionVar, out var exceptionSlotLoad)) { @@ -720,6 +723,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (inlinedUnboxAny.Type.Equals(handler.Variable.Type)) { context.Step("TransformCatchVariable - remove inlined UnboxAny", inlinedUnboxAny); inlinedUnboxAny.ReplaceWith(inlinedUnboxAny.Argument); + (ilOffsets ?? (ilOffsets = new List())).AddRange(inlinedUnboxAny.ILRanges); } } return; @@ -746,6 +750,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms exceptionVar.Kind = VariableKind.ExceptionLocal; exceptionVar.Type = handler.Variable.Type; handler.Variable = exceptionVar; + if (isCatchBlock) { + (ilOffsets ?? (ilOffsets = new List())).AddRange(entryPoint.Instructions[0].Descendants.SelectMany(o => o.ILRanges)); + foreach (var offset in ilOffsets) + handler.AddExceptionSpecifierILRange(offset); + } entryPoint.Instructions.RemoveAt(0); } From 199d38b85a2fec29623fb71cc1eaa4a0997629ea Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 8 Apr 2020 17:32:06 +0200 Subject: [PATCH 07/44] Add more test cases --- .../TestCases/Pretty/NullPropagation.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs index 33c4617de..efb9ec366 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs @@ -68,16 +68,42 @@ 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? GetTextLengthNRE() + { + return (Field1?.ToString()).Length; + } } public interface ITest From 24810cb2e8fbfd4b5290c7d4c7aea4c207df92f0 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 18 Apr 2020 19:43:33 +0200 Subject: [PATCH 08/44] use ILInstruction.StartILOffset instead of ILRanges.First().Start Co-Authored-By: Daniel Grunwald --- ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs index 5e65f5679..a7190e086 100644 --- a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs @@ -118,7 +118,7 @@ namespace ICSharpCode.Decompiler.CSharp // if this block container is part of a TryCatchHandler, do not steal the exception-specifier IL range intervalStart = handler.ExceptionSpecifierILRange.End; } else { - intervalStart = blockContainer.ILRanges.First().Start; + 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; From 4846feb640bbac2e57766950c1a23ab73d8bc709 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 19 Apr 2020 04:03:06 +0200 Subject: [PATCH 09/44] Add support for C# 8 range syntax. This initial commit only handles the trivial case where an Index or Range object is constructed. The TODO portions of the test case show there are plenty of cases where where the C# compiler emits more complex code patterns that will require ILAst transforms. --- .../ICSharpCode.Decompiler.Tests.csproj | 1 + .../PrettyTestRunner.cs | 8 +- .../TestCases/Pretty/IndexRangeTest.cs | 199 ++++++++++++++++++ ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 42 ++++ .../OutputVisitor/CSharpOutputVisitor.cs | 3 + .../OutputVisitor/InsertParenthesesVisitor.cs | 142 +++++++------ .../Expressions/BinaryOperatorExpression.cs | 10 +- .../Expressions/UnaryOperatorExpression.cs | 8 + ICSharpCode.Decompiler/DecompilerSettings.cs | 20 +- .../TypeSystem/KnownTypeReference.cs | 8 +- ILSpy/Properties/Resources.Designer.cs | 9 + ILSpy/Properties/Resources.resx | 3 + 12 files changed, 386 insertions(+), 67 deletions(-) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 053e73191..abf931437 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..bb47378ba --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs @@ -0,0 +1,199 @@ +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 string[] 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 void UseIndex() + { +#if TODO + Console.WriteLine(GetArray()[GetIndex()]); + Console.WriteLine(GetList()[GetIndex()]); + Console.WriteLine(GetSpan()[GetIndex()]); + Console.WriteLine(GetString()[GetIndex()]); + Console.WriteLine(new CustomList()[GetIndex()]); +#endif + Console.WriteLine(new CustomList2()[GetIndex()]); + } + public static void UseRange() + { +#if TODO + 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()]); +#endif + Console.WriteLine(new CustomList2()[GetRange()]); + } + public static void UseNewRangeFromIndex() + { +#if TODO + Console.WriteLine(GetArray()[GetIndex()..GetIndex()]); + //Console.WriteLine(GetList()[GetIndex()..GetIndex()]); // fails to compile + Console.WriteLine(GetSpan()[GetIndex()..GetIndex()].ToString()); + Console.WriteLine(GetString()[GetIndex()..GetIndex()]); + Console.WriteLine(new CustomList()[GetIndex()..GetIndex()]); +#endif + Console.WriteLine(new CustomList2()[GetIndex()..GetIndex()]); + } + public static void UseNewRangeFromIntegers_BothFromStart() + { +#if TODO + Console.WriteLine(GetArray()[GetInt(1)..GetInt(2)]); + //Console.WriteLine(GetList()[GetInt()..GetInt()]); // fails to compile + Console.WriteLine(GetSpan()[GetInt(1)..GetInt(2)].ToString()); + Console.WriteLine(GetString()[GetInt(1)..GetInt(2)]); + Console.WriteLine(new CustomList()[GetInt(1)..GetInt(2)]); +#endif + Console.WriteLine(new CustomList2()[GetInt(1)..GetInt(2)]); + } + public static void UseNewRangeFromIntegers_BothFromEnd() + { +#if TODO + Console.WriteLine(GetArray()[^GetInt(1)..^GetInt(2)]); + //Console.WriteLine(GetList()[^GetInt()..^GetInt()]); // fails to compile + Console.WriteLine(GetSpan()[^GetInt(1)..^GetInt(2)].ToString()); + Console.WriteLine(GetString()[^GetInt(1)..^GetInt(2)]); + Console.WriteLine(new CustomList()[^GetInt(1)..^GetInt(2)]); +#endif + Console.WriteLine(new CustomList2()[^GetInt(1)..^GetInt(2)]); + } + public static void UseNewRangeFromIntegers_FromStartAndEnd() + { +#if TODO + Console.WriteLine(GetArray()[GetInt(1)..^GetInt(2)]); + //Console.WriteLine(GetList()[GetInt()..^GetInt()]); // fails to compile + Console.WriteLine(GetSpan()[GetInt(1)..^GetInt(2)].ToString()); + Console.WriteLine(GetString()[GetInt(1)..^GetInt(2)]); + Console.WriteLine(new CustomList()[GetInt(1)..^GetInt(2)]); +#endif + Console.WriteLine(new CustomList2()[GetInt(1)..^GetInt(2)]); + } + public static void UseNewRangeFromIntegers_FromEndAndStart() + { +#if TODO + Console.WriteLine(GetArray()[^GetInt(1)..GetInt(2)]); + //Console.WriteLine(GetList()[^GetInt()..GetInt()]); // fails to compile + Console.WriteLine(GetSpan()[^GetInt(1)..GetInt(2)].ToString()); + Console.WriteLine(GetString()[^GetInt(1)..GetInt(2)]); + Console.WriteLine(new CustomList()[^GetInt(1)..GetInt(2)]); +#endif + Console.WriteLine(new CustomList2()[^GetInt(1)..GetInt(2)]); + } + + public static void UseNewRangeFromIntegers_OnlyEndPoint() + { +#if TODO + 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)]); +#endif + Console.WriteLine(new CustomList2()[..GetInt(2)]); + } + + public static void UseNewRangeFromIntegers_OnlyStartPoint() + { +#if TODO + 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)..]); +#endif + Console.WriteLine(new CustomList2()[GetInt(1)..]); + } + + public static void UseWholeRange() + { +#if TODO + Console.WriteLine(GetArray()[..]); + //Console.WriteLine(GetList()[..]); // fails to compile + Console.WriteLine(GetSpan()[..].ToString()); + Console.WriteLine(GetString()[..]); + Console.WriteLine(new CustomList()[..]); +#endif + 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/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 267a757fe..ba8aaf82e 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -252,6 +252,12 @@ namespace ICSharpCode.Decompiler.CSharp argumentList.ExpectedParameters = method.Parameters.ToArray(); } + if (settings.Ranges) { + if (HandleRangeConstruction(out var result, callOpCode, method, argumentList)) { + return result; + } + } + if (callOpCode == OpCode.NewObj) { return HandleConstructorCall(expectedTargetDetails, target.ResolveResult, method, argumentList); } @@ -1494,5 +1500,41 @@ 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, 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 ResolveResult(method.DeclaringType)); + 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 ResolveResult(method.DeclaringType)); + 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 ResolveResult(method.DeclaringType)); + 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 ResolveResult(method.DeclaringType)); + 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 ResolveResult(method.DeclaringType)); + return true; + } + return false; + } } } \ No newline at end of file diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index faa67d21c..6e1e9650f 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"); } 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/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/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 95eb3fcb7..18efc74a0 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/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/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 267bf70dd..2da49f2d0 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -920,6 +920,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. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index de2f7a957..426065be5 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -852,4 +852,7 @@ Do you want to continue? Do you want to continue? + + Ranges + \ No newline at end of file From dc6e094a307d3e4a59a48fdb5a3a12c91cbf9e38 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 19 Apr 2020 08:26:18 +0200 Subject: [PATCH 10/44] Add support for indexing arrays using System.Index --- .../TestCases/Pretty/IndexRangeTest.cs | 52 ++++++++--- .../CSharp/ExpressionBuilder.cs | 17 +++- .../ICSharpCode.Decompiler.csproj | 1 + ICSharpCode.Decompiler/IL/Instructions.cs | 5 +- ICSharpCode.Decompiler/IL/Instructions.tt | 12 +++ .../IL/Transforms/ExpressionTransforms.cs | 2 + .../IL/Transforms/ILInlining.cs | 5 ++ .../IL/Transforms/IndexRangeTransform.cs | 89 +++++++++++++++++++ 8 files changed, 168 insertions(+), 15 deletions(-) create mode 100644 ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs index bb47378ba..8ec33c1b7 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs @@ -29,20 +29,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty internal class IndexRangeTest { - public static string[] GetArray() - { + public static int[] GetArray() + { throw null; - } - public static List GetList() - { + } + 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) @@ -60,8 +60,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void UseIndex() { -#if TODO Console.WriteLine(GetArray()[GetIndex()]); +#if TODO Console.WriteLine(GetList()[GetIndex()]); Console.WriteLine(GetSpan()[GetIndex()]); Console.WriteLine(GetString()[GetIndex()]); @@ -69,6 +69,38 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty #endif Console.WriteLine(new CustomList2()[GetIndex()]); } + + public static void UseIndexFromEnd() + { + Console.WriteLine(GetArray()[^GetInt()]); +#if TODO + Console.WriteLine(GetList()[^GetInt()]); + Console.WriteLine(GetSpan()[^GetInt()]); + Console.WriteLine(GetString()[^GetInt()]); + Console.WriteLine(new CustomList()[^GetInt()]); +#endif + Console.WriteLine(new CustomList2()[^GetInt()]); + } + + public static void UseIndexForWrite() + { + GetArray()[GetIndex()] = GetInt(); +#if TODO + GetList()[GetIndex()] = GetInt(); + GetSpan()[GetIndex()] = GetInt(); +#endif + } + + private static void UseRef(ref int i) + { + } + + public static void UseIndexForRef() + { + UseRef(ref GetArray()[GetIndex()]); + UseRef(ref GetArray()[^GetInt()]); + } + public static void UseRange() { #if TODO @@ -173,7 +205,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } 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. @@ -184,7 +216,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } 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. diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index ffec61113..37b58782a 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -2238,14 +2238,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/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index a28fea867..75cb72258 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -64,6 +64,7 @@ + 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/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index 061ee7721..bfc95c390 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -206,6 +206,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) diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index 95fd133ba..448ecbb6c 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -470,6 +470,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms 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: switch (next.OpCode) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs new file mode 100644 index 000000000..ed3140bf5 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs @@ -0,0 +1,89 @@ +// 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 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 + { + /// + /// 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 indexCtor = FindIndexConstructor(context.TypeSystem); + if (indexCtor == null) + return false; // don't use System.Index if not supported by the target framework + context.Step("ldelema indexed from end", ldelema); + foreach (var node in bni.Left.Descendants) + ldelema.AddILRange(node); + ldelema.AddILRange(bni); + ldelema.WithSystemIndex = true; + ldelema.Indices[0] = new NewObj(indexCtor) { Arguments = { bni.Right, new LdcI4(1) } }; + return true; + } + + return false; + } + + static IMethod FindIndexConstructor(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)) { + return ctor; + } + } + return null; + } + } +} From 748c54a1c159f8d15064e059d53e7237317dc7f0 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 19 Apr 2020 08:29:48 +0200 Subject: [PATCH 11/44] Support array slicing. --- .../TestCases/Pretty/IndexRangeTest.cs | 16 ++++++++-------- .../ReplaceMethodCallsWithOperators.cs | 7 +++++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs index 8ec33c1b7..96c86a951 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs @@ -103,8 +103,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void UseRange() { -#if TODO Console.WriteLine(GetArray()[GetRange()]); +#if TODO //Console.WriteLine(GetList()[GetRange()]); // fails to compile Console.WriteLine(GetSpan()[GetRange()].ToString()); Console.WriteLine(GetString()[GetRange()]); @@ -114,8 +114,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } public static void UseNewRangeFromIndex() { -#if TODO Console.WriteLine(GetArray()[GetIndex()..GetIndex()]); +#if TODO //Console.WriteLine(GetList()[GetIndex()..GetIndex()]); // fails to compile Console.WriteLine(GetSpan()[GetIndex()..GetIndex()].ToString()); Console.WriteLine(GetString()[GetIndex()..GetIndex()]); @@ -125,8 +125,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } public static void UseNewRangeFromIntegers_BothFromStart() { -#if TODO Console.WriteLine(GetArray()[GetInt(1)..GetInt(2)]); +#if TODO //Console.WriteLine(GetList()[GetInt()..GetInt()]); // fails to compile Console.WriteLine(GetSpan()[GetInt(1)..GetInt(2)].ToString()); Console.WriteLine(GetString()[GetInt(1)..GetInt(2)]); @@ -136,8 +136,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } public static void UseNewRangeFromIntegers_BothFromEnd() { -#if TODO Console.WriteLine(GetArray()[^GetInt(1)..^GetInt(2)]); +#if TODO //Console.WriteLine(GetList()[^GetInt()..^GetInt()]); // fails to compile Console.WriteLine(GetSpan()[^GetInt(1)..^GetInt(2)].ToString()); Console.WriteLine(GetString()[^GetInt(1)..^GetInt(2)]); @@ -158,8 +158,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } public static void UseNewRangeFromIntegers_FromEndAndStart() { -#if TODO Console.WriteLine(GetArray()[^GetInt(1)..GetInt(2)]); +#if TODO //Console.WriteLine(GetList()[^GetInt()..GetInt()]); // fails to compile Console.WriteLine(GetSpan()[^GetInt(1)..GetInt(2)].ToString()); Console.WriteLine(GetString()[^GetInt(1)..GetInt(2)]); @@ -170,8 +170,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void UseNewRangeFromIntegers_OnlyEndPoint() { -#if TODO Console.WriteLine(GetArray()[..GetInt(2)]); +#if TODO //Console.WriteLine(GetList()[..GetInt()]); // fails to compile Console.WriteLine(GetSpan()[..GetInt(2)].ToString()); Console.WriteLine(GetString()[..GetInt(2)]); @@ -182,8 +182,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void UseNewRangeFromIntegers_OnlyStartPoint() { -#if TODO Console.WriteLine(GetArray()[GetInt(1)..]); +#if TODO //Console.WriteLine(GetList()[GetInt()..]); // fails to compile Console.WriteLine(GetSpan()[GetInt(1)..].ToString()); Console.WriteLine(GetString()[GetInt(1)..]); @@ -194,8 +194,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void UseWholeRange() { -#if TODO Console.WriteLine(GetArray()[..]); +#if TODO //Console.WriteLine(GetList()[..]); // fails to compile Console.WriteLine(GetSpan()[..].ToString()); Console.WriteLine(GetString()[..]); 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); From 12226c5f90330b336f8b79d3f60e0dd9c3f37f1a Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 19 Apr 2020 10:58:01 +0200 Subject: [PATCH 12/44] Add support for indexing a container with a System.Index instance. --- .../TestCases/Pretty/IndexRangeTest.cs | 8 +- .../CSharp/CSharpDecompiler.cs | 3 +- .../ICSharpCode.Decompiler.csproj | 1 + .../IL/Transforms/ILInlining.cs | 4 + .../IL/Transforms/IndexRangeTransform.cs | 149 +++++++++++++++++- .../Implementation/SyntheticRangeIndexer.cs | 128 +++++++++++++++ 6 files changed, 287 insertions(+), 6 deletions(-) create mode 100644 ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs index 96c86a951..27df19e3c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs @@ -61,12 +61,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void UseIndex() { Console.WriteLine(GetArray()[GetIndex()]); -#if TODO Console.WriteLine(GetList()[GetIndex()]); Console.WriteLine(GetSpan()[GetIndex()]); Console.WriteLine(GetString()[GetIndex()]); Console.WriteLine(new CustomList()[GetIndex()]); -#endif Console.WriteLine(new CustomList2()[GetIndex()]); } @@ -85,10 +83,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void UseIndexForWrite() { GetArray()[GetIndex()] = GetInt(); -#if TODO GetList()[GetIndex()] = GetInt(); GetSpan()[GetIndex()] = GetInt(); -#endif } private static void UseRef(ref int i) @@ -99,6 +95,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { UseRef(ref GetArray()[GetIndex()]); UseRef(ref GetArray()[^GetInt()]); + UseRef(ref GetSpan()[GetIndex()]); +#if TODO + UseRef(ref GetSpan()[^GetInt()]); +#endif } public static void UseRange() diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 53b10fa6f..6e7625c57 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() ), } }, diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 75cb72258..d3b5ed919 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -385,6 +385,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index 448ecbb6c..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,9 @@ 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) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs index ed3140bf5..98b81d3f2 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs @@ -16,6 +16,10 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; +using System.Linq; +using ICSharpCode.Decompiler.CSharp.Resolver; +using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem.Implementation; @@ -24,7 +28,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// Transform for the C# 8 System.Index / System.Range feature /// - class IndexRangeTransform + class IndexRangeTransform : IStatementTransform { /// /// Called by expression transforms. @@ -85,5 +89,148 @@ namespace ICSharpCode.Decompiler.IL.Transforms } return null; } + + void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) + { + int startPos = pos; + if (!MatchContainerLengthStore(block.Instructions[pos], out ILVariable containerLengthVar, out ILVariable containerVar)) + return; + pos++; + if (!MatchGetOffsetFromIndex(block.Instructions[pos], out ILVariable startOffsetVar, out ILInstruction startIndexLdloca, containerLengthVar)) + return; + pos++; + if (startOffsetVar.LoadCount == 1) { + // complex_expr(call get_Item(ldloc container, ldloc offset)) + + // 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 (!(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 { + return; + } + if (!call.Method.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)) + return; + if (!call.Arguments[0].MatchLdLoc(containerVar) && !call.Arguments[0].MatchLdLoca(containerVar)) + return; + if (!call.Arguments[1].MatchLdLoc(startOffsetVar)) + return; + var indexType = context.TypeSystem.FindType(KnownTypeCode.Index); + if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, indexType, context)) + return; + + // stloc length(call get_Length/ get_Count(ldloc container)) + // stloc offset(call GetOffset(..., ldloc length)) + // complex_expr(call get_Item(ldloc container, ldloc offset)) + // --> + // complex_expr(call get_Item(ldloc container, ...)) + context.Step($"{call.Method.Name} indexed with System.Index", call); + var newMethod = new SyntheticRangeIndexAccessor(call.Method, indexType); + var newCall = CallInstruction.Create(call.OpCode, newMethod); + newCall.ConstrainedTo = call.ConstrainedTo; + newCall.ILStackWasEmpty = call.ILStackWasEmpty; + newCall.Arguments.Add(call.Arguments[0]); + newCall.Arguments.Add(new LdObj(startIndexLdloca, indexType)); + 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); + } + } + + /// + /// Gets whether the C# compiler will call `container[int]` when using `container[Index]`. + /// + private bool CSharpWillGenerateIndexer(IType declaringType, IType indexType, ILTransformContext context) + { + bool foundInt32Overload = false; + bool foundIndexOverload = 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 (prop.Name == "Length" || prop.Name=="Count") { + foundCountProperty = true; + } + } + return foundInt32Overload && foundCountProperty && !foundIndexOverload; + } + + /// + /// Matches the instruction: + /// stloc containerLengthVar(call get_Length/get_Count(ldloc containerVar)) + /// + static bool MatchContainerLengthStore(ILInstruction inst, out ILVariable lengthVar, out ILVariable containerVar) + { + containerVar = null; + if (!inst.MatchStLoc(out lengthVar, out var init)) + return false; + if (!(lengthVar.IsSingleDefinition && lengthVar.StackType == StackType.I4)) + return false; + if (!(init is CallInstruction call)) + 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 call.Arguments[0].MatchLdLoc(out containerVar) || call.Arguments[0].MatchLdLoca(out containerVar); + } + + /// + /// Matches the instruction: + /// stloc offsetVar(call System.Index.GetOffset(indexLdloca, ldloc containerLengthVar)) + /// + static bool MatchGetOffsetFromIndex(ILInstruction inst, out ILVariable offsetVar, out ILInstruction indexLdloca, ILVariable containerLengthVar) + { + indexLdloca = null; + if (!inst.MatchStLoc(out offsetVar, out var offsetValue)) + return false; + if (!(offsetVar.IsSingleDefinition && offsetVar.StackType == StackType.I4)) + return false; + if (!(offsetValue is CallInstruction call)) + return false; + if (call.Method.Name != "GetOffset") + return false; + if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index)) + return false; + if (call.Arguments.Count != 2) + return false; + indexLdloca = call.Arguments[0]; + return call.Arguments[1].MatchLdLoc(containerLengthVar); + } } } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs new file mode 100644 index 000000000..5ea2e8f10 --- /dev/null +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs @@ -0,0 +1,128 @@ +// 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; + + public SyntheticRangeIndexAccessor(IMethod underlyingMethod, IType indexOrRangeType) + { + Debug.Assert(underlyingMethod != null); + Debug.Assert(indexOrRangeType != null); + this.underlyingMethod = underlyingMethod; + this.indexOrRangeType = indexOrRangeType; + var parameters = new List(); + parameters.Add(new DefaultParameter(indexOrRangeType, "")); + parameters.AddRange(underlyingMethod.Parameters.Skip(1)); + this.parameters = parameters; + } + + 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); + } + + 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); + } + + IMember IMember.Specialize(TypeParameterSubstitution substitution) + { + return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType); + } + } +} From 060830dd6460afbd21a4f6731ee2995bc8f20fc6 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 19 Apr 2020 11:08:31 +0200 Subject: [PATCH 13/44] Variable splitting: support cases where a ref is passed through a ref-returning method, and then used. From `IndexRangeTest.UseIndexForRef`: ``` stloc V_4(call GetSpan()) stloc S_19(ldloca V_4) stloc V_2(call get_Length(ldloc S_19)) stloc V_3(call GetOffset(addressof System.Index(call GetIndex(ldc.i4 0)), ldloc V_2)) call UseRef(call get_Item(ldloc S_19, ldloc V_3)) stloc V_4(call GetSpan()) stloc S_30(ldloca V_4) stloc V_2(binary.sub.i4(call get_Length(ldloc S_30), call GetInt(ldc.i4 0))) call UseRef(call get_Item(ldloc S_30, ldloc V_2)) ``` Due to `Span.get_Item` being a ref-return, it's possible that `ref V_4` is returned and passed into the `UseRef` method (this can't actually happen given Span's implementation, but it's a possible implementation of the get_Item type signature). But we still need to split `V_4` -- it's a compiler-generated variable and needs to be inlined. I think we can do this relatively simply by continuing to go up the ancestor instructions when we hit a ref-returning call. The recursive `DetermineAddressUse` call will check that there are no stores to `V_4` between the `get_Item` call and the point where the returned reference is used. Thus we still ensure that we don't split a variable while there is a live reference to it. --- .../IL/Transforms/SplitVariables.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) 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 From dc38355e1204c0da6cf9d3a9bae8c136814361e8 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 19 Apr 2020 11:55:32 +0200 Subject: [PATCH 14/44] Support `list[^idx]`. Here the C# compiler does not actually create a `System.Index` instance, but instead compiles to `list[list.Count - idx]`. --- .../TestCases/Pretty/IndexRangeTest.cs | 4 - .../IL/Transforms/IndexRangeTransform.cs | 133 +++++++++++++----- 2 files changed, 101 insertions(+), 36 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs index 27df19e3c..9233680a3 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs @@ -71,12 +71,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void UseIndexFromEnd() { Console.WriteLine(GetArray()[^GetInt()]); -#if TODO Console.WriteLine(GetList()[^GetInt()]); Console.WriteLine(GetSpan()[^GetInt()]); Console.WriteLine(GetString()[^GetInt()]); Console.WriteLine(new CustomList()[^GetInt()]); -#endif Console.WriteLine(new CustomList2()[^GetInt()]); } @@ -96,9 +94,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty UseRef(ref GetArray()[GetIndex()]); UseRef(ref GetArray()[^GetInt()]); UseRef(ref GetSpan()[GetIndex()]); -#if TODO UseRef(ref GetSpan()[^GetInt()]); -#endif } public static void UseRange() diff --git a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs index 98b81d3f2..46e69000b 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Diagnostics; using System.Linq; using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.Semantics; @@ -93,15 +94,21 @@ namespace ICSharpCode.Decompiler.IL.Transforms void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { int startPos = pos; - if (!MatchContainerLengthStore(block.Instructions[pos], out ILVariable containerLengthVar, out ILVariable containerVar)) - return; + // The container length access may be a separate instruction, or it may be inline with the variable's use + if (MatchContainerLengthStore(block.Instructions[pos], out ILVariable containerLengthVar, out ILVariable containerVar)) { + pos++; + } else { + // Reset if MatchContainerLengthStore only had a partial match. MatchGetOffset() will then set `containerVar`. + containerLengthVar = null; + containerVar = null; + } + var startIndexKind = MatchGetOffset(block.Instructions[pos], out ILVariable startOffsetVar, out ILInstruction startIndexLoad, containerLengthVar, ref containerVar); pos++; - if (!MatchGetOffsetFromIndex(block.Instructions[pos], out ILVariable startOffsetVar, out ILInstruction startIndexLdloca, containerLengthVar)) + if (startIndexKind == IndexKind.None) return; - pos++; if (startOffsetVar.LoadCount == 1) { - // complex_expr(call get_Item(ldloc container, ldloc offset)) - + // complex_expr(call get_Item(ldloc container, ldloc startOffsetVar)) + // 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])) @@ -129,21 +136,33 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!call.Arguments[1].MatchLdLoc(startOffsetVar)) return; var indexType = context.TypeSystem.FindType(KnownTypeCode.Index); - if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, indexType, context)) + var indexCtor = FindIndexConstructor(context.TypeSystem); + if (indexCtor == null) + return; + if (!CSharpWillGenerateIndexer(call.Method.DeclaringType)) return; - // stloc length(call get_Length/ get_Count(ldloc container)) - // stloc offset(call GetOffset(..., ldloc length)) - // complex_expr(call get_Item(ldloc container, ldloc offset)) - // --> - // complex_expr(call get_Item(ldloc container, ...)) - context.Step($"{call.Method.Name} indexed with System.Index", call); + context.Step($"{call.Method.Name} indexed with {startIndexKind}", call); var newMethod = new SyntheticRangeIndexAccessor(call.Method, indexType); var newCall = CallInstruction.Create(call.OpCode, newMethod); newCall.ConstrainedTo = call.ConstrainedTo; newCall.ILStackWasEmpty = call.ILStackWasEmpty; newCall.Arguments.Add(call.Arguments[0]); - newCall.Arguments.Add(new LdObj(startIndexLdloca, indexType)); + if (startIndexKind == IndexKind.RefSystemIndex) { + // stloc length(call get_Length/get_Count(ldloc container)) + // stloc startOffsetVar(call GetOffset(startIndexLoad, ldloc length)) + // complex_expr(call get_Item(ldloc container, ldloc startOffsetVar)) + // --> + // complex_expr(call get_Item(ldloc container, ldobj startIndexLoad)) + newCall.Arguments.Add(new LdObj(startIndexLoad, indexType)); + } else { + // stloc offsetVar(binary.sub.i4(ldloc containerLengthVar, startIndexLoad)) + // complex_expr(call get_Item(ldloc container, ldloc startOffsetVar)) + // --> + // complex_expr(call get_Item(ldloc container, newobj System.Index(startIndexLoad, fromEnd: true))) + Debug.Assert(startIndexKind == IndexKind.FromEnd); + newCall.Arguments.Add(new NewObj(indexCtor) { Arguments = { startIndexLoad, new LdcI4(1) } }); + } newCall.Arguments.AddRange(call.Arguments.Skip(2)); newCall.AddILRange(call); for (int i = startPos; i < pos; i++) { @@ -157,7 +176,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// Gets whether the C# compiler will call `container[int]` when using `container[Index]`. /// - private bool CSharpWillGenerateIndexer(IType declaringType, IType indexType, ILTransformContext context) + private bool CSharpWillGenerateIndexer(IType declaringType) { bool foundInt32Overload = false; bool foundIndexOverload = false; @@ -188,8 +207,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms 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)) @@ -207,30 +243,63 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (call.Arguments.Count != 1) return false; - return call.Arguments[0].MatchLdLoc(out containerVar) || call.Arguments[0].MatchLdLoca(out containerVar); + if (containerVar != null) { + return call.Arguments[0].MatchLdLoc(containerVar) || call.Arguments[0].MatchLdLoca(containerVar); + } else { + return call.Arguments[0].MatchLdLoc(out containerVar) || call.Arguments[0].MatchLdLoca(out containerVar); + } + } + + enum IndexKind + { + None, + /// + /// indexLoad is loading the address of a System.Index struct + /// + RefSystemIndex, + /// + /// indexLoad is an integer, from the end of the container + /// + FromEnd } /// - /// Matches the instruction: - /// stloc offsetVar(call System.Index.GetOffset(indexLdloca, ldloc containerLengthVar)) + /// Matches an instruction computing an offset: + /// stloc offsetVar(call System.Index.GetOffset(indexLoad, ldloc containerLengthVar)) + /// or + /// stloc offsetVar(binary.sub.i4(ldloc containerLengthVar, indexLoad)) /// - static bool MatchGetOffsetFromIndex(ILInstruction inst, out ILVariable offsetVar, out ILInstruction indexLdloca, ILVariable containerLengthVar) + static IndexKind MatchGetOffset(ILInstruction inst, out ILVariable offsetVar, out ILInstruction indexLoad, + ILVariable containerLengthVar, ref ILVariable containerVar) { - indexLdloca = null; + indexLoad = null; if (!inst.MatchStLoc(out offsetVar, out var offsetValue)) - return false; + return IndexKind.None; if (!(offsetVar.IsSingleDefinition && offsetVar.StackType == StackType.I4)) - return false; - if (!(offsetValue is CallInstruction call)) - return false; - if (call.Method.Name != "GetOffset") - return false; - if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index)) - return false; - if (call.Arguments.Count != 2) - return false; - indexLdloca = call.Arguments[0]; - return call.Arguments[1].MatchLdLoc(containerLengthVar); + return IndexKind.None; + if (offsetValue is CallInstruction call) { + // call System.Index.GetOffset(indexLoad, ldloc containerLengthVar) + if (call.Method.Name != "GetOffset") + return IndexKind.None; + if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index)) + return IndexKind.None; + if (call.Arguments.Count != 2) + return IndexKind.None; + if (!MatchContainerLength(call.Arguments[1], containerLengthVar, ref containerVar)) + return IndexKind.None; + indexLoad = call.Arguments[0]; + return IndexKind.RefSystemIndex; + } else if (offsetValue is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub) { + if (bni.CheckForOverflow || bni.ResultType != StackType.I4 || bni.IsLifted) + return IndexKind.None; + // binary.sub.i4(ldloc containerLengthVar, indexLoad) + if (!MatchContainerLength(bni.Left, containerLengthVar, ref containerVar)) + return IndexKind.None; + indexLoad = bni.Right; + return IndexKind.FromEnd; + } else { + return IndexKind.None; + } } } } From dd54dbc144d4e1e423b028851a3c24c5fef12fb7 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 19 Apr 2020 12:10:27 +0200 Subject: [PATCH 15/44] Disable IndexRangeTransform if the "C# 8 ranges" setting is disabled. --- ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs index 46e69000b..3a91f0d1d 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs @@ -16,11 +16,8 @@ // 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.CSharp.Resolver; -using ICSharpCode.Decompiler.Semantics; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem.Implementation; @@ -93,6 +90,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) { + if (!context.Settings.Ranges) + return; int startPos = pos; // The container length access may be a separate instruction, or it may be inline with the variable's use if (MatchContainerLengthStore(block.Instructions[pos], out ILVariable containerLengthVar, out ILVariable containerVar)) { From aed358b5a09863ea9e263a50277278ebc057b0f8 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 19 Apr 2020 12:38:17 +0200 Subject: [PATCH 16/44] Use more precise ResolveResult, so that Range/Index operators are hyperlinked. --- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index ba8aaf82e..5619f736b 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1510,19 +1510,19 @@ namespace ICSharpCode.Decompiler.CSharp 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 ResolveResult(method.DeclaringType)); + .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 ResolveResult(method.DeclaringType)); + .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 ResolveResult(method.DeclaringType)); + .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 ResolveResult(method.DeclaringType)); + .WithRR(new MemberResolveResult(null, method)); return true; } } else if (callOpCode == OpCode.NewObj && method.DeclaringType.IsKnownType(KnownTypeCode.Index)) { @@ -1531,7 +1531,7 @@ namespace ICSharpCode.Decompiler.CSharp if (!(argumentList.Arguments[1].Expression is PrimitiveExpression pe && pe.Value is true)) return false; result = new UnaryOperatorExpression(UnaryOperatorType.IndexFromEnd, argumentList.Arguments[0]) - .WithRR(new ResolveResult(method.DeclaringType)); + .WithRR(new MemberResolveResult(null, method)); return true; } return false; From 0dd75d68526f7f19845d81198c571d9ab9ed6872 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 19 Apr 2020 14:34:16 +0200 Subject: [PATCH 17/44] Add support for slicing using C# 8 ranges. --- .../TestCases/Pretty/IndexRangeTest.cs | 42 ++- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 10 +- .../IL/Transforms/IndexRangeTransform.cs | 341 +++++++++++++++--- .../Implementation/SyntheticRangeIndexer.cs | 19 +- 4 files changed, 328 insertions(+), 84 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs index 9233680a3..442e9fa72 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs @@ -1,4 +1,22 @@ -using System; +// Copyright (c) 2020 Daniel Grunwald +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; using System.Collections.Generic; namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -110,57 +128,47 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } public static void UseNewRangeFromIndex() { - Console.WriteLine(GetArray()[GetIndex()..GetIndex()]); -#if TODO - //Console.WriteLine(GetList()[GetIndex()..GetIndex()]); // fails to compile - Console.WriteLine(GetSpan()[GetIndex()..GetIndex()].ToString()); - Console.WriteLine(GetString()[GetIndex()..GetIndex()]); - Console.WriteLine(new CustomList()[GetIndex()..GetIndex()]); -#endif - Console.WriteLine(new CustomList2()[GetIndex()..GetIndex()]); + Console.WriteLine(GetArray()[GetIndex(1)..GetIndex(2)]); + //Console.WriteLine(GetList()[GetIndex(1)..GetIndex(2)]); // fails to compile + Console.WriteLine(GetSpan()[GetIndex(1)..GetIndex(2)].ToString()); + Console.WriteLine(GetString()[GetIndex(1)..GetIndex(2)]); + Console.WriteLine(new CustomList()[GetIndex(1)..GetIndex(2)]); + Console.WriteLine(new CustomList2()[GetIndex(1)..GetIndex(2)]); } public static void UseNewRangeFromIntegers_BothFromStart() { Console.WriteLine(GetArray()[GetInt(1)..GetInt(2)]); -#if TODO //Console.WriteLine(GetList()[GetInt()..GetInt()]); // fails to compile Console.WriteLine(GetSpan()[GetInt(1)..GetInt(2)].ToString()); Console.WriteLine(GetString()[GetInt(1)..GetInt(2)]); Console.WriteLine(new CustomList()[GetInt(1)..GetInt(2)]); -#endif Console.WriteLine(new CustomList2()[GetInt(1)..GetInt(2)]); } public static void UseNewRangeFromIntegers_BothFromEnd() { Console.WriteLine(GetArray()[^GetInt(1)..^GetInt(2)]); -#if TODO //Console.WriteLine(GetList()[^GetInt()..^GetInt()]); // fails to compile Console.WriteLine(GetSpan()[^GetInt(1)..^GetInt(2)].ToString()); Console.WriteLine(GetString()[^GetInt(1)..^GetInt(2)]); Console.WriteLine(new CustomList()[^GetInt(1)..^GetInt(2)]); -#endif Console.WriteLine(new CustomList2()[^GetInt(1)..^GetInt(2)]); } public static void UseNewRangeFromIntegers_FromStartAndEnd() { -#if TODO Console.WriteLine(GetArray()[GetInt(1)..^GetInt(2)]); //Console.WriteLine(GetList()[GetInt()..^GetInt()]); // fails to compile Console.WriteLine(GetSpan()[GetInt(1)..^GetInt(2)].ToString()); Console.WriteLine(GetString()[GetInt(1)..^GetInt(2)]); Console.WriteLine(new CustomList()[GetInt(1)..^GetInt(2)]); -#endif Console.WriteLine(new CustomList2()[GetInt(1)..^GetInt(2)]); } public static void UseNewRangeFromIntegers_FromEndAndStart() { Console.WriteLine(GetArray()[^GetInt(1)..GetInt(2)]); -#if TODO //Console.WriteLine(GetList()[^GetInt()..GetInt()]); // fails to compile Console.WriteLine(GetSpan()[^GetInt(1)..GetInt(2)].ToString()); Console.WriteLine(GetString()[^GetInt(1)..GetInt(2)]); Console.WriteLine(new CustomList()[^GetInt(1)..GetInt(2)]); -#endif Console.WriteLine(new CustomList2()[^GetInt(1)..GetInt(2)]); } diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 5619f736b..68a3daf8e 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -253,7 +253,7 @@ namespace ICSharpCode.Decompiler.CSharp } if (settings.Ranges) { - if (HandleRangeConstruction(out var result, callOpCode, method, argumentList)) { + if (HandleRangeConstruction(out var result, callOpCode, method, target, argumentList)) { return result; } } @@ -1501,7 +1501,7 @@ namespace ICSharpCode.Decompiler.CSharp .WithILInstruction(call).WithILInstruction(block); } - private bool HandleRangeConstruction(out ExpressionWithResolveResult result, OpCode callOpCode, IMethod method, ArgumentList argumentList) + private bool HandleRangeConstruction(out ExpressionWithResolveResult result, OpCode callOpCode, IMethod method, TranslatedExpression target, ArgumentList argumentList) { result = default; if (argumentList.ArgumentNames != null) { @@ -1533,6 +1533,12 @@ namespace ICSharpCode.Decompiler.CSharp 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; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs index 3a91f0d1d..72bf4cd6a 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs @@ -16,6 +16,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; using System.Diagnostics; using System.Linq; using ICSharpCode.Decompiler.TypeSystem; @@ -61,31 +62,52 @@ namespace ICSharpCode.Decompiler.IL.Transforms // -> withsystemindex.ldelema T(ldloc array, newobj System.Index(..., fromEnd: true)) if (!(bni.Left.MatchLdLen(StackType.I4, out var arrayLoad) && arrayLoad.MatchLdLoc(array))) return false; - var indexCtor = FindIndexConstructor(context.TypeSystem); - if (indexCtor == null) + var indexMethods = new IndexMethods(context.TypeSystem); + if (!indexMethods.AllValid) return false; // don't use System.Index if not supported by the target framework context.Step("ldelema indexed from end", ldelema); foreach (var node in bni.Left.Descendants) ldelema.AddILRange(node); ldelema.AddILRange(bni); ldelema.WithSystemIndex = true; - ldelema.Indices[0] = new NewObj(indexCtor) { Arguments = { bni.Right, new LdcI4(1) } }; + ldelema.Indices[0] = MakeIndex(IndexKind.FromEnd, bni.Right, indexMethods); return true; } return false; } - static IMethod FindIndexConstructor(ICompilation compilation) + class IndexMethods { - var indexType = compilation.FindType(KnownTypeCode.Index); - foreach (var ctor in indexType.GetConstructors(m => m.Parameters.Count == 2)) { - if (ctor.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32) - && ctor.Parameters[1].Type.IsKnownType(KnownTypeCode.Boolean)) { - return ctor; + public readonly IMethod IndexCtor; + public readonly IMethod IndexImplicitConv; + public readonly IMethod RangeCtor; + public IType IndexType => IndexCtor?.DeclaringType; + public IType RangeType => RangeCtor?.DeclaringType; + public bool AllValid => IndexCtor != null && IndexImplicitConv != null && RangeCtor != null; + + public IndexMethods(ICompilation compilation) + { + var indexType = compilation.FindType(KnownTypeCode.Index); + foreach (var ctor in indexType.GetConstructors(m => m.Parameters.Count == 2)) { + if (ctor.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32) + && ctor.Parameters[1].Type.IsKnownType(KnownTypeCode.Boolean)) { + this.IndexCtor = ctor; + } + } + foreach (var op in indexType.GetMethods(m => m.IsOperator && m.Name == "op_Implicit")) { + if (op.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)) { + this.IndexImplicitConv = op; + } + } + var rangeType = compilation.FindType(KnownTypeCode.Range); + foreach (var ctor in rangeType.GetConstructors(m => m.Parameters.Count == 2)) { + if (ctor.Parameters[0].Type.IsKnownType(KnownTypeCode.Index) + && ctor.Parameters[1].Type.IsKnownType(KnownTypeCode.Index)) { + this.RangeCtor = ctor; + } } } - return null; } void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) @@ -93,21 +115,50 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!context.Settings.Ranges) return; int startPos = pos; + ILVariable containerVar = null; // The container length access may be a separate instruction, or it may be inline with the variable's use - if (MatchContainerLengthStore(block.Instructions[pos], out ILVariable containerLengthVar, out ILVariable containerVar)) { + if (MatchContainerLengthStore(block.Instructions[pos], out ILVariable containerLengthVar, ref containerVar)) { + // stloc containerLengthVar(call get_Length/get_Count(ldloc container)) pos++; } else { // Reset if MatchContainerLengthStore only had a partial match. MatchGetOffset() will then set `containerVar`. containerLengthVar = null; containerVar = null; } - var startIndexKind = MatchGetOffset(block.Instructions[pos], out ILVariable startOffsetVar, out ILInstruction startIndexLoad, containerLengthVar, ref containerVar); - pos++; - if (startIndexKind == IndexKind.None) + if (block.Instructions[pos].MatchStLoc(out var rangeVar, out var rangeVarInit) && rangeVar.Type.IsKnownType(KnownTypeCode.Range)) { + // stloc rangeVar(rangeVarInit) + pos++; + } else { + rangeVar = null; + rangeVarInit = null; + } + // stloc startOffsetVar(call GetOffset(startIndexLoad, ldloc length)) + if (!block.Instructions[pos].MatchStLoc(out ILVariable startOffsetVar, out ILInstruction startOffsetVarInit)) + return; + if (!(startOffsetVar.IsSingleDefinition && startOffsetVar.StackType == StackType.I4)) return; + var startIndexKind = MatchGetOffset(startOffsetVarInit, out ILInstruction startIndexLoad, containerLengthVar, ref containerVar); + pos++; if (startOffsetVar.LoadCount == 1) { + TransformIndexing(); + } else if (startOffsetVar.LoadCount == 2) { + // might be slicing: startOffset is used once for the slice length calculation, and once for the Slice() method call + TransformSlicing(); + } + + void TransformIndexing() + { // complex_expr(call get_Item(ldloc container, ldloc startOffsetVar)) - + + if (rangeVar != null) + return; + if (startIndexKind == IndexKind.FromStart) { + // FromStart is only relevant for slicing; indexing from the start does not involve System.Index at all. + return; + } + if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind)) { + return; + } // startOffsetVar might be used deep inside a complex statement, ensure we can inline up to that point: for (int i = startPos; i < pos; i++) { if (!ILInlining.CanInlineInto(block.Instructions[pos], startOffsetVar, block.Instructions[i])) @@ -130,39 +181,117 @@ namespace ICSharpCode.Decompiler.IL.Transforms } if (!call.Method.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)) return; - if (!call.Arguments[0].MatchLdLoc(containerVar) && !call.Arguments[0].MatchLdLoca(containerVar)) + if (!MatchContainerVar(call.Arguments[0], ref containerVar)) return; if (!call.Arguments[1].MatchLdLoc(startOffsetVar)) return; - var indexType = context.TypeSystem.FindType(KnownTypeCode.Index); - var indexCtor = FindIndexConstructor(context.TypeSystem); - if (indexCtor == null) + var specialMethods = new IndexMethods(context.TypeSystem); + if (!specialMethods.AllValid) return; - if (!CSharpWillGenerateIndexer(call.Method.DeclaringType)) + if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, slicing: false)) return; context.Step($"{call.Method.Name} indexed with {startIndexKind}", call); - var newMethod = new SyntheticRangeIndexAccessor(call.Method, indexType); + var newMethod = new SyntheticRangeIndexAccessor(call.Method, specialMethods.IndexType, slicing: false); var newCall = CallInstruction.Create(call.OpCode, newMethod); newCall.ConstrainedTo = call.ConstrainedTo; newCall.ILStackWasEmpty = call.ILStackWasEmpty; newCall.Arguments.Add(call.Arguments[0]); - if (startIndexKind == IndexKind.RefSystemIndex) { - // stloc length(call get_Length/get_Count(ldloc container)) - // stloc startOffsetVar(call GetOffset(startIndexLoad, ldloc length)) - // complex_expr(call get_Item(ldloc container, ldloc startOffsetVar)) - // --> - // complex_expr(call get_Item(ldloc container, ldobj startIndexLoad)) - newCall.Arguments.Add(new LdObj(startIndexLoad, indexType)); + newCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods)); + newCall.Arguments.AddRange(call.Arguments.Skip(2)); + newCall.AddILRange(call); + for (int i = startPos; i < pos; i++) { + newCall.AddILRange(block.Instructions[i]); + } + call.ReplaceWith(newCall); + block.Instructions.RemoveRange(startPos, pos - startPos); + } + + void TransformSlicing() + { + // stloc containerLengthVar(call get_Length(ldloc containerVar)) + // stloc startOffset(call GetOffset(startIndexLoad, ldloc length)) + // -- we are here -- + // stloc sliceLengthVar(binary.sub.i4(call GetOffset(endIndexLoad, ldloc length), ldloc startOffset)) + // complex_expr(call Slice(ldloc containerVar, ldloc startOffset, ldloc sliceLength)) + if (!block.Instructions[pos].MatchStLoc(out var sliceLengthVar, out var sliceLengthVarInit)) + return; + pos++; + if (!(sliceLengthVar.IsSingleDefinition && sliceLengthVar.LoadCount == 1)) + return; + if (!MatchSliceLength(sliceLengthVarInit, out IndexKind endIndexKind, out ILInstruction endIndexLoad, containerLengthVar, ref containerVar, startOffsetVar)) + return; + if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind, endIndexKind)) { + return; + } + if (rangeVar != null) { + if (!MatchIndexFromRange(startIndexKind, startIndexLoad, rangeVar, "get_Start")) + return; + if (!MatchIndexFromRange(endIndexKind, endIndexLoad, rangeVar, "get_End")) + return; + } + if (!(sliceLengthVar.LoadInstructions.Single().Parent is CallInstruction call)) + return; + if (call.Method.Name == "Slice") { + // OK, custom class slicing + } else if (call.Method.Name == "Substring" && call.Method.DeclaringType.IsKnownType(KnownTypeCode.String)) { + // OK, string slicing } else { - // stloc offsetVar(binary.sub.i4(ldloc containerLengthVar, startIndexLoad)) - // complex_expr(call get_Item(ldloc container, ldloc startOffsetVar)) - // --> - // complex_expr(call get_Item(ldloc container, newobj System.Index(startIndexLoad, fromEnd: true))) - Debug.Assert(startIndexKind == IndexKind.FromEnd); - newCall.Arguments.Add(new NewObj(indexCtor) { Arguments = { startIndexLoad, new LdcI4(1) } }); + return; + } + if (call.Method.IsExtensionMethod) + return; + if (call.Method.Parameters.Count != 2) + return; + if (!call.Method.Parameters.All(p => p.Type.IsKnownType(KnownTypeCode.Int32))) + return; + if (call.Arguments.Count != 3) + return; + if (!MatchContainerVar(call.Arguments[0], ref containerVar)) + return; + if (!call.Arguments[1].MatchLdLoc(startOffsetVar)) + return; + if (!call.Arguments[2].MatchLdLoc(sliceLengthVar)) + return; + if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, slicing: true)) + return; + if (startIndexKind == IndexKind.FromStart && endIndexKind == IndexKind.FromStart) { + // It's possible we actually have a startIndex/endIndex that involves the container length, + // but we couldn't detect it yet because the statement initializing the containerLengthVar is + // not yet part of the region to be transformed. + // If we transform now, we'd end up with: + // int length = span.Length; + // Console.WriteLine(span[(length - GetInt(1))..(length - GetInt(2))].ToString()); + // which is correct but unnecessarily complex. + // So we peek ahead at the next instruction to be transformed: + if (startPos > 0 && MatchContainerLengthStore(block.Instructions[startPos - 1], out _, ref containerVar)) { + // Looks like the transform would be able do to a better job including that previous instruction, + // so let's avoid transforming just yet. + return; + } + // Something similar happens with the rangeVar: + if (startPos > 0 && block.Instructions[startPos - 1] is StLoc stloc && stloc.Variable.Type.IsKnownType(KnownTypeCode.Range)) { + return; + } + } + var specialMethods = new IndexMethods(context.TypeSystem); + if (!specialMethods.AllValid) + return; + + context.Step($"{call.Method.Name} sliced with {startIndexKind}..{endIndexKind}", call); + var newMethod = new SyntheticRangeIndexAccessor(call.Method, specialMethods.RangeType, slicing: true); + var newCall = CallInstruction.Create(call.OpCode, newMethod); + newCall.ConstrainedTo = call.ConstrainedTo; + newCall.ILStackWasEmpty = call.ILStackWasEmpty; + newCall.Arguments.Add(call.Arguments[0]); + if (rangeVar != null) { + newCall.Arguments.Add(rangeVarInit); + } else { + var rangeCtorCall = new NewObj(specialMethods.RangeCtor); + rangeCtorCall.Arguments.Add(MakeIndex(startIndexKind, startIndexLoad, specialMethods)); + rangeCtorCall.Arguments.Add(MakeIndex(endIndexKind, endIndexLoad, specialMethods)); + newCall.Arguments.Add(rangeCtorCall); } - newCall.Arguments.AddRange(call.Arguments.Skip(2)); newCall.AddILRange(call); for (int i = startPos; i < pos; i++) { newCall.AddILRange(block.Instructions[i]); @@ -172,13 +301,74 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } + /// + /// 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) + expectedUses += 1; + if (endIndexKind != IndexKind.FromStart) + 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) { + // stloc offsetVar(binary.sub.i4(call get_Length/get_Count(ldloc container), startIndexLoad)) + // complex_expr(call get_Item(ldloc container, ldloc startOffsetVar)) + // --> + // complex_expr(call get_Item(ldloc container, newobj System.Index(startIndexLoad, fromEnd: true))) + return new NewObj(specialMethods.IndexCtor) { Arguments = { indexLoad, new LdcI4(1) } }; + } else { + Debug.Assert(indexKind == IndexKind.FromStart); + return new Call(specialMethods.IndexImplicitConv) { Arguments = { indexLoad } }; + } + } + /// /// Gets whether the C# compiler will call `container[int]` when using `container[Index]`. /// - private bool CSharpWillGenerateIndexer(IType declaringType) + private bool CSharpWillGenerateIndexer(IType declaringType, bool slicing) { bool foundInt32Overload = false; bool foundIndexOverload = false; + bool foundRangeOverload = false; bool foundCountProperty = false; foreach (var prop in declaringType.GetProperties(p => p.IsIndexer || (p.Name == "Length" || p.Name == "Count"))) { if (prop.IsIndexer && prop.Parameters.Count == 1) { @@ -187,21 +377,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms foundInt32Overload = true; } else if (p.Type.IsKnownType(KnownTypeCode.Index)) { foundIndexOverload = true; + } else if (p.Type.IsKnownType(KnownTypeCode.Range)) { + foundRangeOverload = true; } - } else if (prop.Name == "Length" || prop.Name=="Count") { + } else if (prop.Name == "Length" || prop.Name == "Count") { foundCountProperty = true; } } - return foundInt32Overload && foundCountProperty && !foundIndexOverload; + if (slicing) { + return /* foundSlicingMethod && */ foundCountProperty && !foundRangeOverload; + } else { + return foundInt32Overload && foundCountProperty && !foundIndexOverload; + } } /// /// Matches the instruction: /// stloc containerLengthVar(call get_Length/get_Count(ldloc containerVar)) /// - static bool MatchContainerLengthStore(ILInstruction inst, out ILVariable lengthVar, out ILVariable containerVar) + static bool MatchContainerLengthStore(ILInstruction inst, out ILVariable lengthVar, ref ILVariable containerVar) { - containerVar = null; if (!inst.MatchStLoc(out lengthVar, out var init)) return false; if (!(lengthVar.IsSingleDefinition && lengthVar.StackType == StackType.I4)) @@ -242,16 +437,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (call.Arguments.Count != 1) return false; + return MatchContainerVar(call.Arguments[0], ref containerVar); + } + + static bool MatchContainerVar(ILInstruction inst, ref ILVariable containerVar) + { if (containerVar != null) { - return call.Arguments[0].MatchLdLoc(containerVar) || call.Arguments[0].MatchLdLoca(containerVar); + return inst.MatchLdLoc(containerVar) || inst.MatchLdLoca(containerVar); } else { - return call.Arguments[0].MatchLdLoc(out containerVar) || call.Arguments[0].MatchLdLoca(out containerVar); + return inst.MatchLdLoc(out containerVar) || inst.MatchLdLoca(out containerVar); } } enum IndexKind { - None, + /// + /// indexLoad is an integer, from the start of the container + /// + FromStart, /// /// indexLoad is loading the address of a System.Index struct /// @@ -264,40 +467,58 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// Matches an instruction computing an offset: - /// stloc offsetVar(call System.Index.GetOffset(indexLoad, ldloc containerLengthVar)) + /// call System.Index.GetOffset(indexLoad, ldloc containerLengthVar) /// or - /// stloc offsetVar(binary.sub.i4(ldloc containerLengthVar, indexLoad)) + /// binary.sub.i4(ldloc containerLengthVar, indexLoad) + /// + /// Anything else not matching these patterns is interpreted as an `int` expression from the start of the container. /// - static IndexKind MatchGetOffset(ILInstruction inst, out ILVariable offsetVar, out ILInstruction indexLoad, + static IndexKind MatchGetOffset(ILInstruction inst, out ILInstruction indexLoad, ILVariable containerLengthVar, ref ILVariable containerVar) { - indexLoad = null; - if (!inst.MatchStLoc(out offsetVar, out var offsetValue)) - return IndexKind.None; - if (!(offsetVar.IsSingleDefinition && offsetVar.StackType == StackType.I4)) - return IndexKind.None; - if (offsetValue is CallInstruction call) { + indexLoad = inst; + if (inst is CallInstruction call) { // call System.Index.GetOffset(indexLoad, ldloc containerLengthVar) if (call.Method.Name != "GetOffset") - return IndexKind.None; + return IndexKind.FromStart; if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.Index)) - return IndexKind.None; + return IndexKind.FromStart; if (call.Arguments.Count != 2) - return IndexKind.None; + return IndexKind.FromStart; if (!MatchContainerLength(call.Arguments[1], containerLengthVar, ref containerVar)) - return IndexKind.None; + return IndexKind.FromStart; indexLoad = call.Arguments[0]; return IndexKind.RefSystemIndex; - } else if (offsetValue is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub) { + } else if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub) { if (bni.CheckForOverflow || bni.ResultType != StackType.I4 || bni.IsLifted) - return IndexKind.None; + return IndexKind.FromStart; // binary.sub.i4(ldloc containerLengthVar, indexLoad) if (!MatchContainerLength(bni.Left, containerLengthVar, ref containerVar)) - return IndexKind.None; + return IndexKind.FromStart; indexLoad = bni.Right; return IndexKind.FromEnd; } else { - return IndexKind.None; + return IndexKind.FromStart; + } + } + + /// + /// 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 (!bni.Right.MatchLdLoc(startOffsetVar)) + return false; + endIndexKind = MatchGetOffset(bni.Left, out endIndexLoad, containerLengthVar, ref containerVar); + return true; + } else { + return false; } } } diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs index 5ea2e8f10..9d79e1b79 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SyntheticRangeIndexer.cs @@ -39,19 +39,27 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation readonly IMethod underlyingMethod; readonly IType indexOrRangeType; readonly IReadOnlyList parameters; + readonly bool slicing; - public SyntheticRangeIndexAccessor(IMethod underlyingMethod, IType indexOrRangeType) + public SyntheticRangeIndexAccessor(IMethod underlyingMethod, IType indexOrRangeType, bool slicing) { Debug.Assert(underlyingMethod != null); Debug.Assert(indexOrRangeType != null); this.underlyingMethod = underlyingMethod; this.indexOrRangeType = indexOrRangeType; + this.slicing = slicing; var parameters = new List(); parameters.Add(new DefaultParameter(indexOrRangeType, "")); - parameters.AddRange(underlyingMethod.Parameters.Skip(1)); + if (slicing) { + Debug.Assert(underlyingMethod.Parameters.Count == 2); + } else { + parameters.AddRange(underlyingMethod.Parameters.Skip(1)); + } this.parameters = parameters; } + public bool IsSlicing => slicing; + bool IMethod.ReturnTypeIsRefReadOnly => underlyingMethod.ReturnTypeIsRefReadOnly; bool IMethod.ThisIsRefReadOnly => underlyingMethod.ThisIsRefReadOnly; @@ -96,7 +104,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation { return obj is SyntheticRangeIndexAccessor g && this.underlyingMethod.Equals(g.underlyingMethod) - && this.indexOrRangeType.Equals(g.indexOrRangeType); + && this.indexOrRangeType.Equals(g.indexOrRangeType) + && this.slicing == g.slicing; } public override int GetHashCode() @@ -117,12 +126,12 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation IMethod IMethod.Specialize(TypeParameterSubstitution substitution) { - return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType); + return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType, slicing); } IMember IMember.Specialize(TypeParameterSubstitution substitution) { - return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType); + return new SyntheticRangeIndexAccessor(underlyingMethod.Specialize(substitution), indexOrRangeType, slicing); } } } From 1926756cfa3f56ece61581fb7fa53b30fe46f33d Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 19 Apr 2020 19:51:12 +0200 Subject: [PATCH 18/44] Handle the special cases where the range does not have a start or endpoint. --- .../TestCases/Pretty/IndexRangeTest.cs | 62 ++++++- .../IL/Transforms/IndexRangeTransform.cs | 167 +++++++++++++----- 2 files changed, 173 insertions(+), 56 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs index 442e9fa72..c4c7f6a6c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/IndexRangeTest.cs @@ -75,6 +75,26 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { 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() { @@ -118,12 +138,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void UseRange() { Console.WriteLine(GetArray()[GetRange()]); -#if TODO //Console.WriteLine(GetList()[GetRange()]); // fails to compile Console.WriteLine(GetSpan()[GetRange()].ToString()); Console.WriteLine(GetString()[GetRange()]); Console.WriteLine(new CustomList()[GetRange()]); -#endif Console.WriteLine(new CustomList2()[GetRange()]); } public static void UseNewRangeFromIndex() @@ -175,36 +193,64 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void UseNewRangeFromIntegers_OnlyEndPoint() { Console.WriteLine(GetArray()[..GetInt(2)]); -#if TODO //Console.WriteLine(GetList()[..GetInt()]); // fails to compile Console.WriteLine(GetSpan()[..GetInt(2)].ToString()); Console.WriteLine(GetString()[..GetInt(2)]); Console.WriteLine(new CustomList()[..GetInt(2)]); -#endif 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)..]); -#if TODO //Console.WriteLine(GetList()[GetInt()..]); // fails to compile Console.WriteLine(GetSpan()[GetInt(1)..].ToString()); Console.WriteLine(GetString()[GetInt(1)..]); Console.WriteLine(new CustomList()[GetInt(1)..]); -#endif 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()[..]); -#if TODO //Console.WriteLine(GetList()[..]); // fails to compile Console.WriteLine(GetSpan()[..].ToString()); Console.WriteLine(GetString()[..]); Console.WriteLine(new CustomList()[..]); -#endif Console.WriteLine(new CustomList2()[..]); } diff --git a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs index 72bf4cd6a..2140c5416 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/IndexRangeTransform.cs @@ -63,7 +63,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!(bni.Left.MatchLdLen(StackType.I4, out var arrayLoad) && arrayLoad.MatchLdLoc(array))) return false; var indexMethods = new IndexMethods(context.TypeSystem); - if (!indexMethods.AllValid) + 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) @@ -84,7 +84,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms public readonly IMethod RangeCtor; public IType IndexType => IndexCtor?.DeclaringType; public IType RangeType => RangeCtor?.DeclaringType; - public bool AllValid => IndexCtor != null && IndexImplicitConv != null && RangeCtor != null; + 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) { @@ -96,7 +100,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } foreach (var op in indexType.GetMethods(m => m.IsOperator && m.Name == "op_Implicit")) { - if (op.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)) { + if (op.Parameters.Count == 1 && op.Parameters[0].Type.IsKnownType(KnownTypeCode.Int32)) { this.IndexImplicitConv = op; } } @@ -107,6 +111,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms 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; + } } } @@ -152,18 +167,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (rangeVar != null) return; - if (startIndexKind == IndexKind.FromStart) { - // FromStart is only relevant for slicing; indexing from the start does not involve System.Index at all. - return; - } - if (!CheckContainerLengthVariableUseCount(containerLengthVar, startIndexKind)) { - return; - } - // startOffsetVar might be used deep inside a complex statement, ensure we can inline up to that point: - for (int i = startPos; i < pos; i++) { - if (!ILInlining.CanInlineInto(block.Instructions[pos], startOffsetVar, block.Instructions[i])) - return; - } if (!(startOffsetVar.LoadInstructions.Single().Parent is CallInstruction call)) return; if (call.Method.AccessorKind == System.Reflection.MethodSemanticsAttributes.Getter && call.Arguments.Count == 2) { @@ -176,9 +179,24 @@ namespace ICSharpCode.Decompiler.IL.Transforms 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)) @@ -186,7 +204,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!call.Arguments[1].MatchLdLoc(startOffsetVar)) return; var specialMethods = new IndexMethods(context.TypeSystem); - if (!specialMethods.AllValid) + if (!specialMethods.IsValid) return; if (!CSharpWillGenerateIndexer(call.Method.DeclaringType, slicing: false)) return; @@ -207,16 +225,31 @@ namespace ICSharpCode.Decompiler.IL.Transforms block.Instructions.RemoveRange(startPos, pos - startPos); } - void TransformSlicing() + void TransformSlicing(bool sliceLengthWasMisdetectedAsStartOffset = false) { - // stloc containerLengthVar(call get_Length(ldloc containerVar)) - // stloc startOffset(call GetOffset(startIndexLoad, ldloc length)) - // -- we are here -- - // stloc sliceLengthVar(binary.sub.i4(call GetOffset(endIndexLoad, ldloc length), ldloc startOffset)) - // complex_expr(call Slice(ldloc containerVar, ldloc startOffset, ldloc sliceLength)) - if (!block.Instructions[pos].MatchStLoc(out var sliceLengthVar, out var sliceLengthVarInit)) - return; - pos++; + 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)) @@ -232,25 +265,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms } if (!(sliceLengthVar.LoadInstructions.Single().Parent is CallInstruction call)) return; - if (call.Method.Name == "Slice") { - // OK, custom class slicing - } else if (call.Method.Name == "Substring" && call.Method.DeclaringType.IsKnownType(KnownTypeCode.String)) { - // OK, string slicing - } else { - return; - } - if (call.Method.IsExtensionMethod) - return; - if (call.Method.Parameters.Count != 2) - return; - if (!call.Method.Parameters.All(p => p.Type.IsKnownType(KnownTypeCode.Int32))) + if (!IsSlicingMethod(call.Method)) return; if (call.Arguments.Count != 3) return; if (!MatchContainerVar(call.Arguments[0], ref containerVar)) return; - if (!call.Arguments[1].MatchLdLoc(startOffsetVar)) - 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)) @@ -275,7 +303,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } var specialMethods = new IndexMethods(context.TypeSystem); - if (!specialMethods.AllValid) + if (!specialMethods.IsValid) return; context.Step($"{call.Method.Name} sliced with {startIndexKind}..{endIndexKind}", call); @@ -286,6 +314,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms 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)); @@ -301,15 +339,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } + 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) + if (startIndexKind != IndexKind.FromStart && startIndexKind != IndexKind.TheStart) expectedUses += 1; - if (endIndexKind != IndexKind.FromStart) + if (endIndexKind != IndexKind.FromStart && endIndexKind != IndexKind.TheStart) expectedUses += 1; if (containerLengthVar != null) { return containerLengthVar.LoadCount == expectedUses; @@ -349,14 +399,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms // --> // complex_expr(call get_Item(ldloc container, ldobj startIndexLoad)) return new LdObj(indexLoad, specialMethods.IndexType); - } else if (indexKind == IndexKind.FromEnd) { + } 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); + Debug.Assert(indexKind == IndexKind.FromStart || indexKind == IndexKind.TheStart); return new Call(specialMethods.IndexImplicitConv) { Arguments = { indexLoad } }; } } @@ -462,7 +512,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// indexLoad is an integer, from the end of the container /// - FromEnd + 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, } /// @@ -477,7 +535,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms ILVariable containerLengthVar, ref ILVariable containerVar) { indexLoad = inst; - if (inst is CallInstruction call) { + 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; @@ -513,10 +574,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (inst is BinaryNumericInstruction bni && bni.Operator == BinaryNumericOperator.Sub) { if (bni.CheckForOverflow || bni.ResultType != StackType.I4 || bni.IsLifted) return false; - if (!bni.Right.MatchLdLoc(startOffsetVar)) - return false; + 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; } From 1303c540868b6749c5e676415604747776aa532c Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 26 Apr 2020 12:04:02 +0200 Subject: [PATCH 19/44] Exactly match load, store and address counts of defaultTemporary. --- .../IL/Transforms/NullPropagationTransform.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs index 98fcd8cf1..63b3c5752 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs @@ -411,10 +411,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms // stloc defaultTemporary(default.value type) if (!(block.Instructions[pos + 1].MatchStLoc(out var defaultTemporary, out var defaultExpression) && defaultExpression.MatchDefaultValue(out var type))) return false; - // Some variables are reused by the compiler therefore we cannot check for absolute numbers. See for example ValueTuple`8.ToString - // LoadCount must be == StoreCount and LoadCount must be 2x AddressCount. - // Note: if successful, this transform removes exactly 2 store, 2 load and 1 addressof instruction. - if (!(defaultTemporary.Kind == VariableKind.Local && defaultTemporary.LoadCount == defaultTemporary.StoreCount && defaultTemporary.AddressCount * 2 == defaultTemporary.StoreCount)) + // 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))) From effc49c479e3cb9058cc31b1643df4a46b7c3e00 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 26 Apr 2020 14:29:11 +0200 Subject: [PATCH 20/44] Move TransformNullPropagationOnUnconstrainedGenericExpression into NullPropagationTransform and make use of IsValidAccessChain --- .../IL/Transforms/NullPropagationTransform.cs | 161 +++++++----------- 1 file changed, 64 insertions(+), 97 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs index 63b3c5752..5c4660016 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; @@ -142,17 +142,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 target, out var value, out var rewrapPoint, out var endBlock)) { + context.Step("TransformNullPropagationOnUnconstrainedGenericExpression", block); + // A successful match was found: + // 1. The 'target' instruction, that is, the instruction where the actual 'null-propagating' call happens: + // .Call() is replaced with ?.Call() + // 2. The 'value' instruction, that is, the instruction that produces the value: + // It is inlined at the location of 'target'. + // 3. The 'rewrapPoint' instruction is an ancestor of the call that is used as location for the NullableRewrap instruction + + // Remove the fallback conditions and blocks + block.Instructions.RemoveRange(pos, 3); + // inline value and wrap it in a NullableUnwrap instruction to produce 'value?'. + target.ReplaceWith(new NullableUnwrap(value.ResultType, value, refInput: true)); + + var siblings = rewrapPoint.Parent.Children; + int index = rewrapPoint.ChildIndex; + // remove Nullable-ctor, if necessary + if (NullableLiftingTransform.MatchNullableCtor(rewrapPoint, out var utype, out var arg) && arg.InferType(context.TypeSystem).Equals(utype)) { + rewrapPoint = arg; + } + // insert a NullableRewrap instruction at the 'rewrapPoint' + siblings[index] = new NullableRewrap(rewrapPoint); + // 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 + 1); + endBlock.Remove(); } } } @@ -310,60 +339,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms } oldParentChildren[oldChildIndex] = replacement; } - } - - class NullPropagationStatementTransform : IStatementTransform - { - public void Run(Block block, int pos, StatementTransformContext context) - { - if (!context.Settings.NullPropagation) - return; - new NullPropagationTransform(context).RunStatements(block, pos); - if (TransformNullPropagationOnUnconstrainedGenericExpression(block, pos, out var target, out var value, out var rewrapPoint, out var endBlock)) { - context.Step("TransformNullPropagationOnUnconstrainedGenericExpression", block); - // A successful match was found: - // 1. The 'target' instruction, that is, the instruction where the actual 'null-propagating' call happens: - // .Call() is replaced with ?.Call() - // 2. The 'value' instruction, that is, the instruction that produces the value: - // It is inlined at the location of 'target'. - // 3. The 'rewrapPoint' instruction is an ancestor of the call that is used as location for the NullableRewrap instruction, - // if the expression does not yet contain a NullableRewrap. - - // First try to find a NullableRewrap instruction - bool needsRewrap = true; - var tmp = target; - while (needsRewrap && tmp != null) { - if (tmp is NullableRewrap) { - needsRewrap = false; - break; - } - tmp = tmp.Parent; - } - // Remove the fallback conditions and blocks - block.Instructions.RemoveRange(pos, 3); - // inline value and wrap it in a NullableUnwrap instruction to produce 'value?'. - target.ReplaceWith(new NullableUnwrap(value.ResultType, value, refInput: true)); - - var siblings = rewrapPoint.Parent.Children; - int index = rewrapPoint.ChildIndex; - // remove Nullable-ctor, if necessary - if (NullableLiftingTransform.MatchNullableCtor(rewrapPoint, out var utype, out var arg) && arg.InferType(context.TypeSystem).Equals(utype)) { - rewrapPoint = arg; - } - // if the ancestors do not yet have a NullableRewrap instruction - // insert it at the 'rewrapPoint'. - if (needsRewrap) { - siblings[index] = new NullableRewrap(rewrapPoint); - } - // 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 + 1); - endBlock.Remove(); - } - } - } // stloc valueTemporary(valueExpression) // stloc defaultTemporary(default.value type) @@ -395,11 +370,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms // => // leave (nullable.rewrap(constrained[type].call_instruction(nullable.unwrap(valueExpression), ...))) private bool TransformNullPropagationOnUnconstrainedGenericExpression(Block block, int pos, - out ILInstruction target, out ILInstruction value, out ILInstruction rewrapPoint, out Block endBlock) + out ILInstruction target, out ILInstruction value, out ILInstruction nonNullInst, out Block endBlock) { target = null; value = null; - rewrapPoint = null; + nonNullInst = null; endBlock = null; if (pos + 3 >= block.Instructions.Count) return false; @@ -417,30 +392,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms // 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, valueTemporary, defaultTemporary, fallbackBlock1, out rewrapPoint, out var call, out endBlock) && !MatchLeaveResult(block, pos, type, valueTemporary, defaultTemporary, fallbackBlock1, out rewrapPoint, out call)) + if (!MatchStLocResultTemporary(block, pos, type, valueTemporary, defaultTemporary, fallbackBlock1, out nonNullInst, out target, out endBlock) + && !MatchLeaveResult(block, pos, type, valueTemporary, defaultTemporary, fallbackBlock1, out nonNullInst, out target)) return false; - target = call.Arguments[0]; return true; } // stloc resultTemporary(constrained[type].call_instruction(ldloc valueTemporary, ...)) // br IL_0035 - private bool MatchStLocResultTemporary(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock1, out ILInstruction rewrapPoint, out CallInstruction call, out Block endBlock) + private bool MatchStLocResultTemporary(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock1, out ILInstruction nonNullInst, out ILInstruction finalLoad, out Block endBlock) { - call = null; endBlock = null; - rewrapPoint = null; + nonNullInst = null; + finalLoad = null; if (pos + 4 >= block.Instructions.Count) return false; // stloc resultTemporary(constrained[type].call_instruction(ldloc valueTemporary, ...)) - if (!(block.Instructions[pos + 3].MatchStLoc(out var resultTemporary, out rewrapPoint))) + if (!(block.Instructions[pos + 3].MatchStLoc(out var resultTemporary, out nonNullInst))) return false; - var loadInCall = FindLoadInExpression(valueTemporary, rewrapPoint); - if (!(loadInCall != null && loadInCall.Ancestors.OfType().FirstOrDefault() is CallInstruction c)) - return false; - if (!(c.Arguments.Count > 0 && loadInCall.IsDescendantOf(c.Arguments[0]))) + if (!IsValidAccessChain(valueTemporary, Mode.ReferenceType, nonNullInst, out finalLoad)) return false; // br IL_0035 if (!(block.Instructions[pos + 4].MatchBranch(out endBlock))) @@ -449,41 +421,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!(fallbackBlock1 is Block b && IsFallbackBlock(b, type, valueTemporary, defaultTemporary, resultTemporary, endBlock))) return false; - call = c; return true; } - private bool MatchLeaveResult(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock, out ILInstruction rewrapPoint, out CallInstruction call) + private bool MatchLeaveResult(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock, out ILInstruction rewrapPoint, out ILInstruction finalLoad) { - call = null; rewrapPoint = null; + finalLoad = null; // leave (constrained[type].call_instruction(ldloc valueTemporary, ...)) if (!(block.Instructions[pos + 3] is Leave leave && leave.IsLeavingFunction)) return false; rewrapPoint = leave.Value; - var loadInCall = FindLoadInExpression(valueTemporary, rewrapPoint); - if (!(loadInCall != null && loadInCall.Ancestors.OfType().FirstOrDefault() is CallInstruction c)) - return false; - if (!(c.Arguments.Count > 0 && loadInCall.IsDescendantOf(c.Arguments[0]))) + if (!IsValidAccessChain(valueTemporary, Mode.ReferenceType, rewrapPoint, out finalLoad)) return false; // Analyze Block fallbackBlock if (!(fallbackBlock is Block b && IsFallbackBlock(b, type, valueTemporary, defaultTemporary, null, leave.TargetContainer))) return false; - - call = c; return true; } - private ILInstruction FindLoadInExpression(ILVariable variable, ILInstruction expression) - { - foreach (var load in variable.LoadInstructions) { - if (load.IsDescendantOf(expression)) - return load; - } - return null; - } - // Block fallbackBlock { // stloc defaultTemporary(ldobj type(ldloc valueTemporary)) // stloc valueTemporary(ldloca defaultTemporary) @@ -522,7 +479,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms } else if (!(fallbackInst is Leave fallbackLeave && endBlockOrLeaveContainer is BlockContainer leaveContainer && fallbackLeave.TargetContainer == leaveContainer && MatchDefaultValueOrLdNull(fallbackLeave.Value))) return false; - + return true; } @@ -531,4 +488,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms return inst.MatchLdNull() || inst.MatchDefaultValue(out _); } } + + class NullPropagationStatementTransform : IStatementTransform + { + public void Run(Block block, int pos, StatementTransformContext context) + { + if (!context.Settings.NullPropagation) + return; + new NullPropagationTransform(context).RunStatements(block, pos); + } + } } From 794be9a5b4ed4729d926fe30572c710b0d2fc72a Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 29 Apr 2020 21:03:48 +0200 Subject: [PATCH 21/44] Fix SequencePointBuilder.VisitCatchClause: do not create a sequence point from the catch-token to the closing brace. --- ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs index a7190e086..ce34451a7 100644 --- a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs @@ -262,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); } @@ -327,7 +330,6 @@ namespace ICSharpCode.Decompiler.CSharp public override void VisitCatchClause(CatchClause catchClause) { - StartSequencePoint(catchClause); if (catchClause.Condition.IsNull) { var tryCatchHandler = catchClause.Annotation(); if (!tryCatchHandler.ExceptionSpecifierILRange.IsEmpty) { @@ -341,8 +343,7 @@ namespace ICSharpCode.Decompiler.CSharp AddToSequencePoint(catchClause.Condition); EndSequencePoint(catchClause.WhenToken.StartLocation, catchClause.CondRParToken.EndLocation); } - catchClause.Body.AcceptVisitor(this); - EndSequencePoint(catchClause.StartLocation, catchClause.EndLocation); + VisitAsSequencePoint(catchClause.Body); } /// From 9c8df1d949748de6a2929fda2996a3d875897e06 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 29 Apr 2020 21:14:09 +0200 Subject: [PATCH 22/44] Fix assignment of ILRanges in ExpressionTransforms.TransformCatchVariable --- .../IL/Transforms/ExpressionTransforms.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs index c299eda0b..a011b0051 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs @@ -713,7 +713,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// void TransformCatchVariable(TryCatchHandler handler, Block entryPoint, bool isCatchBlock) { - List ilOffsets = null; if (!handler.Variable.IsSingleDefinition || handler.Variable.LoadCount != 1) return; // handle.Variable already has non-trivial uses if (!entryPoint.Instructions[0].MatchStLoc(out var exceptionVar, out var exceptionSlotLoad)) { @@ -723,7 +722,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (inlinedUnboxAny.Type.Equals(handler.Variable.Type)) { context.Step("TransformCatchVariable - remove inlined UnboxAny", inlinedUnboxAny); inlinedUnboxAny.ReplaceWith(inlinedUnboxAny.Argument); - (ilOffsets ?? (ilOffsets = new List())).AddRange(inlinedUnboxAny.ILRanges); + foreach (var range in inlinedUnboxAny.ILRanges) + handler.AddExceptionSpecifierILRange(range); } } return; @@ -751,8 +751,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms exceptionVar.Type = handler.Variable.Type; handler.Variable = exceptionVar; if (isCatchBlock) { - (ilOffsets ?? (ilOffsets = new List())).AddRange(entryPoint.Instructions[0].Descendants.SelectMany(o => o.ILRanges)); - foreach (var offset in ilOffsets) + foreach (var offset in entryPoint.Instructions[0].Descendants.SelectMany(o => o.ILRanges)) handler.AddExceptionSpecifierILRange(offset); } entryPoint.Instructions.RemoveAt(0); From 2601d1791994b9741f2aa82c179b2f52e6a58b9e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 29 Apr 2020 21:21:31 +0200 Subject: [PATCH 23/44] Add clarifying comment to ExceptionSpecifierILRange --- ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs index 4b519af43..4e87bacc9 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs @@ -180,6 +180,7 @@ namespace ICSharpCode.Decompiler.IL /// /// 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; } From b114734128eb4da79b0321cea24d77c71b221564 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 28 Apr 2020 18:38:07 +0200 Subject: [PATCH 24/44] Add Mode.UnconstrainedType and implement TransformNullPropagationOnUnconstrainedGenericExpression using TryNullPropagation. --- .../TestCases/Pretty/NullPropagation.cs | 5 + .../IL/Transforms/NullPropagationTransform.cs | 110 ++++++++++-------- 2 files changed, 64 insertions(+), 51 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs index efb9ec366..6e927db63 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs @@ -100,6 +100,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty return Other?.Other.Other?.Other.Field1?.ToString()?.GetType().Name; } + public int? Test2() + { + return Field1?.ToString().Length ?? 42; + } + public int? GetTextLengthNRE() { return (Field1?.ToString()).Length; diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs index 5c4660016..19a10b6b9 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs @@ -59,6 +59,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// nullable type, used by reference (comparison is 'call get_HasValue(ldloc(testedVar))') /// NullableByReference, + /// + /// unconstrained generic type (see the pattern described in TransformNullPropagationOnUnconstrainedGenericExpression) + /// + UnconstrainedType, } /// @@ -154,35 +158,34 @@ namespace ICSharpCode.Decompiler.IL.Transforms } } } - if (TransformNullPropagationOnUnconstrainedGenericExpression(block, pos, out var target, out var value, out var rewrapPoint, out var endBlock)) { + 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); - // A successful match was found: - // 1. The 'target' instruction, that is, the instruction where the actual 'null-propagating' call happens: - // .Call() is replaced with ?.Call() - // 2. The 'value' instruction, that is, the instruction that produces the value: - // It is inlined at the location of 'target'. - // 3. The 'rewrapPoint' instruction is an ancestor of the call that is used as location for the NullableRewrap instruction - - // Remove the fallback conditions and blocks - block.Instructions.RemoveRange(pos, 3); - // inline value and wrap it in a NullableUnwrap instruction to produce 'value?'. - target.ReplaceWith(new NullableUnwrap(value.ResultType, value, refInput: true)); - - var siblings = rewrapPoint.Parent.Children; - int index = rewrapPoint.ChildIndex; - // remove Nullable-ctor, if necessary - if (NullableLiftingTransform.MatchNullableCtor(rewrapPoint, out var utype, out var arg) && arg.InferType(context.TypeSystem).Equals(utype)) { - rewrapPoint = arg; + 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(); } - // insert a NullableRewrap instruction at the 'rewrapPoint' - siblings[index] = new NullableRewrap(rewrapPoint); + // 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 + 1); + block.Instructions.RemoveAt(pos + 2); endBlock.Remove(); } + ILInlining.InlineIfPossible(block, pos, context); } } @@ -290,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)); } @@ -334,6 +340,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms refInput: true ).WithILRange(varLoad); break; + case Mode.UnconstrainedType: + // Wrap varLoad in nullable.unwrap: + replacement = new NullableUnwrap(varLoad.ResultType, varLoad, refInput: varLoad.ResultType == StackType.Ref); + break; default: throw new ArgumentOutOfRangeException(nameof(mode)); } @@ -346,7 +356,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms // stloc defaultTemporary(ldobj type(ldloc valueTemporary)) // stloc valueTemporary(ldloca defaultTemporary) // if (comp.o(ldloc defaultTemporary == ldnull)) Block fallbackBlock2 { - // stloc resultTemporary(ldnull) + // stloc resultTemporary(nullInst) // br endBlock // } // } @@ -363,23 +373,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms // stloc defaultTemporary(ldobj type(ldloc valueTemporary)) // stloc valueTemporary(ldloca defaultTemporary) // if (comp.o(ldloc defaultTemporary == ldnull)) Block fallbackBlock2 { - // leave(ldnull) + // leave(nullInst) // } // } // leave (constrained[type].call_instruction(ldloc valueTemporary, ...)) // => // leave (nullable.rewrap(constrained[type].call_instruction(nullable.unwrap(valueExpression), ...))) private bool TransformNullPropagationOnUnconstrainedGenericExpression(Block block, int pos, - out ILInstruction target, out ILInstruction value, out ILInstruction nonNullInst, out Block endBlock) + out ILVariable valueTemporary, out ILInstruction nonNullInst, out ILInstruction nullInst, out Block endBlock) { - target = null; - value = null; + valueTemporary = null; nonNullInst = null; + nullInst = null; endBlock = null; if (pos + 3 >= block.Instructions.Count) return false; // stloc valueTemporary(valueExpression) - if (!(block.Instructions[pos].MatchStLoc(out var valueTemporary, out value))) + if (!block.Instructions[pos].MatchStLoc(out valueTemporary, out var value)) return false; if (!(valueTemporary.Kind == VariableKind.StackSlot && valueTemporary.LoadCount == 2 && valueTemporary.StoreCount == 2)) return false; @@ -392,51 +402,46 @@ namespace ICSharpCode.Decompiler.IL.Transforms // 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, valueTemporary, defaultTemporary, fallbackBlock1, out nonNullInst, out target, out endBlock) - && !MatchLeaveResult(block, pos, type, valueTemporary, defaultTemporary, fallbackBlock1, out nonNullInst, out target)) + if (!MatchStLocResultTemporary(block, pos, type, valueTemporary, defaultTemporary, fallbackBlock1, out nonNullInst, out nullInst, out endBlock) + && !MatchLeaveResult(block, pos, type, valueTemporary, defaultTemporary, fallbackBlock1, out nonNullInst, out nullInst)) return false; return true; } // stloc resultTemporary(constrained[type].call_instruction(ldloc valueTemporary, ...)) - // br IL_0035 - private bool MatchStLocResultTemporary(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock1, out ILInstruction nonNullInst, out ILInstruction finalLoad, out Block endBlock) + // br endBlock + private bool MatchStLocResultTemporary(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock, out ILInstruction nonNullInst, out ILInstruction nullInst, out Block endBlock) { endBlock = null; nonNullInst = null; - finalLoad = null; + nullInst = null; if (pos + 4 >= block.Instructions.Count) return false; - // stloc resultTemporary(constrained[type].call_instruction(ldloc valueTemporary, ...)) if (!(block.Instructions[pos + 3].MatchStLoc(out var resultTemporary, out nonNullInst))) return false; - if (!IsValidAccessChain(valueTemporary, Mode.ReferenceType, nonNullInst, out finalLoad)) - return false; - // br IL_0035 + // br endBlock if (!(block.Instructions[pos + 4].MatchBranch(out endBlock))) return false; // Analyze Block fallbackBlock - if (!(fallbackBlock1 is Block b && IsFallbackBlock(b, type, valueTemporary, defaultTemporary, resultTemporary, endBlock))) + if (!(fallbackBlock is Block b && IsFallbackBlock(b, type, valueTemporary, defaultTemporary, resultTemporary, endBlock, out nullInst))) return false; return true; } - private bool MatchLeaveResult(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock, out ILInstruction rewrapPoint, out ILInstruction finalLoad) + private bool MatchLeaveResult(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock, out ILInstruction nonNullInst, out ILInstruction nullInst) { - rewrapPoint = null; - finalLoad = null; + nonNullInst = null; + nullInst = null; // leave (constrained[type].call_instruction(ldloc valueTemporary, ...)) if (!(block.Instructions[pos + 3] is Leave leave && leave.IsLeavingFunction)) return false; - rewrapPoint = leave.Value; - if (!IsValidAccessChain(valueTemporary, Mode.ReferenceType, rewrapPoint, out finalLoad)) - return false; + nonNullInst = leave.Value; // Analyze Block fallbackBlock - if (!(fallbackBlock is Block b && IsFallbackBlock(b, type, valueTemporary, defaultTemporary, null, leave.TargetContainer))) + if (!(fallbackBlock is Block b && IsFallbackBlock(b, type, valueTemporary, defaultTemporary, null, leave.TargetContainer, out nullInst))) return false; return true; } @@ -449,8 +454,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms // br endBlock // } // } - private bool IsFallbackBlock(Block block, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILVariable resultTemporary, ILInstruction endBlockOrLeaveContainer) + private bool IsFallbackBlock(Block block, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILVariable resultTemporary, ILInstruction endBlockOrLeaveContainer, out ILInstruction nullInst) { + nullInst = null; if (!(block.Instructions.Count == 3)) return false; // stloc defaultTemporary(ldobj type(ldloc valueTemporary)) @@ -465,21 +471,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms 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(ldnull) + // 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 var defaultValue) && MatchDefaultValueOrLdNull(defaultValue))) + 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 && MatchDefaultValueOrLdNull(fallbackLeave.Value))) - 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; } From 4558765941d67c475917e6d8c0f6e4a2b2b35d7b Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 30 Apr 2020 08:12:55 +0200 Subject: [PATCH 25/44] Change license headers of two remaining files to the correct MIT header. --- ILSpy/Languages/CSharpLexer.cs | 19 +++++++++++++++++-- ILSpy/TextView/BracketHighlightRenderer.cs | 19 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) 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/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; From 2f51f3125745d960b210a17372af0bb0025b5b52 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 30 Apr 2020 09:44:45 +0200 Subject: [PATCH 26/44] PDBGen: avoid assertion failure, if there are instructions that are not supported in C# (e.g. calli) --- .../CSharp/OutputVisitor/InsertMissingTokensDecorator.cs | 3 +++ 1 file changed, 3 insertions(+) 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); } From b282cb7274c5f5ef1797e1bd9a588f9f05a5ade7 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 1 May 2020 11:48:39 +0200 Subject: [PATCH 27/44] #1995: Remove registration of DebugStepsPaneModel in case of release builds. --- ILSpy/ViewModels/DebugStepsPaneModel.cs | 2 ++ 1 file changed, 2 insertions(+) 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"; From 7d8b9fee1e3bf9f2a5f1659c849063baceb6fd78 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Fri, 1 May 2020 16:33:03 +0200 Subject: [PATCH 28/44] Remove redundant code. --- .../IL/Transforms/NullPropagationTransform.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs index 19a10b6b9..79c969332 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs @@ -323,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; @@ -340,10 +341,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms refInput: true ).WithILRange(varLoad); break; - case Mode.UnconstrainedType: - // Wrap varLoad in nullable.unwrap: - replacement = new NullableUnwrap(varLoad.ResultType, varLoad, refInput: varLoad.ResultType == StackType.Ref); - break; default: throw new ArgumentOutOfRangeException(nameof(mode)); } @@ -490,11 +487,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms } return true; } - - private bool MatchDefaultValueOrLdNull(ILInstruction inst) - { - return inst.MatchLdNull() || inst.MatchDefaultValue(out _); - } } class NullPropagationStatementTransform : IStatementTransform From 571118583229c87cecb616295667416d461b5587 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 1 May 2020 17:37:22 +0200 Subject: [PATCH 29/44] Make sure that the code and the pattern described in the comment above are in sync. --- .../IL/Transforms/NullPropagationTransform.cs | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs index 79c969332..93d7f90f5 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs @@ -347,48 +347,48 @@ namespace ICSharpCode.Decompiler.IL.Transforms oldParentChildren[oldChildIndex] = replacement; } - // stloc valueTemporary(valueExpression) + // 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 valueTemporary)) - // stloc valueTemporary(ldloca defaultTemporary) + // 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 valueTemporary, ...)) + // stloc resultTemporary(constrained[type].call_instruction(ldloc target, ...)) // br endBlock // => - // stloc resultTemporary(nullable.rewrap(constrained[type].call_instruction(nullable.unwrap(valueExpression), ...))) + // stloc resultTemporary(nullable.rewrap(constrained[type].call_instruction(nullable.unwrap(targetInst), ...))) // // -or- // - // stloc valueTemporary(valueExpression) + // 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 valueTemporary)) - // stloc valueTemporary(ldloca defaultTemporary) + // 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 valueTemporary, ...)) + // leave (constrained[type].call_instruction(ldloc target, ...)) // => - // leave (nullable.rewrap(constrained[type].call_instruction(nullable.unwrap(valueExpression), ...))) + // leave (nullable.rewrap(constrained[type].call_instruction(nullable.unwrap(targetInst), ...))) private bool TransformNullPropagationOnUnconstrainedGenericExpression(Block block, int pos, - out ILVariable valueTemporary, out ILInstruction nonNullInst, out ILInstruction nullInst, out Block endBlock) + out ILVariable target, out ILInstruction nonNullInst, out ILInstruction nullInst, out Block endBlock) { - valueTemporary = null; + target = null; nonNullInst = null; nullInst = null; endBlock = null; if (pos + 3 >= block.Instructions.Count) return false; - // stloc valueTemporary(valueExpression) - if (!block.Instructions[pos].MatchStLoc(out valueTemporary, out var value)) + // stloc target(...) + if (!block.Instructions[pos].MatchStLoc(out target, out _)) return false; - if (!(valueTemporary.Kind == VariableKind.StackSlot && valueTemporary.LoadCount == 2 && valueTemporary.StoreCount == 2)) + 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))) @@ -399,15 +399,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms // 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, valueTemporary, defaultTemporary, fallbackBlock1, out nonNullInst, out nullInst, out endBlock) - && !MatchLeaveResult(block, pos, type, valueTemporary, defaultTemporary, fallbackBlock1, out nonNullInst, out nullInst)) + 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 valueTemporary, ...)) + // stloc resultTemporary(constrained[type].call_instruction(ldloc target, ...)) // br endBlock - private bool MatchStLocResultTemporary(Block block, int pos, IType type, ILVariable valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock, out ILInstruction nonNullInst, out ILInstruction nullInst, out Block 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; @@ -415,54 +415,54 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (pos + 4 >= block.Instructions.Count) return false; - // stloc resultTemporary(constrained[type].call_instruction(ldloc valueTemporary, ...)) + // 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, valueTemporary, defaultTemporary, resultTemporary, endBlock, out nullInst))) + 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 valueTemporary, ILVariable defaultTemporary, ILInstruction fallbackBlock, out ILInstruction nonNullInst, out ILInstruction nullInst) + 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 valueTemporary, ...)) + // 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, valueTemporary, defaultTemporary, null, leave.TargetContainer, out nullInst))) + 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 valueTemporary)) - // stloc valueTemporary(ldloca defaultTemporary) + // 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 valueTemporary, ILVariable defaultTemporary, ILVariable resultTemporary, ILInstruction endBlockOrLeaveContainer, out ILInstruction nullInst) + 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 valueTemporary)) - if (!(block.Instructions[0].MatchStLoc(defaultTemporary, out var valueExpression))) + // stloc defaultTemporary(ldobj type(ldloc target)) + if (!(block.Instructions[0].MatchStLoc(defaultTemporary, out var value))) return false; - if (!(valueExpression.MatchLdObj(out var target, out var t) && type.Equals(t) && target.MatchLdLoc(valueTemporary))) + if (!(value.MatchLdObj(out var inst, out var t) && type.Equals(t) && inst.MatchLdLoc(target))) return false; - // stloc valueTemporary(ldloca defaultTemporary) - if (!(block.Instructions[1].MatchStLoc(valueTemporary, out var defaultAddress) && defaultAddress.MatchLdLoca(defaultTemporary))) + // 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))) From 84a7c968c65911763dc9c8cf490e1c72b92a9d3d Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 6 May 2020 21:57:53 +0200 Subject: [PATCH 30/44] #1999: Add EscapeInvalidIdentifiers flag to allow the use of the EscapeInvalidIdentifiers transform when writing files to disk. We still show the unescaped identifiers in the ILSpy UI. --- ILSpy/DecompilationOptions.cs | 6 ++++++ ILSpy/Languages/CSharpLanguage.cs | 6 ++++++ ILSpy/TextView/DecompilerTextView.cs | 1 + 3 files changed, 13 insertions(+) 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/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/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 5f2f18344..354e0a4a8 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -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)) { From 0df7e1e4a588800b7f4f091b1505cac0feca6551 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 6 May 2020 22:14:39 +0200 Subject: [PATCH 31/44] Fix #1999: Compiler generated variables with weird names; by supporting a newer naming-convention used by mcs for anonymous delegates: See https://github.com/mono/mono/blob/c2795c9cb597901a0b1a7886583cc37bffef1271/mcs/mcs/delegate.cs#L808 --- ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 3fdf9f0ad..92f1594fd 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -366,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) From 5dc79da07f64e36e3401f6025b7af3dea85d0436 Mon Sep 17 00:00:00 2001 From: Andrew Au Date: Wed, 22 Apr 2020 23:05:47 -0700 Subject: [PATCH 32/44] Using import cell name to describe callees --- ILSpy.ReadyToRun/ReadyToRunLanguage.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs index 624cd2944..c87c8ce73 100644 --- a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs +++ b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs @@ -149,7 +149,12 @@ namespace ICSharpCode.ILSpy.ReadyToRun for (int i = 0; i < missingBytes; i++) output.Write(" "); output.Write(" "); - output.WriteLine(tempOutput.ToStringAndReset()); + output.Write(tempOutput.ToStringAndReset()); + if (instr.IsCallNearIndirect && reader.ImportCellNames.ContainsKey((int)instr.IPRelativeMemoryAddress)) { + WriteCommentLine(output, reader.ImportCellNames[(int)instr.IPRelativeMemoryAddress]); + } else { + output.WriteLine(); + } } output.WriteLine(); } From 32723c95adea0aff5621ed84e93fe045403ab607 Mon Sep 17 00:00:00 2001 From: Andrew Au Date: Fri, 24 Apr 2020 17:03:20 -0700 Subject: [PATCH 33/44] Experiment with adding a link --- ILSpy.ReadyToRun/ReadyToRunLanguage.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs index c87c8ce73..850be2645 100644 --- a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs +++ b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs @@ -84,7 +84,7 @@ namespace ICSharpCode.ILSpy.ReadyToRun 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, output, reader, readyToRunMethod, runtimeFunction, bitness, (ulong)runtimeFunction.StartAddress); } } } @@ -96,7 +96,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(IMethod method, ITextOutput output, ReadyToRunReader reader, ReadyToRunMethod readyToRunMethod, RuntimeFunction runtimeFunction, int bitness, ulong address) { WriteCommentLine(output, readyToRunMethod.SignatureString); byte[] codeBytes = new byte[runtimeFunction.Size]; @@ -151,7 +151,11 @@ namespace ICSharpCode.ILSpy.ReadyToRun output.Write(" "); output.Write(tempOutput.ToStringAndReset()); if (instr.IsCallNearIndirect && reader.ImportCellNames.ContainsKey((int)instr.IPRelativeMemoryAddress)) { - WriteCommentLine(output, reader.ImportCellNames[(int)instr.IPRelativeMemoryAddress]); + output.Write(" ;"); + // TODO: Get the right PEFile + // TODO: Get the right method def token + output.WriteReference(method.ParentModule.PEFile, System.Reflection.Metadata.Ecma335.MetadataTokens.Handle(0x06001e24), reader.ImportCellNames[(int)instr.IPRelativeMemoryAddress]); + output.WriteLine(); } else { output.WriteLine(); } From b935b4d065c09802518b025fc2d91a29ae5499be Mon Sep 17 00:00:00 2001 From: Andrew Au Date: Sun, 10 May 2020 16:34:32 -0700 Subject: [PATCH 34/44] Using the structured signature --- ILSpy.ReadyToRun/ReadyToRunLanguage.cs | 38 ++++++++++++++++++-------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs index 850be2645..0bf72a206 100644 --- a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs +++ b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs @@ -22,6 +22,7 @@ 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; @@ -128,14 +129,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}"); + } } } } @@ -150,11 +153,22 @@ namespace ICSharpCode.ILSpy.ReadyToRun output.Write(" "); output.Write(" "); output.Write(tempOutput.ToStringAndReset()); - if (instr.IsCallNearIndirect && reader.ImportCellNames.ContainsKey((int)instr.IPRelativeMemoryAddress)) { + int importCellAddress = (int)instr.IPRelativeMemoryAddress; + if (instr.IsCallNearIndirect && reader.ImportCellNames.ContainsKey(importCellAddress)) { output.Write(" ;"); - // TODO: Get the right PEFile - // TODO: Get the right method def token - output.WriteReference(method.ParentModule.PEFile, System.Reflection.Metadata.Ecma335.MetadataTokens.Handle(0x06001e24), reader.ImportCellNames[(int)instr.IPRelativeMemoryAddress]); + ReadyToRunSignature signature = reader.ImportSignatures[(int)instr.IPRelativeMemoryAddress]; + string formattedSignatureString = reader.ImportCellNames[importCellAddress]; + MethodDefEntrySignature methodDefSignature = signature as MethodDefEntrySignature; + MethodRefEntrySignature methodRefSignature = signature as MethodRefEntrySignature; + if (methodDefSignature != null) { + int methodDefToken = unchecked((int)methodDefSignature.MethodDefToken); + output.WriteReference(method.ParentModule.PEFile, MetadataTokens.Handle(methodDefToken), formattedSignatureString); + } else if (methodRefSignature != null) { + int methodRefToken = unchecked((int)methodRefSignature.MethodRefToken); + output.WriteReference(method.ParentModule.PEFile, MetadataTokens.Handle(methodRefToken), formattedSignatureString); + } else { + output.WriteLine(formattedSignatureString); + } output.WriteLine(); } else { output.WriteLine(); From 51bfebc6cd76b3a62e8ef7211340d4bcbed609a3 Mon Sep 17 00:00:00 2001 From: Andrew Au Date: Mon, 11 May 2020 11:24:18 -0700 Subject: [PATCH 35/44] Upgrade nuget package --- ILSpy.ReadyToRun/ILSpy.ReadyToRun.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 @@ - + From d27c0226b4443508a777e1977c43a1fe73ffbf11 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 20 May 2020 11:35:57 +0200 Subject: [PATCH 36/44] Fix #2010: NullReferenceException in SequencePointBuilder.VisitCatchClause() --- ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs index ce34451a7..7993932c9 100644 --- a/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/SequencePointBuilder.cs @@ -332,7 +332,7 @@ namespace ICSharpCode.Decompiler.CSharp { if (catchClause.Condition.IsNull) { var tryCatchHandler = catchClause.Annotation(); - if (!tryCatchHandler.ExceptionSpecifierILRange.IsEmpty) { + if (tryCatchHandler != null && !tryCatchHandler.ExceptionSpecifierILRange.IsEmpty) { StartSequencePoint(catchClause.CatchToken); var function = tryCatchHandler.Ancestors.OfType().FirstOrDefault(); AddToSequencePointRaw(function, new[] { tryCatchHandler.ExceptionSpecifierILRange }); From 332f5706c873e42c628f3154d74d54584a27f858 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 23 May 2020 17:55:06 +0200 Subject: [PATCH 37/44] Avoid explicit cast when working with unresolvedEntity.Handle. --- ILSpy/MainWindow.xaml.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index ef311f68a..804d1c7af 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -898,9 +898,10 @@ namespace ICSharpCode.ILSpy } } } - 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; From 0d390d604fd9b54d5ce2748302bd30696abc6122 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 23 May 2020 19:21:48 +0200 Subject: [PATCH 38/44] Fix #2013: Add option to extract self-contained executables (PublishSingleFile). --- ILSpy.AddIn/ILSpy.AddIn.csproj | 12 +++-- ILSpy/ILSpy.csproj | 2 + ILSpy/MainWindow.xaml.cs | 75 ++++++++++++++++++++++---- ILSpy/Properties/Resources.Designer.cs | 15 ++++++ ILSpy/Properties/Resources.resx | 9 ++++ 5 files changed, 99 insertions(+), 14 deletions(-) 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/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/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index ef311f68a..e48569701 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; @@ -994,15 +1001,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 +1059,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/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 10b87ca5c..51507cb90 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1605,6 +1605,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 d89524790..b8708bf57 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -867,4 +867,13 @@ Do you want to continue? Culture + + 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? + \ No newline at end of file From ad1d22222e48dfec7a66bed3cb99a7e33acc8512 Mon Sep 17 00:00:00 2001 From: Andrew Au Date: Sat, 23 May 2020 13:00:14 -0700 Subject: [PATCH 39/44] Changed to use a type switch and using the InstructionOutputExtensions.WriteTo() method to display decoded signature --- ILSpy.ReadyToRun/ReadyToRunLanguage.cs | 28 ++++++++++++++------------ 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs index 0bf72a206..d877dbfd6 100644 --- a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs +++ b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs @@ -27,6 +27,7 @@ using System.Reflection.PortableExecutable; using System.Runtime.CompilerServices; using Iced.Intel; using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Solution; using ICSharpCode.Decompiler.TypeSystem; @@ -85,7 +86,7 @@ namespace ICSharpCode.ILSpy.ReadyToRun if (cacheEntry.methodMap.TryGetValue(method.MetadataToken, out var methods)) { foreach (var readyToRunMethod in methods) { foreach (RuntimeFunction runtimeFunction in readyToRunMethod.RuntimeFunctions) { - Disassemble(method, output, reader, readyToRunMethod, runtimeFunction, bitness, (ulong)runtimeFunction.StartAddress); + Disassemble(method.ParentModule.PEFile, output, reader, readyToRunMethod, runtimeFunction, bitness, (ulong)runtimeFunction.StartAddress); } } } @@ -97,7 +98,7 @@ namespace ICSharpCode.ILSpy.ReadyToRun output.WriteLine("; " + comment); } - private void Disassemble(IMethod method, 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) { WriteCommentLine(output, readyToRunMethod.SignatureString); byte[] codeBytes = new byte[runtimeFunction.Size]; @@ -157,17 +158,18 @@ namespace ICSharpCode.ILSpy.ReadyToRun if (instr.IsCallNearIndirect && reader.ImportCellNames.ContainsKey(importCellAddress)) { output.Write(" ;"); ReadyToRunSignature signature = reader.ImportSignatures[(int)instr.IPRelativeMemoryAddress]; - string formattedSignatureString = reader.ImportCellNames[importCellAddress]; - MethodDefEntrySignature methodDefSignature = signature as MethodDefEntrySignature; - MethodRefEntrySignature methodRefSignature = signature as MethodRefEntrySignature; - if (methodDefSignature != null) { - int methodDefToken = unchecked((int)methodDefSignature.MethodDefToken); - output.WriteReference(method.ParentModule.PEFile, MetadataTokens.Handle(methodDefToken), formattedSignatureString); - } else if (methodRefSignature != null) { - int methodRefToken = unchecked((int)methodRefSignature.MethodRefToken); - output.WriteReference(method.ParentModule.PEFile, MetadataTokens.Handle(methodRefToken), formattedSignatureString); - } else { - output.WriteLine(formattedSignatureString); + switch(signature) { + case MethodDefEntrySignature methodDefSignature: + int methodDefToken = unchecked((int)methodDefSignature.MethodDefToken); + InstructionOutputExtensions.WriteTo((EntityHandle)MetadataTokens.Handle(methodDefToken), currentFile, output, null); + break; + case MethodRefEntrySignature methodRefSignature: + int methodRefToken = unchecked((int)methodRefSignature.MethodRefToken); + InstructionOutputExtensions.WriteTo((EntityHandle)MetadataTokens.Handle(methodRefToken), currentFile, output, null); + break; + default: + output.WriteLine(reader.ImportCellNames[importCellAddress]); + break; } output.WriteLine(); } else { From f90b2ef4f5180bcbe84ffbb56c2b0b1cc5102dcc Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 24 May 2020 09:45:42 +0200 Subject: [PATCH 40/44] R2R: Add linked metadata tokens to callee descriptions --- ILSpy.ReadyToRun/ReadyToRunLanguage.cs | 30 ++++++++++++++++++++------ 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs index d877dbfd6..0158677aa 100644 --- a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs +++ b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs @@ -83,10 +83,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(method.ParentModule.PEFile, output, reader, readyToRunMethod, runtimeFunction, bitness, (ulong)runtimeFunction.StartAddress); + Disassemble(method.ParentModule.PEFile, output, reader, readyToRunMethod, runtimeFunction, bitness, (ulong)runtimeFunction.StartAddress, showMetadataTokens, showMetadataTokensInBase10); } } } @@ -98,7 +100,7 @@ namespace ICSharpCode.ILSpy.ReadyToRun output.WriteLine("; " + comment); } - private void Disassemble(PEFile currentFile, 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]; @@ -156,16 +158,30 @@ namespace ICSharpCode.ILSpy.ReadyToRun output.Write(tempOutput.ToStringAndReset()); int importCellAddress = (int)instr.IPRelativeMemoryAddress; if (instr.IsCallNearIndirect && reader.ImportCellNames.ContainsKey(importCellAddress)) { - output.Write(" ;"); + output.Write(" ; "); ReadyToRunSignature signature = reader.ImportSignatures[(int)instr.IPRelativeMemoryAddress]; switch(signature) { case MethodDefEntrySignature methodDefSignature: - int methodDefToken = unchecked((int)methodDefSignature.MethodDefToken); - InstructionOutputExtensions.WriteTo((EntityHandle)MetadataTokens.Handle(methodDefToken), currentFile, output, null); + 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: - int methodRefToken = unchecked((int)methodRefSignature.MethodRefToken); - InstructionOutputExtensions.WriteTo((EntityHandle)MetadataTokens.Handle(methodRefToken), currentFile, output, null); + 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]); From e18dc00ceba5397f4a14d3516860d6624f768dbd Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 24 May 2020 12:47:53 +0200 Subject: [PATCH 41/44] Remove unused code from ReferenceElementGenerator --- ILSpy/TextView/DecompilerTextView.cs | 2 +- ILSpy/TextView/ReferenceElementGenerator.cs | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 354e0a4a8..5a66a16ea 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); 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); - } } /// From 27744e6006fe6ce6d9657a5d998b1687f26c102b Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 24 May 2020 13:01:29 +0200 Subject: [PATCH 42/44] Fix #2019: Add MMB shortcut to "Open in new tab" for tree view and links --- ILSpy/Commands/DecompileInNewViewCommand.cs | 2 +- ILSpy/MainWindow.xaml.cs | 16 +++++++++++++--- ILSpy/TextView/DecompilerTextView.cs | 8 ++++---- ILSpy/TreeNodes/ILSpyTreeNode.cs | 9 +++++++++ SharpTreeView/SharpTreeNode.cs | 10 +++++++++- SharpTreeView/SharpTreeViewItem.cs | 11 ++++++++++- 6 files changed, 46 insertions(+), 10 deletions(-) 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/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 51b0f2438..a085fea59 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -875,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(); } /// @@ -886,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) { @@ -900,7 +910,7 @@ 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; } } @@ -915,7 +925,7 @@ namespace ICSharpCode.ILSpy default: ILSpyTreeNode treeNode = FindTreeNode(reference); if (treeNode != null) - SelectNode(treeNode); + SelectNode(treeNode, inNewTabPage); break; } return decompilationTask; diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 5a66a16ea..e26704dde 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -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)); } } } 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/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) From dcb9631677e41a8425b547b54bde67ab465a2041 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 24 May 2020 13:27:31 +0200 Subject: [PATCH 43/44] Fix #2017: Make sure we never use an empty string as tab header --- ILSpy/Properties/Resources.Designer.cs | 9 +++++++++ ILSpy/Properties/Resources.resx | 3 +++ ILSpy/TextView/AvalonEditTextOutput.cs | 2 +- ILSpy/ViewModels/TabPageModel.cs | 5 +++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 51507cb90..2da4b683b 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -1533,6 +1533,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. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index b8708bf57..6f172298d 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -876,4 +876,7 @@ Do you want to continue? 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..3259ccf1b 100644 --- a/ILSpy/TextView/AvalonEditTextOutput.cs +++ b/ILSpy/TextView/AvalonEditTextOutput.cs @@ -82,7 +82,7 @@ namespace ICSharpCode.ILSpy.TextView public string IndentationString { get; set; } = "\t"; - public string Title { get; set; } + public string Title { get; set; } = Properties.Resources.NewTab; /// /// Gets/sets the that is displayed by this view. 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; From 3436ac32463b45a438888e7b4ccd6562eb86e34d Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 24 May 2020 16:54:01 +0200 Subject: [PATCH 44/44] Fix #2018: Improve tooltips in IL/IL with C#/R2R view to show full member signatures --- .../Disassembler/ReflectionDisassembler.cs | 212 +++++++++++------- .../IL/InstructionOutputExtensions.cs | 2 +- .../IL/Instructions/CallInstruction.cs | 2 +- ILSpy.ReadyToRun/ReadyToRunLanguage.cs | 8 + ILSpy/Languages/CSharpILMixedLanguage.cs | 9 +- ILSpy/Languages/ILLanguage.cs | 38 +++- ILSpy/Properties/AssemblyInfo.template.cs | 2 +- ILSpy/TextView/AvalonEditTextOutput.cs | 20 +- 8 files changed, 204 insertions(+), 89 deletions(-) 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/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/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/ILSpy.ReadyToRun/ReadyToRunLanguage.cs b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs index 0158677aa..3bb34ee04 100644 --- a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs +++ b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs @@ -26,11 +26,14 @@ 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 @@ -195,6 +198,11 @@ namespace ICSharpCode.ILSpy.ReadyToRun 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/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/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/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/TextView/AvalonEditTextOutput.cs b/ILSpy/TextView/AvalonEditTextOutput.cs index 3259ccf1b..fdaa23f19 100644 --- a/ILSpy/TextView/AvalonEditTextOutput.cs +++ b/ILSpy/TextView/AvalonEditTextOutput.cs @@ -82,6 +82,8 @@ namespace ICSharpCode.ILSpy.TextView public string IndentationString { get; set; } = "\t"; + internal bool IgnoreNewLineAndIndent { get; set; } + public string Title { get; set; } = Properties.Resources.NewTab; /// @@ -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(); }