Browse Source

Add support for C# 8.0 disposable ref structs

pull/2113/head
Siegfried Pammer 5 years ago
parent
commit
dab70964a5
  1. 28
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs
  2. 11
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  3. 5
      ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs
  4. 46
      ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs

28
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs

@ -39,6 +39,25 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
} }
} }
#if CS80
[StructLayout(LayoutKind.Sequential, Size = 1)]
public ref struct UsingRefStruct
{
public int i;
public UsingRefStruct(int i)
{
this.i = i;
Console.WriteLine(i);
}
public void Dispose()
{
throw new NotImplementedException();
}
}
#endif
#if LEGACY_CSC #if LEGACY_CSC
// roslyn optimizes out the try-finally; mcs has a compiler bug on using(null-literal) // roslyn optimizes out the try-finally; mcs has a compiler bug on using(null-literal)
public void SimpleUsingNullStatement() public void SimpleUsingNullStatement()
@ -121,5 +140,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine(t); Console.WriteLine(t);
} }
} }
#if CS80
public void UsingRefStruct1(UsingRefStruct s)
{
using (s) {
Console.WriteLine(s.i);
}
}
#endif
} }
} }

11
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -426,7 +426,7 @@ namespace ICSharpCode.Decompiler.CSharp
disposeType = exprBuilder.compilation.FindType(KnownTypeCode.IDisposable); disposeType = exprBuilder.compilation.FindType(KnownTypeCode.IDisposable);
disposeTypeMethodName = "Dispose"; disposeTypeMethodName = "Dispose";
} }
if (!inst.ResourceExpression.MatchLdNull() && !NullableType.GetUnderlyingType(var.Type).GetAllBaseTypes().Any(b => b.IsKnownType(knownTypeCode))) { if (!IsValidInCSharp(inst, knownTypeCode)) {
Debug.Assert(var.Kind == VariableKind.UsingLocal); Debug.Assert(var.Kind == VariableKind.UsingLocal);
var.Kind = VariableKind.Local; var.Kind = VariableKind.Local;
var disposeVariable = currentFunction.RegisterVariable( var disposeVariable = currentFunction.RegisterVariable(
@ -463,6 +463,15 @@ namespace ICSharpCode.Decompiler.CSharp
EmbeddedStatement = ConvertAsBlock(inst.Body) EmbeddedStatement = ConvertAsBlock(inst.Body)
}.WithILInstruction(inst); }.WithILInstruction(inst);
} }
bool IsValidInCSharp(UsingInstruction inst, KnownTypeCode code)
{
if (inst.ResourceExpression.MatchLdNull())
return true;
if (inst.IsRefStruct)
return true;
return NullableType.GetUnderlyingType(var.Type).GetAllBaseTypes().Any(b => b.IsKnownType(code));
}
} }
Statement TransformToForeach(UsingInstruction inst, Expression resource) Statement TransformToForeach(UsingInstruction inst, Expression resource)

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

@ -38,6 +38,8 @@ namespace ICSharpCode.Decompiler.IL
{ {
public bool IsAsync { get; set; } public bool IsAsync { get; set; }
public bool IsRefStruct { get; set; }
public override void WriteTo(ITextOutput output, ILAstWritingOptions options) public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{ {
WriteILRange(output, options); WriteILRange(output, options);
@ -45,6 +47,9 @@ namespace ICSharpCode.Decompiler.IL
if (IsAsync) { if (IsAsync) {
output.Write(".async"); output.Write(".async");
} }
if (IsRefStruct) {
output.Write(".ref");
}
output.Write(" ("); output.Write(" (");
Variable.WriteTo(output); Variable.WriteTo(output);
output.Write(" = "); output.Write(" = ");

46
ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs

@ -89,7 +89,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
context.Step("UsingTransform", tryFinally); context.Step("UsingTransform", tryFinally);
storeInst.Variable.Kind = VariableKind.UsingLocal; storeInst.Variable.Kind = VariableKind.UsingLocal;
block.Instructions.RemoveAt(i); block.Instructions.RemoveAt(i);
block.Instructions[i - 1] = new UsingInstruction(storeInst.Variable, storeInst.Value, tryFinally.TryBlock).WithILRange(storeInst); block.Instructions[i - 1] = new UsingInstruction(storeInst.Variable, storeInst.Value, tryFinally.TryBlock) {
IsRefStruct = context.Settings.IntroduceRefModifiersOnStructs && storeInst.Variable.Type.Kind == TypeKind.Struct && storeInst.Variable.Type.IsByRefLike
}.WithILRange(storeInst);
return true; return true;
} }
@ -152,6 +154,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
if (NullableType.GetUnderlyingType(type).GetAllBaseTypes().Any(b => b.IsKnownType(KnownTypeCode.IDisposable))) if (NullableType.GetUnderlyingType(type).GetAllBaseTypes().Any(b => b.IsKnownType(KnownTypeCode.IDisposable)))
return true; return true;
if (context.Settings.IntroduceRefModifiersOnStructs && type.Kind == TypeKind.Struct && type.IsByRefLike)
return true;
// General GetEnumerator-pattern? // General GetEnumerator-pattern?
if (!type.GetMethods(m => m.Name == "GetEnumerator" && m.TypeParameters.Count == 0 && m.Parameters.Count == 0).Any(m => ImplementsForeachPattern(m.ReturnType))) if (!type.GetMethods(m => m.Name == "GetEnumerator" && m.TypeParameters.Count == 0 && m.Parameters.Count == 0).Any(m => ImplementsForeachPattern(m.ReturnType)))
return false; return false;
@ -198,11 +202,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
} }
bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isReference, bool usingNull, out int numObjVarLoadsInCheck, in string disposeMethodFullName, KnownTypeCode disposeTypeCode) bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isReference, bool usingNull, out int numObjVarLoadsInCheck, string disposeMethodFullName, KnownTypeCode disposeTypeCode)
{ {
numObjVarLoadsInCheck = 2; numObjVarLoadsInCheck = 2;
ILInstruction disposeInvocation; ILInstruction disposeInvocation;
CallVirt callVirt; CallInstruction disposeCall;
if (objVar.Type.IsKnownType(KnownTypeCode.NullableOfT)) { if (objVar.Type.IsKnownType(KnownTypeCode.NullableOfT)) {
if (checkInst.MatchIfInstruction(out var condition, out var disposeInst)) { if (checkInst.MatchIfInstruction(out var condition, out var disposeInst)) {
if (!NullableLiftingTransform.MatchHasValueCall(condition, objVar)) if (!NullableLiftingTransform.MatchHasValueCall(condition, objVar))
@ -219,16 +223,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!UnwrapAwait(ref disposeInvocation)) if (!UnwrapAwait(ref disposeInvocation))
return false; return false;
} }
callVirt = disposeInvocation as CallVirt; disposeCall = disposeInvocation as CallVirt;
if (callVirt == null) if (disposeCall == null)
return false; return false;
if (callVirt.Method.FullName != disposeMethodFullName) if (disposeCall.Method.FullName != disposeMethodFullName)
return false; return false;
if (callVirt.Method.Parameters.Count > 0) if (disposeCall.Method.Parameters.Count > 0)
return false; return false;
if (callVirt.Arguments.Count != 1) if (disposeCall.Arguments.Count != 1)
return false; return false;
var firstArg = callVirt.Arguments.FirstOrDefault(); var firstArg = disposeCall.Arguments.FirstOrDefault();
if (!(firstArg.MatchUnboxAny(out var innerArg1, out var unboxType) && unboxType.IsKnownType(disposeTypeCode))) { if (!(firstArg.MatchUnboxAny(out var innerArg1, out var unboxType) && unboxType.IsKnownType(disposeTypeCode))) {
if (!firstArg.MatchAddressOf(out var innerArg2, out _)) if (!firstArg.MatchAddressOf(out var innerArg2, out _))
return false; return false;
@ -251,7 +255,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!(cv.Arguments.FirstOrDefault() is NullableUnwrap unwrap)) if (!(cv.Arguments.FirstOrDefault() is NullableUnwrap unwrap))
return false; return false;
numObjVarLoadsInCheck = 1; numObjVarLoadsInCheck = 1;
callVirt = cv; disposeCall = cv;
target = unwrap.Argument; target = unwrap.Argument;
} else if (isReference) { } else if (isReference) {
// reference types have a null check. // reference types have a null check.
@ -273,7 +277,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (target.MatchBox(out var newTarget, out var type) && type.Equals(objVar.Type)) if (target.MatchBox(out var newTarget, out var type) && type.Equals(objVar.Type))
target = newTarget; target = newTarget;
callVirt = cv; disposeCall = cv;
} else if (objVar.Type.Kind == TypeKind.Struct && objVar.Type.IsByRefLike) {
if (!(checkInst is Call call && call.Method.DeclaringType == objVar.Type))
return false;
target = call.Arguments.FirstOrDefault();
if (target == null)
return false;
if (call.Method.Name != "Dispose")
return false;
disposeMethodFullName = call.Method.FullName;
disposeCall = call;
} else { } else {
if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) { if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) {
if (!UnwrapAwait(ref checkInst)) if (!UnwrapAwait(ref checkInst))
@ -288,17 +302,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
boxedValue = type.IsReferenceType != true; boxedValue = type.IsReferenceType != true;
target = newTarget; target = newTarget;
} }
callVirt = cv; disposeCall = cv;
} }
if (callVirt.Method.FullName != disposeMethodFullName) if (disposeCall.Method.FullName != disposeMethodFullName)
return false; return false;
if (callVirt.Method.Parameters.Count > 0) if (disposeCall.Method.Parameters.Count > 0)
return false; return false;
if (callVirt.Arguments.Count != 1) if (disposeCall.Arguments.Count != 1)
return false; return false;
return target.MatchLdLocRef(objVar) return target.MatchLdLocRef(objVar)
|| (boxedValue && target.MatchLdLoc(objVar)) || (boxedValue && target.MatchLdLoc(objVar))
|| (usingNull && callVirt.Arguments[0].MatchLdNull()) || (usingNull && disposeCall.Arguments[0].MatchLdNull())
|| (isReference && checkInst is NullableRewrap || (isReference && checkInst is NullableRewrap
&& target.MatchIsInst(out var arg, out var type2) && target.MatchIsInst(out var arg, out var type2)
&& arg.MatchLdLoc(objVar) && type2.IsKnownType(disposeTypeCode)); && arg.MatchLdLoc(objVar) && type2.IsKnownType(disposeTypeCode));

Loading…
Cancel
Save