|
|
@ -34,7 +34,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
if (!context.Settings.UsingStatement) return; |
|
|
|
if (!context.Settings.UsingStatement) return; |
|
|
|
this.context = context; |
|
|
|
this.context = context; |
|
|
|
for (int i = block.Instructions.Count - 1; i >= 0; i--) { |
|
|
|
for (int i = block.Instructions.Count - 1; i >= 0; i--) { |
|
|
|
if (!TransformUsing(block, i) && !TransformUsingVB(block, i)) |
|
|
|
if (!TransformUsing(block, i) && !TransformUsingVB(block, i) && !TransformAsyncUsing(block, i)) |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
// This happens in some cases:
|
|
|
|
// This happens in some cases:
|
|
|
|
// Use correct index after transformation.
|
|
|
|
// Use correct index after transformation.
|
|
|
@ -167,7 +167,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool MatchDisposeBlock(BlockContainer container, ILVariable objVar, bool usingNull) |
|
|
|
bool MatchDisposeBlock(BlockContainer container, ILVariable objVar, bool usingNull, in string disposeMethodFullName = "System.IDisposable.Dispose", KnownTypeCode disposeTypeCode = KnownTypeCode.IDisposable) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var entryPoint = container.EntryPoint; |
|
|
|
var entryPoint = container.EntryPoint; |
|
|
|
if (entryPoint.Instructions.Count < 2 || entryPoint.Instructions.Count > 3 || entryPoint.IncomingEdgeCount != 1) |
|
|
|
if (entryPoint.Instructions.Count < 2 || entryPoint.Instructions.Count > 3 || entryPoint.IncomingEdgeCount != 1) |
|
|
@ -180,17 +180,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
if (castIndex > -1) { |
|
|
|
if (castIndex > -1) { |
|
|
|
if (!entryPoint.Instructions[castIndex].MatchStLoc(out var tempVar, out var isinst)) |
|
|
|
if (!entryPoint.Instructions[castIndex].MatchStLoc(out var tempVar, out var isinst)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!isinst.MatchIsInst(out var load, out var disposableType) || !load.MatchLdLoc(objVar) || !disposableType.IsKnownType(KnownTypeCode.IDisposable)) |
|
|
|
if (!isinst.MatchIsInst(out var load, out var disposableType) || !load.MatchLdLoc(objVar) || !disposableType.IsKnownType(disposeTypeCode)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!tempVar.IsSingleDefinition) |
|
|
|
if (!tempVar.IsSingleDefinition) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
isReference = true; |
|
|
|
isReference = true; |
|
|
|
if (!MatchDisposeCheck(tempVar, checkInst, isReference, usingNull, out int numObjVarLoadsInCheck)) |
|
|
|
if (!MatchDisposeCheck(tempVar, checkInst, isReference, usingNull, out int numObjVarLoadsInCheck, disposeMethodFullName, disposeTypeCode)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (tempVar.LoadCount != numObjVarLoadsInCheck) |
|
|
|
if (tempVar.LoadCount != numObjVarLoadsInCheck) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
if (!MatchDisposeCheck(objVar, checkInst, isReference, usingNull, out _)) |
|
|
|
if (!MatchDisposeCheck(objVar, checkInst, isReference, usingNull, out _, disposeMethodFullName, disposeTypeCode)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
if (!entryPoint.Instructions[leaveIndex].MatchLeave(container, out var returnValue) || !returnValue.MatchNop()) |
|
|
|
if (!entryPoint.Instructions[leaveIndex].MatchLeave(container, out var returnValue) || !returnValue.MatchNop()) |
|
|
@ -198,9 +198,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
return true; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isReference, bool usingNull, out int numObjVarLoadsInCheck) |
|
|
|
bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isReference, bool usingNull, out int numObjVarLoadsInCheck, in string disposeMethodFullName, KnownTypeCode disposeTypeCode) |
|
|
|
{ |
|
|
|
{ |
|
|
|
numObjVarLoadsInCheck = 2; |
|
|
|
numObjVarLoadsInCheck = 2; |
|
|
|
|
|
|
|
ILInstruction disposeInvocation; |
|
|
|
CallVirt callVirt; |
|
|
|
CallVirt callVirt; |
|
|
|
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)) { |
|
|
@ -208,22 +209,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1) |
|
|
|
if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
callVirt = disposeBlock.Instructions[0] as CallVirt; |
|
|
|
disposeInvocation = disposeBlock.Instructions[0]; |
|
|
|
} else if (checkInst.MatchNullableRewrap(out disposeInst)) { |
|
|
|
} else if (checkInst.MatchNullableRewrap(out disposeInst)) { |
|
|
|
callVirt = disposeInst as CallVirt; |
|
|
|
disposeInvocation = disposeInst; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) { |
|
|
|
|
|
|
|
if (!UnwrapAwait(ref disposeInvocation)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
callVirt = disposeInvocation as CallVirt; |
|
|
|
if (callVirt == null) |
|
|
|
if (callVirt == null) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (callVirt.Method.FullName != "System.IDisposable.Dispose") |
|
|
|
if (callVirt.Method.FullName != disposeMethodFullName) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (callVirt.Method.Parameters.Count > 0) |
|
|
|
if (callVirt.Method.Parameters.Count > 0) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (callVirt.Arguments.Count != 1) |
|
|
|
if (callVirt.Arguments.Count != 1) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
var firstArg = callVirt.Arguments.FirstOrDefault(); |
|
|
|
var firstArg = callVirt.Arguments.FirstOrDefault(); |
|
|
|
if (!(firstArg.MatchUnboxAny(out var innerArg1, out var unboxType) && unboxType.IsKnownType(KnownTypeCode.IDisposable))) { |
|
|
|
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; |
|
|
|
return NullableLiftingTransform.MatchGetValueOrDefault(innerArg2, objVar) |
|
|
|
return NullableLiftingTransform.MatchGetValueOrDefault(innerArg2, objVar) |
|
|
@ -255,7 +261,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1) |
|
|
|
if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!(disposeBlock.Instructions[0] is CallVirt cv)) |
|
|
|
disposeInvocation = disposeBlock.Instructions[0]; |
|
|
|
|
|
|
|
if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) { |
|
|
|
|
|
|
|
if (!UnwrapAwait(ref disposeInvocation)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!(disposeInvocation is CallVirt cv)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
target = cv.Arguments.FirstOrDefault(); |
|
|
|
target = cv.Arguments.FirstOrDefault(); |
|
|
|
if (target == null) |
|
|
|
if (target == null) |
|
|
@ -264,6 +275,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
target = newTarget; |
|
|
|
target = newTarget; |
|
|
|
callVirt = cv; |
|
|
|
callVirt = cv; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
|
|
|
|
if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) { |
|
|
|
|
|
|
|
if (!UnwrapAwait(ref checkInst)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
if (!(checkInst is CallVirt cv)) |
|
|
|
if (!(checkInst is CallVirt cv)) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
target = cv.Arguments.FirstOrDefault(); |
|
|
|
target = cv.Arguments.FirstOrDefault(); |
|
|
@ -275,7 +290,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
} |
|
|
|
} |
|
|
|
callVirt = cv; |
|
|
|
callVirt = cv; |
|
|
|
} |
|
|
|
} |
|
|
|
if (callVirt.Method.FullName != "System.IDisposable.Dispose") |
|
|
|
if (callVirt.Method.FullName != disposeMethodFullName) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (callVirt.Method.Parameters.Count > 0) |
|
|
|
if (callVirt.Method.Parameters.Count > 0) |
|
|
|
return false; |
|
|
|
return false; |
|
|
@ -286,8 +301,74 @@ namespace ICSharpCode.Decompiler.IL.Transforms |
|
|
|
|| (usingNull && callVirt.Arguments[0].MatchLdNull()) |
|
|
|
|| (usingNull && callVirt.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(KnownTypeCode.IDisposable)); |
|
|
|
&& arg.MatchLdLoc(objVar) && type2.IsKnownType(disposeTypeCode)); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
|
|
/// stloc test(resourceExpression)
|
|
|
|
|
|
|
|
/// .try BlockContainer {
|
|
|
|
|
|
|
|
/// Block IL_002b (incoming: 1) {
|
|
|
|
|
|
|
|
/// call Use(ldloc test)
|
|
|
|
|
|
|
|
/// leave IL_002b (nop)
|
|
|
|
|
|
|
|
/// }
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// } finally BlockContainer {
|
|
|
|
|
|
|
|
/// Block IL_0045 (incoming: 1) {
|
|
|
|
|
|
|
|
/// if (comp.o(ldloc test == ldnull)) leave IL_0045 (nop)
|
|
|
|
|
|
|
|
/// br IL_00ae
|
|
|
|
|
|
|
|
/// }
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// Block IL_00ae (incoming: 1) {
|
|
|
|
|
|
|
|
/// await(addressof System.Threading.Tasks.ValueTask(callvirt DisposeAsync(ldloc test)))
|
|
|
|
|
|
|
|
/// leave IL_0045 (nop)
|
|
|
|
|
|
|
|
/// }
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// }
|
|
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
|
|
private bool TransformAsyncUsing(Block block, int i) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (i < 1 || !context.Settings.AsyncUsingAndForEachStatement) return false; |
|
|
|
|
|
|
|
if (!(block.Instructions[i] is TryFinally tryFinally) || !(block.Instructions[i - 1] is StLoc storeInst)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!CheckAsyncResourceType(storeInst.Variable.Type)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (storeInst.Variable.Kind != VariableKind.Local) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (storeInst.Variable.LoadInstructions.Any(ld => !ld.IsDescendantOf(tryFinally))) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (storeInst.Variable.AddressInstructions.Any(la => !la.IsDescendantOf(tryFinally) || (la.IsDescendantOf(tryFinally.TryBlock) && !ILInlining.IsUsedAsThisPointerInCall(la)))) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (storeInst.Variable.StoreInstructions.Count > 1) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!(tryFinally.FinallyBlock is BlockContainer container) || !MatchDisposeBlock(container, storeInst.Variable, usingNull: false, "System.IAsyncDisposable.DisposeAsync", KnownTypeCode.IAsyncDisposable)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
context.Step("AsyncUsingTransform", tryFinally); |
|
|
|
|
|
|
|
storeInst.Variable.Kind = VariableKind.UsingLocal; |
|
|
|
|
|
|
|
block.Instructions.RemoveAt(i); |
|
|
|
|
|
|
|
block.Instructions[i - 1] = new UsingInstruction(storeInst.Variable, storeInst.Value, tryFinally.TryBlock) { IsAsync = true } |
|
|
|
|
|
|
|
.WithILRange(storeInst); |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool CheckAsyncResourceType(IType type) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (NullableType.GetUnderlyingType(type).GetAllBaseTypes().Any(b => b.IsKnownType(KnownTypeCode.IAsyncDisposable))) |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool UnwrapAwait(ref ILInstruction awaitInstruction) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
if (awaitInstruction == null) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!awaitInstruction.MatchAwait(out var arg)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!arg.MatchAddressOf(out awaitInstruction, out var type)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
if (!type.IsKnownType(KnownTypeCode.ValueTask)) |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|