diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 852443f6c..67fc813d1 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -82,6 +82,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/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.Tests/TestCases/Pretty/ThrowExpressions.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs new file mode 100644 index 000000000..df8e1f41c --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs @@ -0,0 +1,247 @@ +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 ?? throw new Exception()).IntField); + Use(inst.NullableDataField?.NullableIntField ?? throw new Exception()); + Use((inst.NullableDataProperty ?? throw new Exception()).IntField); + Use(inst.NullableDataProperty?.NullableIntField ?? throw new Exception()); + Use((inst.NullableDataField ?? throw new Exception()).DataField.IntField); + Use(inst.NullableDataField?.DataField.NullableIntField ?? throw new Exception()); + Use((inst.NullableDataProperty ?? throw new Exception()).DataField.IntField); + Use(inst.NullableDataProperty?.DataField.NullableIntField ?? throw new Exception()); + Use((inst.NullableDataField ?? throw new Exception()).DataProperty.IntField); + Use(inst.NullableDataField?.DataProperty.NullableIntField ?? 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()); + 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) + { + + } + } +} 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/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(); } /// diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index 0eee44f79..e2026384e 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"); @@ -561,7 +561,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)) @@ -949,6 +949,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()); + } + protected internal override TranslatedExpression VisitUserDefinedLogicOperator(UserDefinedLogicOperator inst, TranslationContext context) { var left = Translate(inst.Left, inst.Method.Parameters[0].Type).ConvertTo(inst.Method.Parameters[0].Type, this); @@ -1521,7 +1528,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 { @@ -1945,7 +1952,7 @@ namespace ICSharpCode.Decompiler.CSharp 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 @@ -2138,7 +2145,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 +2192,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 +2431,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; @@ -2658,7 +2664,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 = 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 { targetType = value.Type.Equals(SpecialType.NullType) ? fallback.Type : value.Type; diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs index 4747df060..cb52b8385 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/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++) { diff --git a/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs b/ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs index f695f369e..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) { @@ -227,6 +238,9 @@ namespace ICSharpCode.Decompiler.CSharp .WithRR(convAnnotation.ConversionResolveResult) .ConvertTo(targetType, expressionBuilder, checkForOverflow, allowImplicitConversion); } + 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 && tupleExpr.Elements.Count == targetTupleType.ElementTypes.Length) { diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index bb4a45c39..da888d71d 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; @@ -118,7 +119,7 @@ namespace ICSharpCode.Decompiler if (introduceRefModifiersOnStructs || introduceReadonlyAndInModifiers || nonTrailingNamedArguments || refExtensionMethods) 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) @@ -874,6 +875,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/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index 6c985b465..386673716 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -384,6 +384,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index 5788e8901..bfc2f3e1e 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -4432,7 +4432,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 1890424b7..5a96158e8 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -268,7 +268,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/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/Instructions/TryInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs index 0158d557a..fe7753b2c 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs @@ -362,4 +362,9 @@ namespace ICSharpCode.Decompiler.IL } } } + + public partial class Throw + { + internal StackType resultType = StackType.Void; + } } 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. diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index f3f642562..d1d8a0e69 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; } diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs index 0717bc112..2a0780636 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,18 +35,13 @@ 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); + } } /// /// Handles NullCoalescingInstruction case 1: reference types. - /// - /// stloc s(valueInst) - /// if (comp(ldloc s == ldnull)) { - /// stloc s(fallbackInst) - /// } - /// => - /// stloc s(if.notnull(valueInst, fallbackInst)) /// bool TransformRefTypes(Block block, int pos, StatementTransformContext context) { @@ -58,6 +54,12 @@ 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); @@ -83,7 +85,71 @@ 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 (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 + ILInlining.InlineOneIfPossible(block, pos, InliningOptions.None, context); + return true; + } 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; + 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, v))) + return false; + var throwInstParent = throwInst.Parent; + var throwInstChildIndex = throwInst.ChildIndex; + var nullCoalescingWithThrow = new NullCoalescingInstruction( + NullCoalescingKind.NullableWithValueFallback, + stloc.Value, + throwInst); + var resultType = NullableType.GetUnderlyingType(call.Method.DeclaringType).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(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; + } + } } } 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; 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); } 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; } diff --git a/ICSharpCode.Decompiler/Semantics/Conversion.cs b/ICSharpCode.Decompiler/Semantics/Conversion.cs index 447da9891..b90107392 100644 --- a/ICSharpCode.Decompiler/Semantics/Conversion.cs +++ b/ICSharpCode.Decompiler/Semantics/Conversion.cs @@ -76,8 +76,16 @@ namespace ICSharpCode.Decompiler.Semantics /// public static readonly Conversion TryCast = new BuiltinConversion(false, 9); + /// + /// C# 6 string interpolation expression implicitly being converted to or . + /// 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) @@ -231,7 +239,11 @@ namespace ICSharpCode.Decompiler.Semantics } public override bool IsInterpolatedStringConversion => type == 10; - + + public override bool IsThrowExpressionConversion { + get { return type == 11; } + } + public override string ToString() { string name = null; @@ -263,6 +275,8 @@ namespace ICSharpCode.Decompiler.Semantics return "try cast"; case 10: return "interpolated string"; + case 11: + return "throw-expression conversion"; } return (isImplicit ? "implicit " : "explicit ") + name + " conversion"; } @@ -449,7 +463,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) + { + } + } +} 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; diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index eebafad92..110c68eef 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -935,6 +935,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 e71b775c1..9b898ea00 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -729,6 +729,9 @@ Entity could not be resolved. Cannot analyze entities from missing assembly references. Add the missing reference and try again. + + Use throw expressions + Use 'ref' extension methods