mirror of https://github.com/icsharpcode/ILSpy.git
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							500 lines
						
					
					
						
							17 KiB
						
					
					
				
			
		
		
	
	
							500 lines
						
					
					
						
							17 KiB
						
					
					
				// Copyright (c) 2017 Siegfried Pammer | 
						|
//  | 
						|
// 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 System; | 
						|
using System.Collections.Generic; | 
						|
using System.Linq; | 
						|
using System.Text; | 
						|
using System.Threading.Tasks; | 
						|
 | 
						|
using ICSharpCode.Decompiler.TypeSystem; | 
						|
 | 
						|
namespace ICSharpCode.Decompiler.IL.Transforms | 
						|
{ | 
						|
	public class UsingTransform : IBlockTransform | 
						|
	{ | 
						|
		BlockTransformContext context; | 
						|
 | 
						|
		void IBlockTransform.Run(Block block, BlockTransformContext context) | 
						|
		{ | 
						|
			if (!context.Settings.UsingStatement) | 
						|
				return; | 
						|
			this.context = context; | 
						|
			for (int i = block.Instructions.Count - 1; i >= 0; i--) | 
						|
			{ | 
						|
				if (TransformUsing(block, i)) | 
						|
				{ | 
						|
					continue; | 
						|
				} | 
						|
				if (TransformUsingVB(block, i)) | 
						|
				{ | 
						|
					continue; | 
						|
				} | 
						|
				if (TransformAsyncUsing(block, i)) | 
						|
				{ | 
						|
					continue; | 
						|
				} | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// stloc obj(resourceExpression) | 
						|
		/// .try BlockContainer { | 
						|
		///		Block IL_0003(incoming: 1) { | 
						|
		///			call WriteLine(ldstr "using (null)") | 
						|
		///			leave IL_0003(nop) | 
						|
		///		} | 
						|
		///	} finally BlockContainer { | 
						|
		///		Block IL_0012(incoming: 1) { | 
						|
		///			if (comp(ldloc obj != ldnull)) Block IL_001a  { | 
						|
		///				callvirt Dispose(ldnull) | 
						|
		///			} | 
						|
		///			leave IL_0012(nop) | 
						|
		///		} | 
						|
		/// } | 
						|
		/// leave IL_0000(nop) | 
						|
		/// => | 
						|
		/// using (resourceExpression) { | 
						|
		///		BlockContainer { | 
						|
		///			Block IL_0003(incoming: 1) { | 
						|
		///				call WriteLine(ldstr "using (null)") | 
						|
		///				leave IL_0003(nop) | 
						|
		///			} | 
						|
		///		} | 
						|
		/// } | 
						|
		/// </summary> | 
						|
		bool TransformUsing(Block block, int i) | 
						|
		{ | 
						|
			if (i + 1 >= block.Instructions.Count) | 
						|
				return false; | 
						|
			if (!(block.Instructions[i + 1] is TryFinally tryFinally) || !(block.Instructions[i] is StLoc storeInst)) | 
						|
				return false; | 
						|
			if (!(storeInst.Value.MatchLdNull() || CheckResourceType(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)) | 
						|
				return false; | 
						|
			if (!MatchDisposeBlock(container, storeInst.Variable, storeInst.Value.MatchLdNull())) | 
						|
				return false; | 
						|
			context.Step("UsingTransform", tryFinally); | 
						|
			storeInst.Variable.Kind = VariableKind.UsingLocal; | 
						|
			block.Instructions.RemoveAt(i + 1); | 
						|
			block.Instructions[i] = 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; | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// .try BlockContainer { | 
						|
		///		Block IL_0003(incoming: 1) { | 
						|
		///			stloc obj(resourceExpression) | 
						|
		///			call WriteLine(ldstr "using (null)") | 
						|
		///			leave IL_0003(nop) | 
						|
		///		} | 
						|
		///	} finally BlockContainer { | 
						|
		///		Block IL_0012(incoming: 1) { | 
						|
		///			if (comp(ldloc obj != ldnull)) Block IL_001a  { | 
						|
		///				callvirt Dispose(ldnull) | 
						|
		///			} | 
						|
		///			leave IL_0012(nop) | 
						|
		///		} | 
						|
		/// } | 
						|
		/// leave IL_0000(nop) | 
						|
		/// => | 
						|
		/// using (resourceExpression) { | 
						|
		///		BlockContainer { | 
						|
		///			Block IL_0003(incoming: 1) { | 
						|
		///				call WriteLine(ldstr "using (null)") | 
						|
		///				leave IL_0003(nop) | 
						|
		///			} | 
						|
		///		} | 
						|
		/// } | 
						|
		/// </summary> | 
						|
		bool TransformUsingVB(Block block, int i) | 
						|
		{ | 
						|
			if (i >= block.Instructions.Count) | 
						|
				return false; | 
						|
			if (!(block.Instructions[i] is TryFinally tryFinally)) | 
						|
				return false; | 
						|
			if (!(tryFinally.TryBlock is BlockContainer tryContainer && tryContainer.EntryPoint.Instructions.FirstOrDefault() is StLoc storeInst)) | 
						|
				return false; | 
						|
			if (!(storeInst.Value.MatchLdNull() || CheckResourceType(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, storeInst.Value.MatchLdNull())) | 
						|
				return false; | 
						|
			context.Step("UsingTransformVB", tryFinally); | 
						|
			storeInst.Variable.Kind = VariableKind.UsingLocal; | 
						|
			tryContainer.EntryPoint.Instructions.RemoveAt(0); | 
						|
			block.Instructions[i] = new UsingInstruction(storeInst.Variable, storeInst.Value, tryFinally.TryBlock); | 
						|
			return true; | 
						|
		} | 
						|
 | 
						|
		bool CheckResourceType(IType type) | 
						|
		{ | 
						|
			// non-generic IEnumerator does not implement IDisposable. | 
						|
			// This is a workaround for non-generic foreach. | 
						|
			if (type.IsKnownType(KnownTypeCode.IEnumerator) || type.GetAllBaseTypes().Any(b => b.IsKnownType(KnownTypeCode.IEnumerator))) | 
						|
				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; | 
						|
			return true; | 
						|
		} | 
						|
 | 
						|
		bool ImplementsForeachPattern(IType type) | 
						|
		{ | 
						|
			if (!type.GetMethods(m => m.Name == "MoveNext" && m.TypeParameters.Count == 0 && m.Parameters.Count == 0).Any(m => m.ReturnType.IsKnownType(KnownTypeCode.Boolean))) | 
						|
				return false; | 
						|
			if (!type.GetProperties(p => p.Name == "Current" && p.CanGet && !p.IsIndexer).Any()) | 
						|
				return false; | 
						|
			return true; | 
						|
		} | 
						|
 | 
						|
		///	finally BlockContainer { | 
						|
		///		Block IL_0012(incoming: 1) { | 
						|
		///			if (comp(ldloc obj != ldnull)) Block IL_001a  { | 
						|
		///				callvirt Dispose(obj) | 
						|
		///			} | 
						|
		///			leave IL_0012(nop) | 
						|
		///		} | 
						|
		/// } | 
						|
		bool MatchDisposeBlock(BlockContainer container, ILVariable objVar, bool usingNull, | 
						|
			in string disposeMethodFullName = "System.IDisposable.Dispose", | 
						|
			KnownTypeCode disposeTypeCode = KnownTypeCode.IDisposable) | 
						|
		{ | 
						|
			var entryPoint = container.EntryPoint; | 
						|
			if (entryPoint.IncomingEdgeCount != 1) | 
						|
				return false; | 
						|
			int pos = 0; | 
						|
			bool isReference = objVar.Type.IsReferenceType != false; | 
						|
			// optional: | 
						|
			// stloc temp(isinst TDisposable(ldloc obj)) | 
						|
			if (entryPoint.Instructions.ElementAtOrDefault(pos).MatchStLoc(out var tempVar, out var isinst)) | 
						|
			{ | 
						|
				if (!isinst.MatchIsInst(out var load, out var disposableType) || !load.MatchLdLoc(objVar) | 
						|
					|| !disposableType.IsKnownType(disposeTypeCode)) | 
						|
				{ | 
						|
					return false; | 
						|
				} | 
						|
				if (!tempVar.IsSingleDefinition) | 
						|
					return false; | 
						|
				isReference = true; | 
						|
				pos++; | 
						|
				objVar = tempVar; | 
						|
			} | 
						|
			// if (comp(ldloc obj != ldnull)) Block IL_001a  { | 
						|
			//	callvirt Dispose(obj) | 
						|
			// } | 
						|
			var checkInst = entryPoint.Instructions.ElementAtOrDefault(pos); | 
						|
			if (checkInst == null) | 
						|
				return false; | 
						|
			if (!MatchDisposeCheck(objVar, checkInst, isReference, usingNull, | 
						|
				out int numObjVarLoadsInCheck, disposeMethodFullName, disposeTypeCode)) | 
						|
			{ | 
						|
				return false; | 
						|
			} | 
						|
			// make sure the (optional) temporary is used only in the dispose check | 
						|
			if (pos > 0 && objVar.LoadCount != numObjVarLoadsInCheck) | 
						|
				return false; | 
						|
			pos++; | 
						|
			// make sure, the finally ends in a leave(nop) instruction. | 
						|
			if (!entryPoint.Instructions.ElementAtOrDefault(pos).MatchLeave(container, out var returnValue)) | 
						|
				return false; | 
						|
			if (!returnValue.MatchNop()) | 
						|
				return false; | 
						|
			// leave is the last instruction | 
						|
			return (pos + 1) == entryPoint.Instructions.Count; | 
						|
		} | 
						|
 | 
						|
		bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isReference, bool usingNull, | 
						|
			out int numObjVarLoadsInCheck, string disposeMethodFullName, KnownTypeCode disposeTypeCode) | 
						|
		{ | 
						|
			numObjVarLoadsInCheck = 2; | 
						|
			ILInstruction disposeInvocation; | 
						|
			CallInstruction disposeCall; | 
						|
			if (objVar.Type.IsKnownType(KnownTypeCode.NullableOfT)) | 
						|
			{ | 
						|
				if (checkInst.MatchIfInstruction(out var condition, out var disposeInst)) | 
						|
				{ | 
						|
					if (!NullableLiftingTransform.MatchHasValueCall(condition, objVar)) | 
						|
						return false; | 
						|
					if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1) | 
						|
						return false; | 
						|
					disposeInvocation = disposeBlock.Instructions[0]; | 
						|
				} | 
						|
				else if (checkInst.MatchNullableRewrap(out disposeInst)) | 
						|
				{ | 
						|
					disposeInvocation = disposeInst; | 
						|
				} | 
						|
				else | 
						|
				{ | 
						|
					return false; | 
						|
				} | 
						|
				if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) | 
						|
				{ | 
						|
					if (!UnwrapAwait(ref disposeInvocation)) | 
						|
						return false; | 
						|
				} | 
						|
				disposeCall = disposeInvocation as CallVirt; | 
						|
				if (disposeCall == null) | 
						|
					return false; | 
						|
				if (disposeCall.Method.FullName != disposeMethodFullName) | 
						|
					return false; | 
						|
				if (disposeCall.Method.Parameters.Count > 0) | 
						|
					return false; | 
						|
				if (disposeCall.Arguments.Count != 1) | 
						|
					return false; | 
						|
				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; | 
						|
					return NullableLiftingTransform.MatchGetValueOrDefault(innerArg2, objVar) | 
						|
						|| (innerArg2 is NullableUnwrap unwrap | 
						|
							&& unwrap.Argument.MatchLdLoc(objVar)); | 
						|
				} | 
						|
				else | 
						|
				{ | 
						|
					if (!(innerArg1.MatchBox(out firstArg, out var boxType) && boxType.IsKnownType(KnownTypeCode.NullableOfT) && | 
						|
					NullableType.GetUnderlyingType(boxType).Equals(NullableType.GetUnderlyingType(objVar.Type)))) | 
						|
						return false; | 
						|
					return firstArg.MatchLdLoc(objVar); | 
						|
				} | 
						|
			} | 
						|
			else | 
						|
			{ | 
						|
				ILInstruction target; | 
						|
				bool boxedValue = false; | 
						|
				if (isReference && checkInst is NullableRewrap rewrap) | 
						|
				{ | 
						|
					// the null check of reference types might have been transformed into "objVar?.Dispose();" | 
						|
					if (!(rewrap.Argument is CallVirt cv)) | 
						|
						return false; | 
						|
					if (!(cv.Arguments.FirstOrDefault() is NullableUnwrap unwrap)) | 
						|
						return false; | 
						|
					numObjVarLoadsInCheck = 1; | 
						|
					disposeCall = cv; | 
						|
					target = unwrap.Argument; | 
						|
				} | 
						|
				else if (isReference) | 
						|
				{ | 
						|
					// reference types have a null check. | 
						|
					if (!checkInst.MatchIfInstruction(out var condition, out var disposeInst)) | 
						|
						return false; | 
						|
					if (!MatchNullCheckOrTypeCheck(condition, ref objVar, disposeTypeCode, out var isInlinedIsInst)) | 
						|
						return false; | 
						|
					if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1) | 
						|
						return false; | 
						|
					disposeInvocation = disposeBlock.Instructions[0]; | 
						|
					if (disposeTypeCode == KnownTypeCode.IAsyncDisposable) | 
						|
					{ | 
						|
						if (!UnwrapAwait(ref disposeInvocation)) | 
						|
							return false; | 
						|
					} | 
						|
					if (!(disposeInvocation is CallVirt cv)) | 
						|
						return false; | 
						|
					target = cv.Arguments.FirstOrDefault(); | 
						|
					if (target == null) | 
						|
						return false; | 
						|
					if (target.MatchBox(out var newTarget, out var type) && type.Equals(objVar.Type)) | 
						|
						target = newTarget; | 
						|
					else if (isInlinedIsInst && target.MatchIsInst(out newTarget, out type) && type.IsKnownType(disposeTypeCode)) | 
						|
						target = newTarget; | 
						|
					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)) | 
						|
							return false; | 
						|
					} | 
						|
					if (!(checkInst is CallInstruction cv)) | 
						|
						return false; | 
						|
					target = cv.Arguments.FirstOrDefault(); | 
						|
					if (target == null) | 
						|
						return false; | 
						|
					if (target.MatchBox(out var newTarget, out var type) && type.Equals(objVar.Type)) | 
						|
					{ | 
						|
						boxedValue = type.IsReferenceType != true; | 
						|
						target = newTarget; | 
						|
					} | 
						|
					disposeCall = cv; | 
						|
				} | 
						|
				if (disposeCall.Method.FullName != disposeMethodFullName) | 
						|
					return false; | 
						|
				if (disposeCall.Method.Parameters.Count > 0) | 
						|
					return false; | 
						|
				if (disposeCall.Arguments.Count != 1) | 
						|
					return false; | 
						|
				return target.MatchLdLocRef(objVar) | 
						|
					|| (boxedValue && target.MatchLdLoc(objVar)) | 
						|
					|| (usingNull && disposeCall.Arguments[0].MatchLdNull()) | 
						|
					|| (isReference && checkInst is NullableRewrap | 
						|
						&& target.MatchIsInst(out var arg, out var type2) | 
						|
						&& arg.MatchLdLoc(objVar) && type2.IsKnownType(disposeTypeCode)); | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		bool MatchNullCheckOrTypeCheck(ILInstruction condition, ref ILVariable objVar, KnownTypeCode disposeType, out bool isInlinedIsInst) | 
						|
		{ | 
						|
			isInlinedIsInst = false; | 
						|
			if (condition.MatchCompNotEquals(out var left, out var right)) | 
						|
			{ | 
						|
				if (left.MatchIsInst(out var arg, out var type) && type.IsKnownType(disposeType)) | 
						|
				{ | 
						|
					isInlinedIsInst = true; | 
						|
					left = arg; | 
						|
				} | 
						|
				if (!left.MatchLdLoc(objVar) || !right.MatchLdNull()) | 
						|
					return false; | 
						|
				return true; | 
						|
			} | 
						|
			if (condition is MatchInstruction { | 
						|
				CheckNotNull: true, | 
						|
				CheckType: true, | 
						|
				TestedOperand: LdLoc { Variable: var v }, | 
						|
				Variable: var newObjVar | 
						|
			}) | 
						|
			{ | 
						|
				if (v != objVar) | 
						|
					return false; | 
						|
				if (!newObjVar.Type.IsKnownType(disposeType)) | 
						|
					return false; | 
						|
				objVar = newObjVar; | 
						|
				return true; | 
						|
			} | 
						|
			return false; | 
						|
		} | 
						|
 | 
						|
		/// <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 (!context.Settings.AsyncUsingAndForEachStatement) | 
						|
				return false; | 
						|
			if (i < 1 || i >= block.Instructions.Count) | 
						|
				return false; | 
						|
			if (!(block.Instructions[i] is TryFinally tryFinally) || !(block.Instructions[i - 1] is StLoc storeInst)) | 
						|
				return false; | 
						|
			if (!CheckAsyncResourceType(storeInst.Variable.Type, out string disposeMethodFullName)) | 
						|
				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, disposeMethodFullName, 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, out string disposeMethodFullName) | 
						|
		{ | 
						|
			disposeMethodFullName = null; | 
						|
			IType t = NullableType.GetUnderlyingType(type); | 
						|
			if (t.GetAllBaseTypes().Any(b => b.IsKnownType(KnownTypeCode.IAsyncDisposable))) | 
						|
			{ | 
						|
				disposeMethodFullName = "System.IAsyncDisposable.DisposeAsync"; | 
						|
				return true; | 
						|
			} | 
						|
 | 
						|
			IMethod disposeMethod = t | 
						|
				.GetMethods(m => m.Parameters.Count == 0 && m.TypeParameters.Count == 0 && m.Name == "DisposeAsync") | 
						|
				.SingleOrDefault(); | 
						|
			if (disposeMethod != null) | 
						|
			{ | 
						|
				disposeMethodFullName = disposeMethod.FullName; | 
						|
				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; | 
						|
			// TODO check type: does it match the structural 'Awaitable' pattern? | 
						|
			return true; | 
						|
		} | 
						|
	} | 
						|
}
 | 
						|
 |