Browse Source

Merge pull request #1600 from icsharpcode/throwExpression

Support for throw expression
pull/1633/head
Daniel Grunwald 6 years ago committed by GitHub
parent
commit
428f1ce7ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 6
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  3. 14
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs
  4. 247
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs
  5. 10
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs
  6. 26
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  7. 3
      ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs
  8. 16
      ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
  9. 20
      ICSharpCode.Decompiler/DecompilerSettings.cs
  10. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  11. 2
      ICSharpCode.Decompiler/IL/Instructions.cs
  12. 2
      ICSharpCode.Decompiler/IL/Instructions.tt
  13. 2
      ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs
  14. 5
      ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs
  15. 16
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  16. 82
      ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs
  17. 6
      ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs
  18. 2
      ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
  19. 12
      ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
  20. 22
      ICSharpCode.Decompiler/Semantics/Conversion.cs
  21. 29
      ICSharpCode.Decompiler/Semantics/ThrowResolveResult.cs
  22. 9
      ILSpy/Properties/Resources.Designer.cs
  23. 3
      ILSpy/Properties/Resources.resx

1
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -82,6 +82,7 @@ @@ -82,6 +82,7 @@
<Compile Include="TestCases\ILPretty\ConstantBlobs.cs" />
<Compile Include="TestCases\Pretty\OutVariables.cs" />
<None Include="TestCases\VBPretty\VBCompoundAssign.cs" />
<Compile Include="TestCases\Pretty\ThrowExpressions.cs" />
<None Include="TestCases\ILPretty\Issue1145.cs" />
<Compile Include="TestCases\ILPretty\Issue1157.cs" />
<None Include="TestCases\ILPretty\Unsafe.cs" />

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -329,6 +329,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -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)
{

14
ICSharpCode.Decompiler.Tests/TestCases/Pretty/NullPropagation.cs

@ -25,6 +25,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -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 @@ -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 @@ -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)
{

247
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ThrowExpressions.cs

@ -0,0 +1,247 @@ @@ -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>(T usage)
{
}
}
}

10
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ValueTypes.cs

@ -66,6 +66,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -66,6 +66,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
S s = this;
s.SetField();
}
public void UseField(int val)
{
UseField(Get<S>().Field);
}
}
#if CS72
@ -263,5 +268,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -263,5 +268,10 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
r.Property = 2;
#endif
}
public static void CallOnFieldOfTemporary()
{
Get<S>().Field.ToString();
}
}
}

26
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -134,7 +134,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -2420,7 +2426,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
TranslatedExpression MakeInitializerAssignment(InitializedObjectResolveResult rr, IL.Transforms.AccessPathElement memberPath,
IL.Transforms.AccessPathElement valuePath, List<TranslatedExpression> values,
IL.Transforms.AccessPathElement valuePath, List<TranslatedExpression> values,
Dictionary<ILVariable, ILInstruction> indexVariables)
{
TranslatedExpression value;
@ -2653,7 +2659,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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;

3
ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs

@ -115,6 +115,9 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver @@ -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);

16
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -163,7 +163,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -163,7 +163,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
throw new ArgumentException("descendant must be a descendant of the current node");
}
/// <summary>
/// Adds casts (if necessary) to convert this expression to the specified target type.
/// </summary>
@ -176,6 +176,17 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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 <c>this.Expression</c>,
/// 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".
/// </remarks>
public TranslatedExpression ConvertTo(IType targetType, ExpressionBuilder expressionBuilder, bool checkForOverflow = false, bool allowImplicitConversion = false)
{
@ -227,6 +238,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -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)
{

20
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -88,6 +88,7 @@ namespace ICSharpCode.Decompiler @@ -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 @@ -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 @@ -874,6 +875,23 @@ namespace ICSharpCode.Decompiler
}
}
bool throwExpressions = true;
/// <summary>
/// Gets/Sets whether throw expressions should be used.
/// </summary>
[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;
/// <summary>

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -384,6 +384,7 @@ @@ -384,6 +384,7 @@
<Compile Include="TypeSystem\ModifiedType.cs" />
<Compile Include="TypeSystem\Implementation\PinnedType.cs" />
<Compile Include="Metadata\MetadataExtensions.cs" />
<Compile Include="Semantics\ThrowResolveResult.cs" />
<Compile Include="Semantics\TupleResolveResult.cs" />
<Compile Include="TypeSystem\NormalizeTypeVisitor.cs" />
<Compile Include="TypeSystem\Nullability.cs" />

2
ICSharpCode.Decompiler/IL/Instructions.cs

@ -4370,7 +4370,7 @@ namespace ICSharpCode.Decompiler.IL @@ -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;

2
ICSharpCode.Decompiler/IL/Instructions.tt

@ -265,7 +265,7 @@ @@ -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.",

2
ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs

@ -308,7 +308,7 @@ namespace ICSharpCode.Decompiler.IL @@ -308,7 +308,7 @@ namespace ICSharpCode.Decompiler.IL
protected abstract SlotInfo GetChildSlot(int index);
#region ChildrenCollection + ChildrenEnumerator
public struct ChildrenCollection : IReadOnlyList<ILInstruction>
public readonly struct ChildrenCollection : IReadOnlyList<ILInstruction>
{
readonly ILInstruction inst;

5
ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs

@ -362,4 +362,9 @@ namespace ICSharpCode.Decompiler.IL @@ -362,4 +362,9 @@ namespace ICSharpCode.Decompiler.IL
}
}
}
public partial class Throw
{
internal StackType resultType = StackType.Void;
}
}

16
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -272,10 +272,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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 @@ -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 @@ -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;
}

82
ICSharpCode.Decompiler/IL/Transforms/NullCoalescingTransform.cs

@ -21,6 +21,7 @@ using System.Collections.Generic; @@ -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 @@ -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);
}
}
/// <summary>
/// Handles NullCoalescingInstruction case 1: reference types.
///
/// stloc s(valueInst)
/// if (comp(ldloc s == ldnull)) {
/// stloc s(fallbackInst)
/// }
/// =>
/// stloc s(if.notnull(valueInst, fallbackInst))
/// </summary>
bool TransformRefTypes(Block block, int pos, StatementTransformContext context)
{
@ -58,6 +54,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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 @@ -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;
}
/// <summary>
/// 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) ...
/// </summary>
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;
}
}
}
}

6
ICSharpCode.Decompiler/IL/Transforms/NullPropagationTransform.cs

@ -186,6 +186,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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;

2
ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs

@ -833,7 +833,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -833,7 +833,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary>
/// Matches 'logic.not(call get_HasValue(ldloca v))'
/// </summary>
static bool MatchNegatedHasValueCall(ILInstruction inst, ILVariable v)
internal static bool MatchNegatedHasValueCall(ILInstruction inst, ILVariable v)
{
return inst.MatchLogicNot(out var arg) && MatchHasValueCall(arg, v);
}

12
ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs

@ -109,7 +109,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -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 @@ -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;
}

22
ICSharpCode.Decompiler/Semantics/Conversion.cs

@ -76,8 +76,16 @@ namespace ICSharpCode.Decompiler.Semantics @@ -76,8 +76,16 @@ namespace ICSharpCode.Decompiler.Semantics
/// </summary>
public static readonly Conversion TryCast = new BuiltinConversion(false, 9);
/// <summary>
/// C# 6 string interpolation expression implicitly being converted to <see cref="System.IFormattable"/> or <see cref="System.FormattableString"/>.
/// </summary>
public static readonly Conversion ImplicitInterpolatedStringConversion = new BuiltinConversion(true, 10);
/// <summary>
/// C# 7 throw expression being converted to an arbitrary type.
/// </summary>
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 @@ -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 @@ -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 @@ -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; }
}

29
ICSharpCode.Decompiler/Semantics/ThrowResolveResult.cs

@ -0,0 +1,29 @@ @@ -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)
{
}
}
}

9
ILSpy/Properties/Resources.Designer.cs generated

@ -935,6 +935,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -935,6 +935,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Use throw expressions.
/// </summary>
public static string DecompilerSettings_UseThrowExpressions {
get {
return ResourceManager.GetString("DecompilerSettings.UseThrowExpressions", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use tuple type syntax.
/// </summary>

3
ILSpy/Properties/Resources.resx

@ -729,6 +729,9 @@ @@ -729,6 +729,9 @@
<data name="CannotAnalyzeMissingRef" xml:space="preserve">
<value>Entity could not be resolved. Cannot analyze entities from missing assembly references. Add the missing reference and try again.</value>
</data>
<data name="DecompilerSettings.UseThrowExpressions" xml:space="preserve">
<value>Use throw expressions</value>
</data>
<data name="DecompilerSettings.AllowExtensionMethodSyntaxOnRef" xml:space="preserve">
<value>Use 'ref' extension methods</value>
</data>

Loading…
Cancel
Save