From a25491362443824a854be7bb2c79e64d8f2459a3 Mon Sep 17 00:00:00 2001 From: Andreas Weizel Date: Fri, 1 Jun 2018 13:39:34 +0200 Subject: [PATCH 01/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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/17] 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 c0f954aaa6e6b495bcc882e226d93dbad4643b21 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 3 Aug 2019 20:52:31 +0200 Subject: [PATCH 15/17] 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 16/17] 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 17/17] 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; - } } } }