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/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index fefd35111..06c00d1b9 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");
@@ -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))
@@ -944,6 +944,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);
@@ -1516,7 +1523,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 {
@@ -1940,7 +1947,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
@@ -2133,7 +2140,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(
@@ -2180,8 +2187,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);
}
@@ -2420,7 +2426,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;
@@ -2653,7 +2659,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/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 cfa1c9f0a..81190cd40 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions.cs
@@ -4370,7 +4370,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 31407d783..18e6bd61e 100644
--- a/ICSharpCode.Decompiler/IL/Instructions.tt
+++ b/ICSharpCode.Decompiler/IL/Instructions.tt
@@ -265,7 +265,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/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/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