From a25491362443824a854be7bb2c79e64d8f2459a3 Mon Sep 17 00:00:00 2001 From: Andreas Weizel Date: Fri, 1 Jun 2018 13:39:34 +0200 Subject: [PATCH 01/21] Work for throw expressions. --- .../CSharp/ExpressionBuilder.cs | 11 ++- .../CSharp/TranslatedExpression.cs | 3 + ICSharpCode.Decompiler/IL/Instructions.cs | 2 +- ICSharpCode.Decompiler/IL/Instructions.tt | 2 +- .../IL/Instructions/TryInstruction.cs | 5 ++ .../IL/Transforms/NullCoalescingTransform.cs | 69 ++++++++++++++++++- 6 files changed, 86 insertions(+), 6 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 93c66b85d..ce304a7a3 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -746,6 +746,13 @@ namespace ICSharpCode.Decompiler.CSharp return HandleThreeValuedLogic(inst, BinaryOperatorType.BitwiseOr, ExpressionType.Or); } + protected internal override TranslatedExpression VisitThrow(Throw inst, TranslationContext context) + { + return new ThrowExpression(Translate(inst.Argument)) + .WithILInstruction(inst) + .WithRR(new ResolveResult(SpecialType.NoType)); + } + TranslatedExpression HandleThreeValuedLogic(BinaryInstruction inst, BinaryOperatorType op, ExpressionType eop) { var left = Translate(inst.Left); @@ -2181,7 +2188,9 @@ namespace ICSharpCode.Decompiler.CSharp var rr = resolver.ResolveBinaryOperator(BinaryOperatorType.NullCoalescing, value.ResolveResult, fallback.ResolveResult); if (rr.IsError) { IType targetType; - if (!value.Type.Equals(SpecialType.NullType) && !fallback.Type.Equals(SpecialType.NullType) && !value.Type.Equals(fallback.Type)) { + if (fallback.Expression is ThrowExpression && fallback.Type.Equals(SpecialType.NoType)) { + targetType = value.Type; + } else if (!value.Type.Equals(SpecialType.NullType) && !fallback.Type.Equals(SpecialType.NullType) && !value.Type.Equals(fallback.Type)) { targetType = compilation.FindType(inst.UnderlyingResultType.ToKnownTypeCode()); } else { targetType = value.Type.Equals(SpecialType.NullType) ? fallback.Type : value.Type; diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index 5ebd40771..0346e2502 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -203,6 +203,9 @@ namespace ICSharpCode.Decompiler.CSharp if (targetType.Kind == TypeKind.Unknown || targetType.Kind == TypeKind.Void || targetType.Kind == TypeKind.None) { return this; // don't attempt to insert cast to '?' or 'void' as these are not valid. } + if (Expression is ThrowExpression) { + return this; // Throw expressions have no type and are implicitly convertible to any type + } if (Expression is TupleExpression tupleExpr && targetType is TupleType targetTupleType && tupleExpr.Elements.Count == targetTupleType.ElementTypes.Length) { diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index 97e585d0d..76e4c76d6 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -4225,7 +4225,7 @@ namespace ICSharpCode.Decompiler.IL public Throw(ILInstruction argument) : base(OpCode.Throw, argument) { } - public override StackType ResultType { get { return StackType.Void; } } + public override StackType ResultType { get { return this.resultType; } } protected override InstructionFlags ComputeFlags() { return base.ComputeFlags() | InstructionFlags.MayThrow | InstructionFlags.EndPointUnreachable; diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index 95347ede4..4b81890a7 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -250,7 +250,7 @@ new OpCode("default.value", "Returns the default value for a type.", NoArguments, HasTypeOperand, ResultType("type.GetStackType()")), new OpCode("throw", "Throws an exception.", - Unary, MayThrow, UnconditionalBranch), + Unary, MayThrow, HasFlag("InstructionFlags.EndPointUnreachable"), ResultType("this.resultType")), new OpCode("rethrow", "Rethrows the current exception.", NoArguments, MayThrow, UnconditionalBranch), new OpCode("sizeof", "Gets the size of a type in bytes.", diff --git a/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs index b53611c42..6f7f6daa5 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs @@ -366,4 +366,9 @@ namespace ICSharpCode.Decompiler.IL } } } + + public partial class Throw + { + internal StackType resultType = StackType.Void; + } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs index 0eab50bd0..49aa44fe0 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs @@ -46,6 +46,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// } /// => /// stloc s(if.notnull(valueInst, fallbackInst)) + /// + /// ------------------- + /// + /// stloc obj(valueInst) + /// if (comp(ldloc obj == ldnull)) { + /// throw(...) + /// } + /// => + /// stloc obj(if.notnull(valueInst, throw(...))) /// bool TransformRefTypes(Block block, int pos, StatementTransformContext context) { @@ -55,15 +64,69 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst)) return false; + if (!(condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(stloc.Variable) && right.MatchLdNull())) + return false; trueInst = Block.Unwrap(trueInst); - if (condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(stloc.Variable) && right.MatchLdNull() - && trueInst.MatchStLoc(stloc.Variable, out var fallbackValue) - ) { + if (trueInst.MatchStLoc(stloc.Variable, out var fallbackValue)) { context.Step("NullCoalescingTransform (reference types)", stloc); stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, fallbackValue); block.Instructions.RemoveAt(pos + 1); // remove if instruction ILInlining.InlineOneIfPossible(block, pos, false, context); return true; + } else if (trueInst is Throw throwInst) { + context.Step("NullCoalescingTransform (throw expression)", stloc); + throwInst.resultType = StackType.O; + stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, throwInst); + block.Instructions.RemoveAt(pos + 1); // remove if instruction + ILInlining.InlineOneIfPossible(block, pos, false, context); + return true; + } + return false; + } + + /// + /// Handles NullCoalescingInstruction case 1: reference types. + /// + /// stloc s(valueInst) + /// if (comp(ldloc s == ldnull)) { + /// stloc s(fallbackInst) + /// } + /// => + /// stloc s(if.notnull(valueInst, fallbackInst)) + /// + /// ------------------- + /// + /// stloc obj(valueInst) + /// if (comp(ldloc obj == ldnull)) { + /// throw(...) + /// } + /// => + /// stloc obj(if.notnull(valueInst, throw(...))) + /// + bool TransformRefTypesA(Block block, int pos, StatementTransformContext context) + { + if (!(block.Instructions[pos] is StLoc stloc)) + return false; + if (stloc.Variable.Kind != VariableKind.StackSlot) + return false; + if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst)) + return false; + if (!(condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(stloc.Variable) && right.MatchLdNull())) + return false; + trueInst = Block.Unwrap(trueInst); + if (trueInst.MatchStLoc(stloc.Variable, out var fallbackValue)) { + context.Step("NullCoalescingTransform (reference types)", stloc); + stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, fallbackValue); + block.Instructions.RemoveAt(pos + 1); // remove if instruction + ILInlining.InlineOneIfPossible(block, pos, false, context); + return true; + } else if (trueInst is Throw throwInst) { + context.Step("NullCoalescingTransform (throw expression)", stloc); + throwInst.resultType = StackType.O; + stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, throwInst); + block.Instructions.RemoveAt(pos + 1); // remove if instruction + ILInlining.InlineOneIfPossible(block, pos, false, context); + return true; } return false; } From 58b0ddda9e62a9c3f84ce307dd8e2ce873caf81a Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 2 Jun 2018 09:16:02 +0200 Subject: [PATCH 02/21] Add ThrowExpressionConversion. --- .../CSharp/ExpressionBuilder.cs | 16 +++---- .../CSharp/Resolver/CSharpConversions.cs | 3 ++ .../CSharp/TranslatedExpression.cs | 2 +- .../ICSharpCode.Decompiler.csproj | 1 + .../IL/Transforms/NullCoalescingTransform.cs | 47 ------------------- .../Semantics/Conversion.cs | 19 +++++++- .../Semantics/ThrowResolveResult.cs | 29 ++++++++++++ 7 files changed, 59 insertions(+), 58 deletions(-) create mode 100644 ICSharpCode.Decompiler/Semantics/ThrowResolveResult.cs diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index ce304a7a3..f207ff408 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -746,13 +746,6 @@ namespace ICSharpCode.Decompiler.CSharp return HandleThreeValuedLogic(inst, BinaryOperatorType.BitwiseOr, ExpressionType.Or); } - protected internal override TranslatedExpression VisitThrow(Throw inst, TranslationContext context) - { - return new ThrowExpression(Translate(inst.Argument)) - .WithILInstruction(inst) - .WithRR(new ResolveResult(SpecialType.NoType)); - } - TranslatedExpression HandleThreeValuedLogic(BinaryInstruction inst, BinaryOperatorType op, ExpressionType eop) { var left = Translate(inst.Left); @@ -775,6 +768,13 @@ namespace ICSharpCode.Decompiler.CSharp .WithILInstruction(inst); } + protected internal override TranslatedExpression VisitThrow(Throw inst, TranslationContext context) + { + return new ThrowExpression(Translate(inst.Argument)) + .WithILInstruction(inst) + .WithRR(new ThrowResolveResult()); + } + ExpressionWithResolveResult Assignment(TranslatedExpression left, TranslatedExpression right) { right = right.ConvertTo(left.Type, this, allowImplicitConversion: true); @@ -2189,7 +2189,7 @@ namespace ICSharpCode.Decompiler.CSharp if (rr.IsError) { IType targetType; if (fallback.Expression is ThrowExpression && fallback.Type.Equals(SpecialType.NoType)) { - targetType = value.Type; + targetType = NullableType.GetUnderlyingType(value.Type); } else if (!value.Type.Equals(SpecialType.NullType) && !fallback.Type.Equals(SpecialType.NullType) && !value.Type.Equals(fallback.Type)) { targetType = compilation.FindType(inst.UnderlyingResultType.ToKnownTypeCode()); } else { diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs index 5d8860d36..2d42e143b 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs @@ -115,6 +115,9 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver if (c != Conversion.None) return c; } + if (resolveResult is ThrowResolveResult) { + return Conversion.ThrowExpressionConversion; + } if (allowUserDefined && allowTuple) { // if allowUserDefined and allowTuple are true, we might as well use the cache c = ImplicitConversion(resolveResult.Type, toType); diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index 0346e2502..6217398aa 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -203,7 +203,7 @@ namespace ICSharpCode.Decompiler.CSharp if (targetType.Kind == TypeKind.Unknown || targetType.Kind == TypeKind.Void || targetType.Kind == TypeKind.None) { return this; // don't attempt to insert cast to '?' or 'void' as these are not valid. } - if (Expression is ThrowExpression) { + if (Expression is ThrowExpression && allowImplicitConversion) { return this; // Throw expressions have no type and are implicitly convertible to any type } if (Expression is TupleExpression tupleExpr && targetType is TupleType targetTupleType diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index cacda3343..64608367e 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -326,6 +326,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs index 49aa44fe0..031c0e00b 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs @@ -83,52 +83,5 @@ namespace ICSharpCode.Decompiler.IL.Transforms } return false; } - - /// - /// Handles NullCoalescingInstruction case 1: reference types. - /// - /// stloc s(valueInst) - /// if (comp(ldloc s == ldnull)) { - /// stloc s(fallbackInst) - /// } - /// => - /// stloc s(if.notnull(valueInst, fallbackInst)) - /// - /// ------------------- - /// - /// stloc obj(valueInst) - /// if (comp(ldloc obj == ldnull)) { - /// throw(...) - /// } - /// => - /// stloc obj(if.notnull(valueInst, throw(...))) - /// - bool TransformRefTypesA(Block block, int pos, StatementTransformContext context) - { - if (!(block.Instructions[pos] is StLoc stloc)) - return false; - if (stloc.Variable.Kind != VariableKind.StackSlot) - return false; - if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst)) - return false; - if (!(condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(stloc.Variable) && right.MatchLdNull())) - return false; - trueInst = Block.Unwrap(trueInst); - if (trueInst.MatchStLoc(stloc.Variable, out var fallbackValue)) { - context.Step("NullCoalescingTransform (reference types)", stloc); - stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, fallbackValue); - block.Instructions.RemoveAt(pos + 1); // remove if instruction - ILInlining.InlineOneIfPossible(block, pos, false, context); - return true; - } else if (trueInst is Throw throwInst) { - context.Step("NullCoalescingTransform (throw expression)", stloc); - throwInst.resultType = StackType.O; - stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, throwInst); - block.Instructions.RemoveAt(pos + 1); // remove if instruction - ILInlining.InlineOneIfPossible(block, pos, false, context); - return true; - } - return false; - } } } diff --git a/ICSharpCode.Decompiler/Semantics/Conversion.cs b/ICSharpCode.Decompiler/Semantics/Conversion.cs index 49af18b3e..0e3ba13f5 100644 --- a/ICSharpCode.Decompiler/Semantics/Conversion.cs +++ b/ICSharpCode.Decompiler/Semantics/Conversion.cs @@ -75,7 +75,12 @@ namespace ICSharpCode.Decompiler.Semantics /// C# 'as' cast. /// public static readonly Conversion TryCast = new BuiltinConversion(false, 9); - + + /// + /// C# 7 throw expression being converted to an arbitrary type. + /// + public static readonly Conversion ThrowExpressionConversion = new BuiltinConversion(true, 10); + public static Conversion UserDefinedConversion(IMethod operatorMethod, bool isImplicit, Conversion conversionBeforeUserDefinedOperator, Conversion conversionAfterUserDefinedOperator, bool isLifted = false, bool isAmbiguous = false) { if (operatorMethod == null) @@ -227,6 +232,10 @@ namespace ICSharpCode.Decompiler.Semantics public override bool IsTryCast { get { return type == 9; } } + + public override bool IsThrowExpressionConversion { + get { return type == 10; } + } public override string ToString() { @@ -257,6 +266,8 @@ namespace ICSharpCode.Decompiler.Semantics return "unboxing conversion"; case 9: return "try cast"; + case 10: + return "throw-expression conversion"; } return (isImplicit ? "implicit " : "explicit ") + name + " conversion"; } @@ -443,7 +454,11 @@ namespace ICSharpCode.Decompiler.Semantics public virtual bool IsTryCast { get { return false; } } - + + public virtual bool IsThrowExpressionConversion { + get { return false; } + } + public virtual bool IsIdentityConversion { get { return false; } } diff --git a/ICSharpCode.Decompiler/Semantics/ThrowResolveResult.cs b/ICSharpCode.Decompiler/Semantics/ThrowResolveResult.cs new file mode 100644 index 000000000..518e38e4f --- /dev/null +++ b/ICSharpCode.Decompiler/Semantics/ThrowResolveResult.cs @@ -0,0 +1,29 @@ +// Copyright (c) 2018 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; + +namespace ICSharpCode.Decompiler.Semantics +{ + class ThrowResolveResult : ResolveResult + { + public ThrowResolveResult() : base(SpecialType.NoType) + { + } + } +} From 62c4635cb8f1e44bb0c176c3b7a2a9663eb45f04 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 10 May 2019 08:59:11 +0200 Subject: [PATCH 03/21] Add DecompilerSettings.ThrowExpressions --- ICSharpCode.Decompiler/DecompilerSettings.cs | 20 +++++++++++++++++++- ILSpy/Properties/Resources.Designer.cs | 9 +++++++++ ILSpy/Properties/Resources.resx | 3 +++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 163d58346..ae72802db 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -88,6 +88,7 @@ namespace ICSharpCode.Decompiler } if (languageVersion < CSharp.LanguageVersion.CSharp7) { outVariables = false; + throwExpressions = false; tupleTypes = false; tupleConversions = false; discards = false; @@ -117,7 +118,7 @@ namespace ICSharpCode.Decompiler if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers || nonTrailingNamedArguments) return CSharp.LanguageVersion.CSharp7_2; // C# 7.1 missing - if (outVariables || tupleTypes || tupleConversions || discards || localFunctions) + if (outVariables || throwExpressions || tupleTypes || tupleConversions || discards || localFunctions) return CSharp.LanguageVersion.CSharp7; if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation || stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers) @@ -856,6 +857,23 @@ namespace ICSharpCode.Decompiler } } + bool throwExpressions = true; + + /// + /// Gets/Sets whether throw expressions should be used. + /// + [Category("C# 7.0 / VS 2017")] + [Description("DecompilerSettings.UseThrowExpressions")] + public bool ThrowExpressions { + get { return throwExpressions; } + set { + if (throwExpressions != value) { + throwExpressions = value; + OnPropertyChanged(); + } + } + } + bool tupleConversions = true; /// diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 5c86e06e2..59d99b15d 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -908,6 +908,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Use throw expressions. + /// + public static string DecompilerSettings_UseThrowExpressions { + get { + return ResourceManager.GetString("DecompilerSettings.UseThrowExpressions", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use tuple type syntax. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 0128389d9..f3a237aec 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -729,4 +729,7 @@ Entity could not be resolved. Cannot analyze entities from missing assembly references. Add the missing reference and try again. + + Use throw expressions + \ No newline at end of file From c6f4f77b57776b928d69bb68f8597bd55f514846 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 10 May 2019 08:59:55 +0200 Subject: [PATCH 04/21] Reorder code in NullCoalescingTransform.cs --- .../IL/Transforms/NullCoalescingTransform.cs | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs index 2a0ce4db8..2f534ef03 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs @@ -39,22 +39,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// Handles NullCoalescingInstruction case 1: reference types. - /// - /// stloc s(valueInst) - /// if (comp(ldloc s == ldnull)) { - /// stloc s(fallbackInst) - /// } - /// => - /// stloc s(if.notnull(valueInst, fallbackInst)) - /// - /// ------------------- - /// - /// stloc obj(valueInst) - /// if (comp(ldloc obj == ldnull)) { - /// throw(...) - /// } - /// => - /// stloc obj(if.notnull(valueInst, throw(...))) /// bool TransformRefTypes(Block block, int pos, StatementTransformContext context) { @@ -67,19 +51,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!(condition.MatchCompEquals(out var left, out var right) && left.MatchLdLoc(stloc.Variable) && right.MatchLdNull())) return false; trueInst = Block.Unwrap(trueInst); + // stloc s(valueInst) + // if (comp(ldloc s == ldnull)) { + // stloc s(fallbackInst) + // } + // => + // stloc s(if.notnull(valueInst, fallbackInst)) if (trueInst.MatchStLoc(stloc.Variable, out var fallbackValue)) { context.Step("NullCoalescingTransform: simple (reference types)", stloc); stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, fallbackValue); block.Instructions.RemoveAt(pos + 1); // remove if instruction ILInlining.InlineOneIfPossible(block, pos, InliningOptions.None, context); return true; - } else if (trueInst is Throw throwInst) { - context.Step("NullCoalescingTransform (throw expression)", stloc); - throwInst.resultType = StackType.O; - stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, throwInst); - block.Instructions.RemoveAt(pos + 1); // remove if instruction - ILInlining.InlineOneIfPossible(block, pos, InliningOptions.None, context); - return true; } // sometimes the compiler generates: // stloc s(valueInst) @@ -99,6 +82,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms ILInlining.InlineOneIfPossible(block, pos, InliningOptions.None, context); return true; } + // stloc obj(valueInst) + // if (comp(ldloc obj == ldnull)) { + // throw(...) + // } + // => + // stloc obj(if.notnull(valueInst, throw(...))) + if (context.Settings.ThrowExpressions && trueInst is Throw throwInst) { + context.Step("NullCoalescingTransform (throw expression)", stloc); + throwInst.resultType = StackType.O; + stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, throwInst); + block.Instructions.RemoveAt(pos + 1); // remove if instruction + ILInlining.InlineOneIfPossible(block, pos, InliningOptions.None, context); + return true; + } return false; } } From 4a0ca21bbfa078bc9bc7c3dbecea986e6e765f14 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 28 Jul 2019 10:38:50 +0200 Subject: [PATCH 05/21] Add (failing) test case for throw expressions. --- .../ICSharpCode.Decompiler.Tests.csproj | 1 + .../PrettyTestRunner.cs | 6 + .../TestCases/Pretty/ThrowExpressions.cs | 201 ++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index ad5e8bb47..7a447d2c9 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -81,6 +81,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 0f932c117..cfee6cb8b 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -329,6 +329,12 @@ namespace ICSharpCode.Decompiler.Tests RunForLibrary(cscOptions: cscOptions); } + [Test] + public void ThrowExpressions([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) + { + RunForLibrary(cscOptions: cscOptions); + } + [Test] public void WellKnownConstants([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) { diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs new file mode 100644 index 000000000..89742681d --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs @@ -0,0 +1,201 @@ +using System; + +namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty +{ + internal class ThrowExpressions + { + private class ArgumentCheckingCtor + { + private int initializedFromCtor = CountSheep() ?? throw new Exception("No sheep?!"); + private object cacheObj = TryGetObj() ?? throw new Exception("What?"); + + private object simpleObj; + private int? nullableInt; + + public ArgumentCheckingCtor(object simpleObj, int? nullableInt) + { + this.simpleObj = simpleObj ?? throw new ArgumentNullException("simpleObj"); + this.nullableInt = nullableInt ?? throw new ArgumentNullException("nullableInt"); + } + + public ArgumentCheckingCtor(string input) + : this(input, GetIntOrNull(input ?? throw new ArgumentNullException("input"))) + { + + } + + public ArgumentCheckingCtor(DataObject obj) + : this(obj ?? throw new Exception(), GetIntOrNull(obj.NullableDataField?.NullableDataField.ToString() ?? throw new ArgumentNullException("input"))) + { + + } + + private static int? GetIntOrNull(string v) + { + if (int.TryParse(v, out int result)) + return result; + return null; + } + + private static int? CountSheep() + { + throw new NotImplementedException(); + } + + private static object TryGetObj() + { + return null; + } + + public override int GetHashCode() + { + return initializedFromCtor; + } + + public override bool Equals(object obj) + { + return true; + } + } + + public class DataObject + { + public int IntField; + public int? NullableIntField; + public Data DataField; + public Data? NullableDataField; + public int IntProperty { get; set; } + public int? NullableIntProperty { get; set; } + public Data DataProperty { get; } + public Data? NullableDataProperty { get; } + } + + public struct Data + { + public int IntField; + public int? NullableIntField; + public MoreData DataField; + public MoreData? NullableDataField; + public int IntProperty { get; set; } + public int? NullableIntProperty { get; set; } + public MoreData DataProperty { get; } + public MoreData? NullableDataProperty { get; } + } + + public struct MoreData + { + public int IntField; + public int? NullableIntField; + public int IntProperty { get; set; } + public int? NullableIntProperty { get; set; } + } + + public static int IntField; + public static int? NullableIntField; + public static object ObjectField; + public int InstIntField; + public int? InstNullableIntField; + public object InstObjectField; + public Data DataField; + public Data? NullableDataField; + public DataObject DataObjectField; + + public static int IntProperty { get; } + public static int? NullableIntProperty { get; } + public static object ObjProperty { get; } + public int InstIntProperty { get; } + public int? InstNullableIntProperty { get; } + public object InstObjProperty { get; } + public Data DataProperty { get; } + public Data? NullableDataProperty { get; } + public DataObject DataObjectProperty { get; } + + public static int ReturnIntField() + { + return NullableIntField ?? throw new Exception(); + } + public static int ReturnIntProperty() + { + return NullableIntProperty ?? throw new Exception(); + } + public static object ReturnObjField() + { + return ObjectField ?? throw new Exception(); + } + public static object ReturnObjProperty() + { + return ObjProperty ?? throw new Exception(); + } + public static int ReturnIntField(ThrowExpressions inst) + { + return inst.InstNullableIntField ?? throw new Exception(); + } + public static int ReturnIntProperty(ThrowExpressions inst) + { + return inst.InstNullableIntProperty ?? throw new Exception(); + } + public static object ReturnObjField(ThrowExpressions inst) + { + return inst.InstObjectField ?? throw new Exception(); + } + public static object ReturnObjProperty(ThrowExpressions inst) + { + return inst.InstObjProperty ?? throw new Exception(); + } + + public static void UseComplexNullableStruct(ThrowExpressions inst) + { + Use(inst.InstNullableIntField ?? throw new Exception()); + Use(inst.NullableDataField?.IntField ?? throw new Exception()); + Use(inst.NullableDataField?.NullableIntField ?? throw new Exception()); + Use(inst.NullableDataProperty?.IntField ?? throw new Exception()); + Use(inst.NullableDataProperty?.NullableIntField ?? throw new Exception()); + Use(inst.NullableDataField?.DataField.IntField ?? throw new Exception()); + Use(inst.NullableDataField?.DataField.NullableIntField ?? throw new Exception()); + Use(inst.NullableDataProperty?.DataField.IntField ?? throw new Exception()); + Use(inst.NullableDataProperty?.DataField.NullableIntField ?? throw new Exception()); + Use(inst.NullableDataField?.DataProperty.IntField ?? throw new Exception()); + Use(inst.NullableDataField?.DataProperty.NullableIntField ?? throw new Exception()); + Use(inst.NullableDataProperty?.DataProperty.IntField ?? throw new Exception()); + Use(inst.NullableDataProperty?.DataProperty.NullableIntField ?? throw new Exception()); + Use(inst.NullableDataField?.NullableDataField?.IntField ?? throw new Exception()); + Use(inst.NullableDataField?.NullableDataField?.NullableIntField ?? throw new Exception()); + Use(inst.NullableDataProperty?.NullableDataField?.IntField ?? throw new Exception()); + Use(inst.NullableDataProperty?.NullableDataField?.NullableIntField ?? throw new Exception()); + Use(inst.NullableDataField?.NullableDataProperty?.IntField ?? throw new Exception()); + Use(inst.NullableDataField?.NullableDataProperty?.NullableIntField ?? throw new Exception()); + Use(inst.NullableDataProperty?.NullableDataProperty?.IntField ?? throw new Exception()); + Use(inst.NullableDataProperty?.NullableDataProperty?.NullableIntField ?? throw new Exception()); + } + + public static void UseComplexNullableObject(DataObject inst) + { + Use(inst?.NullableIntField ?? throw new Exception()); + Use(inst?.NullableDataField?.IntField ?? throw new Exception()); + Use(inst?.NullableDataField?.NullableIntField ?? throw new Exception()); + Use(inst?.NullableDataProperty?.IntField ?? throw new Exception()); + Use(inst?.NullableDataProperty?.NullableIntField ?? throw new Exception()); + Use(inst?.NullableDataField?.DataField.IntField ?? throw new Exception()); + Use(inst?.NullableDataField?.DataField.NullableIntField ?? throw new Exception()); + Use(inst?.NullableDataProperty?.DataField.IntField ?? throw new Exception()); + Use(inst?.NullableDataProperty?.DataField.NullableIntField ?? throw new Exception()); + Use(inst?.NullableDataField?.DataProperty.IntField ?? throw new Exception()); + Use(inst?.NullableDataField?.DataProperty.NullableIntField ?? throw new Exception()); + Use(inst?.NullableDataProperty?.DataProperty.IntField ?? throw new Exception()); + Use(inst?.NullableDataProperty?.DataProperty.NullableIntField ?? throw new Exception()); + Use(inst?.NullableDataField?.NullableDataField?.IntField ?? throw new Exception()); + Use(inst?.NullableDataField?.NullableDataField?.NullableIntField ?? throw new Exception()); + Use(inst?.NullableDataProperty?.NullableDataField?.IntField ?? throw new Exception()); + Use(inst?.NullableDataProperty?.NullableDataField?.NullableIntField ?? throw new Exception()); + Use(inst?.NullableDataField?.NullableDataProperty?.IntField ?? throw new Exception()); + Use(inst?.NullableDataField?.NullableDataProperty?.NullableIntField ?? throw new Exception()); + Use(inst?.NullableDataProperty?.NullableDataProperty?.IntField ?? throw new Exception()); + Use(inst?.NullableDataProperty?.NullableDataProperty?.NullableIntField ?? throw new Exception()); + } + + public static void Use(T usage) + { + + } + } +} From 2b6c0c389270960bac3718d7330bfd07125ec72a Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 28 Jul 2019 13:40:26 +0200 Subject: [PATCH 06/21] Fix formatting in ExpressionBuilder. --- .../CSharp/ExpressionBuilder.cs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 6125179eb..bc442745b 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -441,7 +441,7 @@ namespace ICSharpCode.Decompiler.CSharp .WithILInstruction(inst); } finally { astBuilder.PrintIntegralValuesAsHex = false; - } + } } protected internal override TranslatedExpression VisitLdcI8(LdcI8 inst, TranslationContext context) @@ -465,7 +465,7 @@ namespace ICSharpCode.Decompiler.CSharp .WithILInstruction(inst); } finally { astBuilder.PrintIntegralValuesAsHex = false; - } + } } private bool ShouldDisplayAsHex(long value, ILInstruction parent) @@ -556,7 +556,7 @@ namespace ICSharpCode.Decompiler.CSharp var argUType = NullableType.GetUnderlyingType(argument.Type); if (argUType.GetStackType().GetSize() < inst.UnderlyingResultType.GetSize() - || argUType.Kind == TypeKind.Enum && argUType.IsSmallIntegerType() + || argUType.Kind == TypeKind.Enum && argUType.IsSmallIntegerType() || argUType.GetStackType() == StackType.I || argUType.IsKnownType(KnownTypeCode.Boolean) || argUType.IsKnownType(KnownTypeCode.Char)) @@ -1522,7 +1522,7 @@ namespace ICSharpCode.Decompiler.CSharp var pao = GetPointerArithmeticOffset(inst.Value, value, ((PointerType)target.Type).ElementType, inst.CheckForOverflow); if (pao != null) { value = pao.Value; - } else { + } else { value.Expression.AddChild(new Comment("ILSpy Error: GetPointerArithmeticOffset() failed", CommentType.MultiLine), Roles.Comment); } } else { @@ -1939,13 +1939,13 @@ namespace ICSharpCode.Decompiler.CSharp // When accessing members on value types, ensure we use a reference of the correct type, // and not a pointer or a reference to a different type (issue #1333) if (!(translatedTarget.Type is ByReferenceType brt && NormalizeTypeVisitor.TypeErasure.EquivalentTypes(brt.ElementType, memberDeclaringType))) { - translatedTarget = translatedTarget.ConvertTo(new ByReferenceType(memberDeclaringType), this); - } + translatedTarget = translatedTarget.ConvertTo(new ByReferenceType(memberDeclaringType), this); + } } if (translatedTarget.Expression is DirectionExpression) { // (ref x).member => x.member translatedTarget = translatedTarget.UnwrapChild(((DirectionExpression)translatedTarget).Expression); - } else if (translatedTarget.Expression is UnaryOperatorExpression uoe + } else if (translatedTarget.Expression is UnaryOperatorExpression uoe && uoe.Operator == UnaryOperatorType.NullConditional && uoe.Expression is DirectionExpression) { // (ref x)?.member => x?.member @@ -2040,7 +2040,7 @@ namespace ICSharpCode.Decompiler.CSharp pointer = pointer.ConvertTo(new PointerType(value.Type), this); } else { pointer = pointer.ConvertTo(new PointerType(inst.Type), this); - } + } } if (pointer.Expression is UnaryOperatorExpression uoe && uoe.Operator == UnaryOperatorType.AddressOf) { // *&ptr -> ptr @@ -2053,7 +2053,7 @@ namespace ICSharpCode.Decompiler.CSharp } if (value.Expression == null) { value = Translate(inst.Value, typeHint: target.Type); - } + } return Assignment(target, value).WithILInstruction(inst); } @@ -2138,7 +2138,7 @@ namespace ICSharpCode.Decompiler.CSharp TranslatedExpression arrayExpr = Translate(inst.Array); var arrayType = arrayExpr.Type as ArrayType; if (arrayType == null || !TypeUtils.IsCompatibleTypeForMemoryAccess(arrayType.ElementType, inst.Type)) { - arrayType = new ArrayType(compilation, inst.Type, inst.Indices.Count); + arrayType = new ArrayType(compilation, inst.Type, inst.Indices.Count); arrayExpr = arrayExpr.ConvertTo(arrayType, this); } TranslatedExpression expr = new IndexerExpression( @@ -2185,8 +2185,7 @@ namespace ICSharpCode.Decompiler.CSharp // try via its effective base class. arg = arg.ConvertTo(((ITypeParameter)targetType).EffectiveBaseClass, this); } - } - else { + } else { // Before unboxing arg must be a object arg = arg.ConvertTo(compilation.FindType(KnownTypeCode.Object), this); } @@ -2425,7 +2424,7 @@ namespace ICSharpCode.Decompiler.CSharp } TranslatedExpression MakeInitializerAssignment(InitializedObjectResolveResult rr, IL.Transforms.AccessPathElement memberPath, - IL.Transforms.AccessPathElement valuePath, List values, + IL.Transforms.AccessPathElement valuePath, List values, Dictionary indexVariables) { TranslatedExpression value; @@ -2445,9 +2444,9 @@ namespace ICSharpCode.Decompiler.CSharp if (memberPath.Member is IProperty property) { index = new CallBuilder(this, typeSystem, settings) .BuildDictionaryInitializerExpression(valuePath.OpCode, property.Setter, rr, GetIndices(valuePath.Indices, indexVariables).ToList()); - } else { + } else { index = new IndexerExpression(null, GetIndices(valuePath.Indices, indexVariables).Select(i => Translate(i).Expression)); - } + } return new AssignmentExpression(index, value) .WithRR(new MemberResolveResult(rr, memberPath.Member)) .WithoutILInstruction(); @@ -2455,7 +2454,7 @@ namespace ICSharpCode.Decompiler.CSharp return new NamedExpression(valuePath.Member.Name, value) .WithRR(new MemberResolveResult(rr, valuePath.Member)) .WithoutILInstruction(); - } + } } class ArrayInitializer @@ -2469,7 +2468,7 @@ namespace ICSharpCode.Decompiler.CSharp public ArrayInitializerExpression Expression; // HACK: avoid using Expression.Elements.Count: https://github.com/icsharpcode/ILSpy/issues/1202 public int CurrentElementCount; - } + } TranslatedExpression TranslateArrayInitializer(Block block) { From 7d4b4c6433a46a958c06c36bbeb45fc6104750a2 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 28 Jul 2019 15:39:47 +0200 Subject: [PATCH 07/21] Implement NullCoalescingTransform with value types. --- .../CSharp/ExpressionBuilder.cs | 2 +- .../IL/Instructions/ILInstruction.cs | 2 +- .../IL/Transforms/NullCoalescingTransform.cs | 57 ++++++++++++++++++- .../IL/Transforms/NullableLiftingTransform.cs | 2 +- 4 files changed, 58 insertions(+), 5 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index bc442745b..77992c60d 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -134,7 +134,7 @@ namespace ICSharpCode.Decompiler.CSharp }; var cexpr = inst.AcceptVisitor(this, context); #if DEBUG - if (inst.ResultType != StackType.Void && cexpr.Type.Kind != TypeKind.Unknown && inst.ResultType != StackType.Unknown) { + if (inst.ResultType != StackType.Void && cexpr.Type.Kind != TypeKind.Unknown && inst.ResultType != StackType.Unknown && cexpr.Type.Kind != TypeKind.None) { // Validate the Translate post-condition (documented at beginning of this file): if (inst.ResultType.IsIntegerType()) { Debug.Assert(cexpr.Type.GetStackType().IsIntegerType(), "IL instructions of integer type must convert into C# expressions of integer type"); diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs index d1d7e17f8..609b31dc7 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs @@ -308,7 +308,7 @@ namespace ICSharpCode.Decompiler.IL protected abstract SlotInfo GetChildSlot(int index); #region ChildrenCollection + ChildrenEnumerator - public struct ChildrenCollection : IReadOnlyList + public readonly struct ChildrenCollection : IReadOnlyList { readonly ILInstruction inst; diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs index 2f534ef03..7734099ac 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using ICSharpCode.Decompiler.TypeSystem; namespace ICSharpCode.Decompiler.IL.Transforms { @@ -34,7 +35,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms { public void Run(Block block, int pos, StatementTransformContext context) { - TransformRefTypes(block, pos, context); + if (!TransformRefTypes(block, pos, context)) { + TransformThrowExpressionValueTypes(block, pos, context); + } } /// @@ -89,7 +92,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms // => // stloc obj(if.notnull(valueInst, throw(...))) if (context.Settings.ThrowExpressions && trueInst is Throw throwInst) { - context.Step("NullCoalescingTransform (throw expression)", stloc); + context.Step("NullCoalescingTransform (reference types + throw expression)", stloc); throwInst.resultType = StackType.O; stloc.Value = new NullCoalescingInstruction(NullCoalescingKind.Ref, stloc.Value, throwInst); block.Instructions.RemoveAt(pos + 1); // remove if instruction @@ -98,5 +101,55 @@ namespace ICSharpCode.Decompiler.IL.Transforms } return false; } + + /// + /// stloc v(value) + /// if (logic.not(call get_HasValue(ldloca v))) { + /// throw(...) + /// } + /// ... Call(arg1, arg2, call GetValueOrDefault(ldloca v), arg4) ... + /// => + /// ... Call(arg1, arg2, if.notnull(value, throw(...)), arg4) ... + /// + bool TransformThrowExpressionValueTypes(Block block, int pos, StatementTransformContext context) + { + if (pos + 2 >= block.Instructions.Count) + return false; + if (!(block.Instructions[pos] is StLoc stloc)) + return false; + if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst)) + return false; + if (!(Block.Unwrap(trueInst) is Throw throwInst)) + return false; + ILVariable v = stloc.Variable; + if (!(v.StoreCount == 1 && v.LoadCount == 0 && v.AddressCount == 2)) + return false; + if (!NullableLiftingTransform.MatchNegatedHasValueCall(condition, v)) + return false; + var throwInstParent = throwInst.Parent; + var throwInstChildIndex = throwInst.ChildIndex; + var nullcoalescingWithThrow = new NullCoalescingInstruction( + NullCoalescingKind.NullableWithValueFallback, + stloc.Value, + throwInst); + var resultType = NullableType.GetUnderlyingType(v.Type).GetStackType(); + nullcoalescingWithThrow.UnderlyingResultType = resultType; + var result = ILInlining.FindLoadInNext(block.Instructions[pos + 2], v, nullcoalescingWithThrow, InliningOptions.None); + if (result.Type == ILInlining.FindResultType.Found + && NullableLiftingTransform.MatchGetValueOrDefault(result.LoadInst.Parent, v)) + { + context.Step("NullCoalescingTransform (value types + throw expression)", stloc); + throwInst.resultType = resultType; + result.LoadInst.Parent.ReplaceWith(nullcoalescingWithThrow); + block.Instructions.RemoveRange(pos, 2); // remove store and if instruction + return true; + } else { + // reset the primary position (see remarks on ILInstruction.Parent) + stloc.Value = stloc.Value; + var children = throwInstParent.Children; + children[throwInstChildIndex] = throwInst; + return false; + } + } } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs index e1c816028..bc3e05151 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs @@ -833,7 +833,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// /// Matches 'logic.not(call get_HasValue(ldloca v))' /// - static bool MatchNegatedHasValueCall(ILInstruction inst, ILVariable v) + internal static bool MatchNegatedHasValueCall(ILInstruction inst, ILVariable v) { return inst.MatchLogicNot(out var arg) && MatchHasValueCall(arg, v); } From 903544598a1573b1117287a4e4c9a9c073c5d45b Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 28 Jul 2019 16:41:51 +0200 Subject: [PATCH 08/21] Add comments about expected result after a ConvertTo() call. This method has grown a few cases where it doesn't actually convert. --- .../CSharp/TranslatedExpression.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index 3a5e32744..35582ec1d 100644 --- a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs @@ -163,7 +163,7 @@ namespace ICSharpCode.Decompiler.CSharp } throw new ArgumentException("descendant must be a descendant of the current node"); } - + /// /// Adds casts (if necessary) to convert this expression to the specified target type. /// @@ -176,6 +176,17 @@ namespace ICSharpCode.Decompiler.CSharp /// /// From the caller's perspective, IntPtr/UIntPtr behave like normal C# integers except that they have native int size. /// All the special cases necessary to make IntPtr/UIntPtr behave sanely are handled internally in ConvertTo(). + /// + /// Post-condition: + /// The "expected evaluation result" is the value computed by this.Expression, + /// converted to targetType via an IL conv instruction. + /// + /// ConvertTo(targetType, allowImplicitConversion=false).Type must be equal to targetType (modulo identity conversions). + /// The value computed by the converted expression must match the "expected evaluation result". + /// + /// ConvertTo(targetType, allowImplicitConversion=true) must produce an expression that, + /// when evaluated in a context where it will be implicitly converted to targetType, + /// evaluates to the "expected evaluation result". /// public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expressionBuilder, bool checkForOverflow = false, bool allowImplicitConversion = false) { From 7f8689c46429061f44f7d44d546f85c4e070247a Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 28 Jul 2019 16:42:44 +0200 Subject: [PATCH 09/21] Allow inlining of compiler-generated value-type-temporaries when field accesses are involved. --- .../TestCases/Pretty/ValueTypes.cs | 10 ++++++++++ .../IL/Transforms/ILInlining.cs | 16 ++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs index e03c5468e..346c3b13e 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs @@ -66,6 +66,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty S s = this; s.SetField(); } + + public void UseField(int val) + { + UseField(Get().Field); + } } #if CS72 @@ -263,5 +268,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty r.Property = 2; #endif } + + public static void CallOnFieldOfTemporary() + { + Get().Field.ToString(); + } } } \ No newline at end of file diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index 4f7b59d73..ce84921e4 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -272,10 +272,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (ldloca.Variable.Type.IsReferenceType ?? false) return false; - switch (ldloca.Parent.OpCode) { + ILInstruction inst = ldloca; + while (inst.Parent is LdFlda ldflda) { + inst = ldflda; + } + switch (inst.Parent.OpCode) { case OpCode.Call: case OpCode.CallVirt: - var method = ((CallInstruction)ldloca.Parent).Method; + var method = ((CallInstruction)inst.Parent).Method; if (method.IsAccessor && method.AccessorKind != MethodSemanticsAttributes.Getter) { // C# doesn't allow calling setters on temporary structs return false; @@ -284,7 +288,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms case OpCode.Await: return true; case OpCode.NullableUnwrap: - return ((NullableUnwrap)ldloca.Parent).RefInput; + return ((NullableUnwrap)inst.Parent).RefInput; default: return false; } @@ -346,13 +350,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms { switch (addr) { case LdFlda ldflda: - return ldflda.Field.IsReadOnly; + return ldflda.Field.IsReadOnly + || (ldflda.Field.DeclaringType.Kind == TypeKind.Struct && IsReadonlyReference(ldflda.Target)); case LdsFlda ldsflda: return ldsflda.Field.IsReadOnly; case LdLoc ldloc: return IsReadonlyRefLocal(ldloc.Variable); case Call call: return call.Method.ReturnTypeIsRefReadOnly; + case AddressOf _: + // C# doesn't allow mutation of value-type temporaries + return true; default: return false; } From 0e0179edff5d4eaa7e3856945d9830a8f19fde58 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 28 Jul 2019 17:33:17 +0200 Subject: [PATCH 10/21] Improve NullPropagation (?.) when fields of value-type are involved. --- .../TestCases/Pretty/NullPropagation.cs | 14 ++++++++++++++ .../IL/Transforms/NullPropagationTransform.cs | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs index 9ce4066cf..98b5df626 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs @@ -25,6 +25,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty private class MyClass { public int IntVal; + public readonly int ReadonlyIntVal; + public MyStruct StructField; + public readonly MyStruct ReadonlyStructField; public string Text; public MyClass Field; public MyClass Property { @@ -45,6 +48,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty private struct MyStruct { public int IntVal; + public readonly int ReadonlyIntVal; public MyClass Field; public MyStruct? Property1 => null; public MyStruct Property2 => default(MyStruct); @@ -178,6 +182,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty { Use(GetMyClass()?.Text ?? "Hello"); } + + public void CallOnValueTypeField() + { + Use(GetMyClass()?.IntVal.ToString()); + Use(GetMyStruct()?.IntVal.ToString()); + Use(GetMyClass()?.ReadonlyIntVal.ToString()); + Use(GetMyStruct()?.ReadonlyIntVal.ToString()); + GetMyClass()?.StructField.Done(); + GetMyClass()?.ReadonlyStructField.Done(); + } public void InvokeDelegate(EventHandler eh) { diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs index 675e94280..9288c447b 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs @@ -186,6 +186,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms return chainLength >= 1; } else if (inst.MatchLdFld(out var target, out _)) { inst = target; + } else if (inst.MatchLdFlda(out target, out var f)) { + if (target is AddressOf addressOf && f.DeclaringType.Kind == TypeKind.Struct) { + inst = addressOf.Value; + } else { + inst = target; + } } else if (inst is CallInstruction call && call.OpCode != OpCode.NewObj) { if (call.Arguments.Count == 0) { return false; From ddf4053a45a52609023047be326419b1ec8980df Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Mon, 29 Jul 2019 08:47:24 +0200 Subject: [PATCH 11/21] Fix merge conflict in Conversion.cs --- ICSharpCode.Decompiler/Semantics/Conversion.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ICSharpCode.Decompiler/Semantics/Conversion.cs b/ICSharpCode.Decompiler/Semantics/Conversion.cs index 4895a1ab8..b90107392 100644 --- a/ICSharpCode.Decompiler/Semantics/Conversion.cs +++ b/ICSharpCode.Decompiler/Semantics/Conversion.cs @@ -77,12 +77,15 @@ namespace ICSharpCode.Decompiler.Semantics public static readonly Conversion TryCast = new BuiltinConversion(false, 9); /// - /// C# 7 throw expression being converted to an arbitrary type. + /// C# 6 string interpolation expression implicitly being converted to or . /// - public static readonly Conversion ThrowExpressionConversion = new BuiltinConversion(true, 10); - public static readonly Conversion ImplicitInterpolatedStringConversion = new BuiltinConversion(true, 10); + /// + /// C# 7 throw expression being converted to an arbitrary type. + /// + public static readonly Conversion ThrowExpressionConversion = new BuiltinConversion(true, 11); + public static Conversion UserDefinedConversion(IMethod operatorMethod, bool isImplicit, Conversion conversionBeforeUserDefinedOperator, Conversion conversionAfterUserDefinedOperator, bool isLifted = false, bool isAmbiguous = false) { if (operatorMethod == null) From 1e3e8fdac161d2a7f0eb06417054fdd86bb726ec Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 30 Jul 2019 00:43:48 +0200 Subject: [PATCH 12/21] Allow ref-locals to be initialized from nested field addresses. --- .../IL/Transforms/SplitVariables.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs index 4dcf0720a..850f50fc1 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs @@ -109,7 +109,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms // Address stored in local variable: also check all uses of that variable. if (!(stloc.Variable.Kind == VariableKind.StackSlot || stloc.Variable.Kind == VariableKind.Local)) return AddressUse.Unknown; - if (stloc.Value.OpCode != OpCode.LdLoca) { + var value = stloc.Value; + while (value is LdFlda ldFlda) { + value = ldFlda.Target; + } + if (value.OpCode != OpCode.LdLoca) { // GroupStores.HandleLoad() only detects ref-locals when they are directly initialized with ldloca return AddressUse.Unknown; } @@ -162,7 +166,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms return null; // only single-definition variables can be supported ref locals var store = ldloc.Variable.StoreInstructions.SingleOrDefault(); if (store is StLoc stloc) { - return stloc.Value as LdLoca; + var value = stloc.Value; + while (value is LdFlda ldFlda) { + value = ldFlda.Target; + } + return value as LdLoca; } return null; } From 50c4fa1c1313dcb59fa4eea16851d62793d81f9e Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 30 Jul 2019 00:44:42 +0200 Subject: [PATCH 13/21] Add two more patterns with throw expressions. --- .../IL/Transforms/NullCoalescingTransform.cs | 130 ++++++++++++++++-- 1 file changed, 117 insertions(+), 13 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs index 7734099ac..aedf1038b 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs @@ -36,7 +36,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms public void Run(Block block, int pos, StatementTransformContext context) { if (!TransformRefTypes(block, pos, context)) { - TransformThrowExpressionValueTypes(block, pos, context); + if (!TransformThrowExpressionValueTypes(block, pos, context)) { + TransformThrowExpressionOnAddress(block, pos, context); + } } } @@ -102,14 +104,23 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; } + delegate bool PatternMatcher(ILInstruction input, out ILInstruction output); + /// /// stloc v(value) - /// if (logic.not(call get_HasValue(ldloca v))) { - /// throw(...) - /// } + /// if (logic.not(call get_HasValue(ldloca v))) throw(...) /// ... Call(arg1, arg2, call GetValueOrDefault(ldloca v), arg4) ... /// => /// ... Call(arg1, arg2, if.notnull(value, throw(...)), arg4) ... + /// + /// -or- + /// + /// stloc v(value) + /// stloc s(ldloca v) + /// if (logic.not(call get_HasValue(ldloc s))) throw(...) + /// ... Call(arg1, arg2, call GetValueOrDefault(ldloc s), arg4) ... + /// => + /// ... Call(arg1, arg2, if.notnull(value, throw(...)), arg4) ... /// bool TransformThrowExpressionValueTypes(Block block, int pos, StatementTransformContext context) { @@ -117,30 +128,114 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (!(block.Instructions[pos] is StLoc stloc)) return false; - if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst)) + int offset = 1; + ILVariable v = stloc.Variable; + // alternative pattern using stack slot containing address of v + if (block.Instructions[pos + offset] is StLoc addrCopyStore + && addrCopyStore.Variable.Kind == VariableKind.StackSlot + && addrCopyStore.Value.MatchLdLoca(v) + && pos + 3 < block.Instructions.Count) + { + offset++; + // v turns into s in the pattern above. + v = addrCopyStore.Variable; + if (!(v.StoreCount == 1 && v.LoadCount == 2 && v.AddressCount == 0)) + return false; + } else { + if (!(v.StoreCount == 1 && v.LoadCount == 0 && v.AddressCount == 2)) + return false; + } + if (!block.Instructions[pos + offset].MatchIfInstruction(out var condition, out var trueInst)) return false; if (!(Block.Unwrap(trueInst) is Throw throwInst)) return false; - ILVariable v = stloc.Variable; - if (!(v.StoreCount == 1 && v.LoadCount == 0 && v.AddressCount == 2)) + if (!condition.MatchLogicNot(out var arg)) return false; - if (!NullableLiftingTransform.MatchNegatedHasValueCall(condition, v)) + if (!MatchNullableCall(arg, NullableLiftingTransform.MatchHasValueCall)) return false; var throwInstParent = throwInst.Parent; var throwInstChildIndex = throwInst.ChildIndex; - var nullcoalescingWithThrow = new NullCoalescingInstruction( + var nullCoalescingWithThrow = new NullCoalescingInstruction( NullCoalescingKind.NullableWithValueFallback, stloc.Value, throwInst); var resultType = NullableType.GetUnderlyingType(v.Type).GetStackType(); - nullcoalescingWithThrow.UnderlyingResultType = resultType; - var result = ILInlining.FindLoadInNext(block.Instructions[pos + 2], v, nullcoalescingWithThrow, InliningOptions.None); + nullCoalescingWithThrow.UnderlyingResultType = resultType; + var result = ILInlining.FindLoadInNext(block.Instructions[pos + offset + 1], v, nullCoalescingWithThrow, InliningOptions.None); if (result.Type == ILInlining.FindResultType.Found - && NullableLiftingTransform.MatchGetValueOrDefault(result.LoadInst.Parent, v)) + && MatchNullableCall(result.LoadInst.Parent, NullableLiftingTransform.MatchGetValueOrDefault)) { context.Step("NullCoalescingTransform (value types + throw expression)", stloc); throwInst.resultType = resultType; - result.LoadInst.Parent.ReplaceWith(nullcoalescingWithThrow); + result.LoadInst.Parent.ReplaceWith(nullCoalescingWithThrow); + block.Instructions.RemoveRange(pos, offset + 1); // remove store(s) and if instruction + return true; + } else { + // reset the primary position (see remarks on ILInstruction.Parent) + stloc.Value = stloc.Value; + var children = throwInstParent.Children; + children[throwInstChildIndex] = throwInst; + return false; + } + + bool MatchNullableCall(ILInstruction input, PatternMatcher matcher) + { + if (!matcher(input, out var loadInst)) + return false; + if (offset == 1) { // Pattern 1 + if (!loadInst.MatchLdLoca(v)) + return false; + } else { + if (!loadInst.MatchLdLoc(v)) + return false; + } + return true; + } + } + + /// + /// stloc s(addressOfValue) + /// if (logic.not(call get_HasValue(ldloc s))) throw(...) + /// ... Call(arg1, arg2, call GetValueOrDefault(ldloc s), arg4) ... + /// => + /// ... Call(arg1, arg2, if.notnull(value, throw(...)), arg4) ... + /// + bool TransformThrowExpressionOnAddress(Block block, int pos, StatementTransformContext context) + { + if (pos + 2 >= block.Instructions.Count) + return false; + if (!(block.Instructions[pos] is StLoc stloc)) + return false; + var s = stloc.Variable; + if (s.Kind != VariableKind.StackSlot) + return false; + if (!(s.StoreCount == 1 && s.LoadCount == 2 && s.AddressCount == 0)) + return false; + if (!(s.Type is ByReferenceType byRef && byRef.ElementType.IsReferenceType == false)) + return false; + if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst)) + return false; + if (!(Block.Unwrap(trueInst) is Throw throwInst)) + return false; + if (!condition.MatchLogicNot(out var arg)) + return false; + if (!MatchNullableCall(arg, NullableLiftingTransform.MatchHasValueCall)) + return false; + var throwInstParent = throwInst.Parent; + var throwInstChildIndex = throwInst.ChildIndex; + var ldobj = new LdObj(stloc.Value, byRef.ElementType); + var nullCoalescingWithThrow = new NullCoalescingInstruction( + NullCoalescingKind.NullableWithValueFallback, + ldobj, + throwInst); + var resultType = NullableType.GetUnderlyingType(byRef.ElementType).GetStackType(); + nullCoalescingWithThrow.UnderlyingResultType = resultType; + var result = ILInlining.FindLoadInNext(block.Instructions[pos + 2], s, nullCoalescingWithThrow, InliningOptions.None); + if (result.Type == ILInlining.FindResultType.Found + && MatchNullableCall(result.LoadInst.Parent, NullableLiftingTransform.MatchGetValueOrDefault)) { + context.Step("NullCoalescingTransform (address + throw expression)", stloc); + throwInst.resultType = resultType; + result.LoadInst.Parent.ReplaceWith(nullCoalescingWithThrow); block.Instructions.RemoveRange(pos, 2); // remove store and if instruction return true; } else { @@ -150,6 +245,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms children[throwInstChildIndex] = throwInst; return false; } + + bool MatchNullableCall(ILInstruction input, PatternMatcher matcher) + { + if (!matcher(input, out var loadInst)) + return false; + if (!loadInst.MatchLdLoc(s)) + return false; + return true; + } } } } From ce04964206769b9ef6912b817d8ba4c33d23e786 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 30 Jul 2019 00:45:03 +0200 Subject: [PATCH 14/21] Reformat ThrowExpressions tests. --- .../TestCases/Pretty/ThrowExpressions.cs | 102 +++++++++++++----- 1 file changed, 74 insertions(+), 28 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs index 89742681d..df8e1f41c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs @@ -14,8 +14,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public ArgumentCheckingCtor(object simpleObj, int? nullableInt) { - this.simpleObj = simpleObj ?? throw new ArgumentNullException("simpleObj"); - this.nullableInt = nullableInt ?? throw new ArgumentNullException("nullableInt"); + this.simpleObj = (simpleObj ?? throw new ArgumentNullException("simpleObj")); + this.nullableInt = (nullableInt ?? throw new ArgumentNullException("nullableInt")); } public ArgumentCheckingCtor(string input) @@ -32,8 +32,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty private static int? GetIntOrNull(string v) { - if (int.TryParse(v, out int result)) + if (int.TryParse(v, out int result)) { return result; + } + return null; } @@ -64,10 +66,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public int? NullableIntField; public Data DataField; public Data? NullableDataField; - public int IntProperty { get; set; } - public int? NullableIntProperty { get; set; } - public Data DataProperty { get; } - public Data? NullableDataProperty { get; } + public int IntProperty { + get; + set; + } + public int? NullableIntProperty { + get; + set; + } + public Data DataProperty { + get; + } + public Data? NullableDataProperty { + get; + } } public struct Data @@ -76,18 +88,34 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public int? NullableIntField; public MoreData DataField; public MoreData? NullableDataField; - public int IntProperty { get; set; } - public int? NullableIntProperty { get; set; } - public MoreData DataProperty { get; } - public MoreData? NullableDataProperty { get; } + public int IntProperty { + get; + set; + } + public int? NullableIntProperty { + get; + set; + } + public MoreData DataProperty { + get; + } + public MoreData? NullableDataProperty { + get; + } } public struct MoreData { public int IntField; public int? NullableIntField; - public int IntProperty { get; set; } - public int? NullableIntProperty { get; set; } + public int IntProperty { + get; + set; + } + public int? NullableIntProperty { + get; + set; + } } public static int IntField; @@ -100,15 +128,33 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public Data? NullableDataField; public DataObject DataObjectField; - public static int IntProperty { get; } - public static int? NullableIntProperty { get; } - public static object ObjProperty { get; } - public int InstIntProperty { get; } - public int? InstNullableIntProperty { get; } - public object InstObjProperty { get; } - public Data DataProperty { get; } - public Data? NullableDataProperty { get; } - public DataObject DataObjectProperty { get; } + public static int IntProperty { + get; + } + public static int? NullableIntProperty { + get; + } + public static object ObjProperty { + get; + } + public int InstIntProperty { + get; + } + public int? InstNullableIntProperty { + get; + } + public object InstObjProperty { + get; + } + public Data DataProperty { + get; + } + public Data? NullableDataProperty { + get; + } + public DataObject DataObjectProperty { + get; + } public static int ReturnIntField() { @@ -146,17 +192,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty public static void UseComplexNullableStruct(ThrowExpressions inst) { Use(inst.InstNullableIntField ?? throw new Exception()); - Use(inst.NullableDataField?.IntField ?? throw new Exception()); + Use((inst.NullableDataField ?? throw new Exception()).IntField); Use(inst.NullableDataField?.NullableIntField ?? throw new Exception()); - Use(inst.NullableDataProperty?.IntField ?? throw new Exception()); + Use((inst.NullableDataProperty ?? throw new Exception()).IntField); Use(inst.NullableDataProperty?.NullableIntField ?? throw new Exception()); - Use(inst.NullableDataField?.DataField.IntField ?? throw new Exception()); + Use((inst.NullableDataField ?? throw new Exception()).DataField.IntField); Use(inst.NullableDataField?.DataField.NullableIntField ?? throw new Exception()); - Use(inst.NullableDataProperty?.DataField.IntField ?? throw new Exception()); + Use((inst.NullableDataProperty ?? throw new Exception()).DataField.IntField); Use(inst.NullableDataProperty?.DataField.NullableIntField ?? throw new Exception()); - Use(inst.NullableDataField?.DataProperty.IntField ?? throw new Exception()); + Use((inst.NullableDataField ?? throw new Exception()).DataProperty.IntField); Use(inst.NullableDataField?.DataProperty.NullableIntField ?? throw new Exception()); - Use(inst.NullableDataProperty?.DataProperty.IntField ?? throw new Exception()); + Use((inst.NullableDataProperty ?? throw new Exception()).DataProperty.IntField); Use(inst.NullableDataProperty?.DataProperty.NullableIntField ?? throw new Exception()); Use(inst.NullableDataField?.NullableDataField?.IntField ?? throw new Exception()); Use(inst.NullableDataField?.NullableDataField?.NullableIntField ?? throw new Exception()); From e4b15256ababf09b6bcf18fd04705de5aef7d771 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 3 Aug 2019 20:48:56 +0200 Subject: [PATCH 15/21] #1025: Fix method groups not being clickable. --- ICSharpCode.Decompiler/CSharp/Annotations.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler/CSharp/Annotations.cs b/ICSharpCode.Decompiler/CSharp/Annotations.cs index ea813e554..28edfe0bb 100644 --- a/ICSharpCode.Decompiler/CSharp/Annotations.cs +++ b/ICSharpCode.Decompiler/CSharp/Annotations.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Linq; +using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.Semantics; @@ -104,7 +105,17 @@ namespace ICSharpCode.Decompiler.CSharp public static ISymbol GetSymbol(this AstNode node) { var rr = node.Annotation(); - return rr != null ? rr.GetSymbol() : null; + if (rr is MethodGroupResolveResult) { + // delegate construction? + var newObj = node.Annotation(); + var funcptr = newObj?.Arguments.ElementAtOrDefault(1); + if (funcptr is LdFtn ldftn) { + return ldftn.Method; + } else if (funcptr is LdVirtFtn ldVirtFtn) { + return ldVirtFtn.Method; + } + } + return rr?.GetSymbol(); } /// From c0f954aaa6e6b495bcc882e226d93dbad4643b21 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 3 Aug 2019 20:52:31 +0200 Subject: [PATCH 16/21] Simplify nullable value types throw expression pattern. --- .../IL/Transforms/NullCoalescingTransform.cs | 119 +++--------------- 1 file changed, 20 insertions(+), 99 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs index aedf1038b..26cfd8b55 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs @@ -36,9 +36,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms public void Run(Block block, int pos, StatementTransformContext context) { if (!TransformRefTypes(block, pos, context)) { - if (!TransformThrowExpressionValueTypes(block, pos, context)) { - TransformThrowExpressionOnAddress(block, pos, context); - } + TransformThrowExpressionValueTypes(block, pos, context); } } @@ -112,15 +110,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms /// ... Call(arg1, arg2, call GetValueOrDefault(ldloca v), arg4) ... /// => /// ... Call(arg1, arg2, if.notnull(value, throw(...)), arg4) ... - /// - /// -or- - /// - /// stloc v(value) - /// stloc s(ldloca v) - /// if (logic.not(call get_HasValue(ldloc s))) throw(...) - /// ... Call(arg1, arg2, call GetValueOrDefault(ldloc s), arg4) ... - /// => - /// ... Call(arg1, arg2, if.notnull(value, throw(...)), arg4) ... /// bool TransformThrowExpressionValueTypes(Block block, int pos, StatementTransformContext context) { @@ -128,47 +117,42 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (!(block.Instructions[pos] is StLoc stloc)) return false; - int offset = 1; + if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst)) + return false; + if (!(Block.Unwrap(trueInst) is Throw throwInst)) + return false; + if (!condition.MatchLogicNot(out var arg)) + return false; + if (!(arg is CallInstruction call && NullableLiftingTransform.MatchHasValueCall(call, out ILInstruction target))) + return false; ILVariable v = stloc.Variable; - // alternative pattern using stack slot containing address of v - if (block.Instructions[pos + offset] is StLoc addrCopyStore - && addrCopyStore.Variable.Kind == VariableKind.StackSlot - && addrCopyStore.Value.MatchLdLoca(v) - && pos + 3 < block.Instructions.Count) - { - offset++; - // v turns into s in the pattern above. - v = addrCopyStore.Variable; + if (v.Type is ByReferenceType byRefType && byRefType.ElementType.IsReferenceType == false) { if (!(v.StoreCount == 1 && v.LoadCount == 2 && v.AddressCount == 0)) return false; + if (!target.MatchLdLoc(v)) + return false; } else { if (!(v.StoreCount == 1 && v.LoadCount == 0 && v.AddressCount == 2)) return false; + if (!target.MatchLdLoca(v)) + return false; } - if (!block.Instructions[pos + offset].MatchIfInstruction(out var condition, out var trueInst)) - return false; - if (!(Block.Unwrap(trueInst) is Throw throwInst)) - return false; - if (!condition.MatchLogicNot(out var arg)) - return false; - if (!MatchNullableCall(arg, NullableLiftingTransform.MatchHasValueCall)) - return false; var throwInstParent = throwInst.Parent; var throwInstChildIndex = throwInst.ChildIndex; var nullCoalescingWithThrow = new NullCoalescingInstruction( NullCoalescingKind.NullableWithValueFallback, stloc.Value, throwInst); - var resultType = NullableType.GetUnderlyingType(v.Type).GetStackType(); + var resultType = NullableType.GetUnderlyingType(call.Method.DeclaringType).GetStackType(); nullCoalescingWithThrow.UnderlyingResultType = resultType; - var result = ILInlining.FindLoadInNext(block.Instructions[pos + offset + 1], v, nullCoalescingWithThrow, InliningOptions.None); + var result = ILInlining.FindLoadInNext(block.Instructions[pos + 2], v, nullCoalescingWithThrow, InliningOptions.None); if (result.Type == ILInlining.FindResultType.Found && MatchNullableCall(result.LoadInst.Parent, NullableLiftingTransform.MatchGetValueOrDefault)) { context.Step("NullCoalescingTransform (value types + throw expression)", stloc); throwInst.resultType = resultType; result.LoadInst.Parent.ReplaceWith(nullCoalescingWithThrow); - block.Instructions.RemoveRange(pos, offset + 1); // remove store(s) and if instruction + block.Instructions.RemoveRange(pos, 2); // remove store(s) and if instruction return true; } else { // reset the primary position (see remarks on ILInstruction.Parent) @@ -182,78 +166,15 @@ namespace ICSharpCode.Decompiler.IL.Transforms { if (!matcher(input, out var loadInst)) return false; - if (offset == 1) { // Pattern 1 - if (!loadInst.MatchLdLoca(v)) + if (v.Type is ByReferenceType) { + if (!loadInst.MatchLdLoc(v)) return false; } else { - if (!loadInst.MatchLdLoc(v)) + if (!loadInst.MatchLdLoca(v)) return false; } return true; } } - - /// - /// stloc s(addressOfValue) - /// if (logic.not(call get_HasValue(ldloc s))) throw(...) - /// ... Call(arg1, arg2, call GetValueOrDefault(ldloc s), arg4) ... - /// => - /// ... Call(arg1, arg2, if.notnull(value, throw(...)), arg4) ... - /// - bool TransformThrowExpressionOnAddress(Block block, int pos, StatementTransformContext context) - { - if (pos + 2 >= block.Instructions.Count) - return false; - if (!(block.Instructions[pos] is StLoc stloc)) - return false; - var s = stloc.Variable; - if (s.Kind != VariableKind.StackSlot) - return false; - if (!(s.StoreCount == 1 && s.LoadCount == 2 && s.AddressCount == 0)) - return false; - if (!(s.Type is ByReferenceType byRef && byRef.ElementType.IsReferenceType == false)) - return false; - if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst)) - return false; - if (!(Block.Unwrap(trueInst) is Throw throwInst)) - return false; - if (!condition.MatchLogicNot(out var arg)) - return false; - if (!MatchNullableCall(arg, NullableLiftingTransform.MatchHasValueCall)) - return false; - var throwInstParent = throwInst.Parent; - var throwInstChildIndex = throwInst.ChildIndex; - var ldobj = new LdObj(stloc.Value, byRef.ElementType); - var nullCoalescingWithThrow = new NullCoalescingInstruction( - NullCoalescingKind.NullableWithValueFallback, - ldobj, - throwInst); - var resultType = NullableType.GetUnderlyingType(byRef.ElementType).GetStackType(); - nullCoalescingWithThrow.UnderlyingResultType = resultType; - var result = ILInlining.FindLoadInNext(block.Instructions[pos + 2], s, nullCoalescingWithThrow, InliningOptions.None); - if (result.Type == ILInlining.FindResultType.Found - && MatchNullableCall(result.LoadInst.Parent, NullableLiftingTransform.MatchGetValueOrDefault)) { - context.Step("NullCoalescingTransform (address + throw expression)", stloc); - throwInst.resultType = resultType; - result.LoadInst.Parent.ReplaceWith(nullCoalescingWithThrow); - block.Instructions.RemoveRange(pos, 2); // remove store and if instruction - return true; - } else { - // reset the primary position (see remarks on ILInstruction.Parent) - stloc.Value = stloc.Value; - var children = throwInstParent.Children; - children[throwInstChildIndex] = throwInst; - return false; - } - - bool MatchNullableCall(ILInstruction input, PatternMatcher matcher) - { - if (!matcher(input, out var loadInst)) - return false; - if (!loadInst.MatchLdLoc(s)) - return false; - return true; - } - } } } From fd24b5ea2675c9ac45261b97021bbb668b6d6899 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 3 Aug 2019 21:08:21 +0200 Subject: [PATCH 17/21] Simplify check for addresses --- .../IL/Transforms/NullCoalescingTransform.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs index 26cfd8b55..a9febd138 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs @@ -126,7 +126,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms if (!(arg is CallInstruction call && NullableLiftingTransform.MatchHasValueCall(call, out ILInstruction target))) return false; ILVariable v = stloc.Variable; - if (v.Type is ByReferenceType byRefType && byRefType.ElementType.IsReferenceType == false) { + if (v.StackType == StackType.Ref) { if (!(v.StoreCount == 1 && v.LoadCount == 2 && v.AddressCount == 0)) return false; if (!target.MatchLdLoc(v)) @@ -166,7 +166,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms { if (!matcher(input, out var loadInst)) return false; - if (v.Type is ByReferenceType) { + if (v.StackType == StackType.Ref) { if (!loadInst.MatchLdLoc(v)) return false; } else { From 65f9fe1076e631f9bb0e8f93a236751431678ac7 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 3 Aug 2019 21:21:24 +0200 Subject: [PATCH 18/21] Remove unused code. --- .../IL/Transforms/NullCoalescingTransform.cs | 35 +++---------------- 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs index a9febd138..2a0780636 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs @@ -102,8 +102,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; } - delegate bool PatternMatcher(ILInstruction input, out ILInstruction output); - /// /// stloc v(value) /// if (logic.not(call get_HasValue(ldloca v))) throw(...) @@ -117,26 +115,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms return false; if (!(block.Instructions[pos] is StLoc stloc)) return false; + ILVariable v = stloc.Variable; + if (!(v.StoreCount == 1 && v.LoadCount == 0 && v.AddressCount == 2)) + return false; if (!block.Instructions[pos + 1].MatchIfInstruction(out var condition, out var trueInst)) return false; if (!(Block.Unwrap(trueInst) is Throw throwInst)) return false; if (!condition.MatchLogicNot(out var arg)) return false; - if (!(arg is CallInstruction call && NullableLiftingTransform.MatchHasValueCall(call, out ILInstruction target))) + if (!(arg is CallInstruction call && NullableLiftingTransform.MatchHasValueCall(call, v))) return false; - ILVariable v = stloc.Variable; - if (v.StackType == StackType.Ref) { - if (!(v.StoreCount == 1 && v.LoadCount == 2 && v.AddressCount == 0)) - return false; - if (!target.MatchLdLoc(v)) - return false; - } else { - if (!(v.StoreCount == 1 && v.LoadCount == 0 && v.AddressCount == 2)) - return false; - if (!target.MatchLdLoca(v)) - return false; - } var throwInstParent = throwInst.Parent; var throwInstChildIndex = throwInst.ChildIndex; var nullCoalescingWithThrow = new NullCoalescingInstruction( @@ -147,7 +136,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms nullCoalescingWithThrow.UnderlyingResultType = resultType; var result = ILInlining.FindLoadInNext(block.Instructions[pos + 2], v, nullCoalescingWithThrow, InliningOptions.None); if (result.Type == ILInlining.FindResultType.Found - && MatchNullableCall(result.LoadInst.Parent, NullableLiftingTransform.MatchGetValueOrDefault)) + && NullableLiftingTransform.MatchGetValueOrDefault(result.LoadInst.Parent, v)) { context.Step("NullCoalescingTransform (value types + throw expression)", stloc); throwInst.resultType = resultType; @@ -161,20 +150,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms children[throwInstChildIndex] = throwInst; return false; } - - bool MatchNullableCall(ILInstruction input, PatternMatcher matcher) - { - if (!matcher(input, out var loadInst)) - return false; - if (v.StackType == StackType.Ref) { - if (!loadInst.MatchLdLoc(v)) - return false; - } else { - if (!loadInst.MatchLdLoca(v)) - return false; - } - return true; - } } } } From c9d2e4f7286a7b0080c2ecf88b4a9247eef1e391 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 4 Aug 2019 11:02:01 +0200 Subject: [PATCH 19/21] Fix #1599: Ignore duplicate keys in resource dictionary. --- .../Rewrite/MarkupExtensionRewritePass.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/ILSpy.BamlDecompiler/Rewrite/MarkupExtensionRewritePass.cs b/ILSpy.BamlDecompiler/Rewrite/MarkupExtensionRewritePass.cs index e2411672f..bd2bf8e31 100644 --- a/ILSpy.BamlDecompiler/Rewrite/MarkupExtensionRewritePass.cs +++ b/ILSpy.BamlDecompiler/Rewrite/MarkupExtensionRewritePass.cs @@ -77,14 +77,16 @@ namespace ILSpy.BamlDecompiler.Rewrite { var attrName = elem.Name; if (attrName != key) attrName = property.ToXName(ctx, parent, property.IsAttachedTo(type)); - var attr = new XAttribute(attrName, extValue); - var list = new List(parent.Attributes()); - if (attrName == key) - list.Insert(0, attr); - else - list.Add(attr); - parent.RemoveAttributes(); - parent.ReplaceAttributes(list); + if (!parent.Attributes(attrName).Any()) { + var attr = new XAttribute(attrName, extValue); + var list = new List(parent.Attributes()); + if (attrName == key) + list.Insert(0, attr); + else + list.Add(attr); + parent.RemoveAttributes(); + parent.ReplaceAttributes(list); + } elem.Remove(); return true; From 423e553c6aa8427be1f2d12bac48a88be693f42c Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 4 Aug 2019 11:34:26 +0200 Subject: [PATCH 20/21] Fix #545: Add hyperlink to ctor method on parentheses in attributes --- .../CSharp/Syntax/TypeSystemAstBuilder.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs index 901a1149e..2e3009fa3 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs @@ -527,12 +527,18 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax { Attribute attr = new Attribute(); attr.Type = ConvertAttributeType(attribute.AttributeType); - SimpleType st = attr.Type as SimpleType; - MemberType mt = attr.Type as MemberType; - if (st != null && st.Identifier.EndsWith("Attribute", StringComparison.Ordinal)) { - st.Identifier = st.Identifier.Substring(0, st.Identifier.Length - 9); - } else if (mt != null && mt.MemberName.EndsWith("Attribute", StringComparison.Ordinal)) { - mt.MemberName = mt.MemberName.Substring(0, mt.MemberName.Length - 9); + switch (attr.Type) { + case SimpleType st: + if (st.Identifier.EndsWith("Attribute", StringComparison.Ordinal)) + st.Identifier = st.Identifier.Substring(0, st.Identifier.Length - 9); + break; + case MemberType mt: + if (mt.MemberName.EndsWith("Attribute", StringComparison.Ordinal)) + mt.MemberName = mt.MemberName.Substring(0, mt.MemberName.Length - 9); + break; + } + if (AddResolveResultAnnotations && attribute.Constructor != null) { + attr.AddAnnotation(new MemberResolveResult(null, attribute.Constructor)); } var parameters = attribute.Constructor?.Parameters ?? EmptyList.Instance; for (int i = 0; i < attribute.FixedArguments.Length; i++) { From fc73851bb4f90c09818845982b24aceb4d79e7f8 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sun, 4 Aug 2019 14:12:15 +0200 Subject: [PATCH 21/21] #1610: Slightly more aggressive copy propagation. This helps clean up the mess left behind when stack slots are not eliminated by the normal transforms. We previously didn't do this because aggressive copy propagation could confuse the normal transforms; but this is no longer an issue with the new pass ordering. --- .../IL/Transforms/CopyPropagation.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs b/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs index d4e867e7f..3ccf25e02 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/CopyPropagation.cs @@ -105,13 +105,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms // Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga) // note: the initialization by the caller is the first store -> StoreCount must be 1 return v.IsSingleDefinition; - case VariableKind.StackSlot: - case VariableKind.ExceptionStackSlot: - // Variables are be copied only if both they and the target copy variable are generated, - // and if the variable has only a single assignment - return v.IsSingleDefinition && target.Kind == VariableKind.StackSlot; default: - return false; + // Variables can be copied if both are single-definition. + // To avoid removing too many variables, we do this only if the target + // is either a stackslot or a ref local. + Debug.Assert(target.IsSingleDefinition); + return v.IsSingleDefinition && (target.Kind == VariableKind.StackSlot || target.StackType == StackType.Ref); } default: // All instructions without special behavior that target a stack-variable can be copied.