diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs index 0e242bb97..fbac6758c 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs +++ b/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 // 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 Console.WriteLine(t); } } + +#if CS80 + public void UsingRefStruct1(UsingRefStruct s) + { + using (s) { + Console.WriteLine(s.i); + } + } +#endif } } diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index a935bfb99..b1d6dd1eb 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -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 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) diff --git a/ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs index f44d30459..5b7550927 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs @@ -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 if (IsAsync) { output.Write(".async"); } + if (IsRefStruct) { + output.Write(".ref"); + } output.Write(" ("); Variable.WriteTo(output); output.Write(" = "); diff --git a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs index 06375f71c..385e70a94 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs @@ -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 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 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 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 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 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 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));