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 @@ -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
// roslyn optimizes out the try-finally; mcs has a compiler bug on using(null-literal)
public void SimpleUsingNullStatement()
@ -121,5 +140,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -121,5 +140,14 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
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 @@ -426,7 +426,7 @@ namespace ICSharpCode.Decompiler.CSharp
disposeType = exprBuilder.compilation.FindType(KnownTypeCode.IDisposable);
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);
var.Kind = VariableKind.Local;
var disposeVariable = currentFunction.RegisterVariable(
@ -463,6 +463,15 @@ namespace ICSharpCode.Decompiler.CSharp @@ -463,6 +463,15 @@ namespace ICSharpCode.Decompiler.CSharp
EmbeddedStatement = ConvertAsBlock(inst.Body)
}.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)

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

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

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

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

Loading…
Cancel
Save