From dbd4f3d2e1fb2357cafdf7ccc88384d8458ecde3 Mon Sep 17 00:00:00 2001
From: Siegfried Pammer <siegfriedpammer@gmail.com>
Date: Mon, 27 Jun 2022 16:32:47 +0200
Subject: [PATCH] Fix #1388: Async await on dynamic expression is not detected.
 Fix #1928: dynamic JObject() not decompile correctly

---
 .../IL/ControlFlow/AsyncAwaitDecompiler.cs    |   1 +
 .../IL/Transforms/DynamicCallSiteTransform.cs | 114 ++++++++++--------
 2 files changed, 67 insertions(+), 48 deletions(-)

diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
index 335dffa36..1e7ee2550 100644
--- a/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
+++ b/ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs
@@ -1107,6 +1107,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
 				foreach (var block in container.Blocks)
 				{
 					context.CancellationToken.ThrowIfCancellationRequested();
+					DynamicCallSiteTransform.RunOnBasicBlock(block, context);
 					if (block.Instructions.Last() is Leave leave && moveNextLeaves.Contains(leave))
 					{
 						// This is likely an 'await' block
diff --git a/ICSharpCode.Decompiler/IL/Transforms/DynamicCallSiteTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/DynamicCallSiteTransform.cs
index 2c78d42f9..4bd7ff814 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/DynamicCallSiteTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/DynamicCallSiteTransform.cs
@@ -45,48 +45,32 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			this.context = context;
 
 			Dictionary<IField, CallSiteInfo> callsites = new Dictionary<IField, CallSiteInfo>();
-			HashSet<BlockContainer> modifiedContainers = new HashSet<BlockContainer>();
 
 			foreach (var block in function.Descendants.OfType<Block>())
 			{
-				if (block.Instructions.Count < 2)
-					continue;
-				// Check if, we deal with a callsite cache field null check:
-				// if (comp(ldsfld <>p__3 == ldnull)) br IL_000c
-				// br IL_002b
-				if (!(block.Instructions.SecondToLastOrDefault() is IfInstruction ifInst))
-					continue;
-				if (!(block.Instructions.LastOrDefault() is Branch branchAfterInit))
-					continue;
-				if (!MatchCallSiteCacheNullCheck(ifInst.Condition, out var callSiteCacheField, out var callSiteDelegate, out bool invertBranches))
-					continue;
-				if (!ifInst.TrueInst.MatchBranch(out var trueBlock))
-					continue;
-				Block callSiteInitBlock, targetBlockAfterInit;
-				if (invertBranches)
-				{
-					callSiteInitBlock = branchAfterInit.TargetBlock;
-					targetBlockAfterInit = trueBlock;
-				}
-				else
-				{
-					callSiteInitBlock = trueBlock;
-					targetBlockAfterInit = branchAfterInit.TargetBlock;
-				}
-				if (!ScanCallSiteInitBlock(callSiteInitBlock, callSiteCacheField, callSiteDelegate, out var callSiteInfo, out var blockAfterInit))
-					continue;
-				if (targetBlockAfterInit != blockAfterInit)
-					continue;
-				callSiteInfo.DelegateType = callSiteDelegate;
-				callSiteInfo.ConditionalJumpToInit = ifInst;
-				callSiteInfo.Inverted = invertBranches;
-				callSiteInfo.BranchAfterInit = branchAfterInit;
-				callsites.Add(callSiteCacheField, callSiteInfo);
+				FindDynamicCallSitesInBlock(block, callsites, context);
 			}
 
-			var storesToRemove = new List<StLoc>();
+			TransformCallSites((BlockContainer)function.Body, callsites, context);
+		}
 
-			foreach (var invokeCall in function.Descendants.OfType<CallVirt>())
+		internal static void RunOnBasicBlock(Block block, ILTransformContext context)
+		{
+			if (!context.Settings.Dynamic)
+				return;
+
+			Dictionary<IField, CallSiteInfo> callsites = new Dictionary<IField, CallSiteInfo>();
+			FindDynamicCallSitesInBlock(block, callsites, context);
+			TransformCallSites((BlockContainer)block.Parent, callsites, context);
+		}
+
+		private static void TransformCallSites(BlockContainer parent, Dictionary<IField, CallSiteInfo> callsites,
+			ILTransformContext context)
+		{
+			List<StLoc> storesToRemove = new();
+			HashSet<BlockContainer> modifiedContainers = new();
+
+			foreach (var invokeCall in parent.Descendants.OfType<CallVirt>())
 			{
 				if (invokeCall.Method.DeclaringType.Kind != TypeKind.Delegate || invokeCall.Method.Name != "Invoke" || invokeCall.Arguments.Count == 0)
 					continue;
@@ -141,12 +125,46 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 				Block parentBlock = (Block)inst.Parent;
 				parentBlock.Instructions.RemoveAt(inst.ChildIndex);
 			}
+		}
 
-			foreach (var container in modifiedContainers)
-				container.SortBlocks(deleteUnreachableBlocks: true);
+		static void FindDynamicCallSitesInBlock(Block block, Dictionary<IField, CallSiteInfo> callsites, ILTransformContext context)
+		{
+			if (block.Instructions.Count < 2)
+				return;
+			// Check if, we deal with a callsite cache field null check:
+			// if (comp(ldsfld <>p__3 == ldnull)) br IL_000c
+			// br IL_002b
+			if (!(block.Instructions.SecondToLastOrDefault() is IfInstruction ifInst))
+				return;
+			if (!(block.Instructions.LastOrDefault() is Branch branchAfterInit))
+				return;
+			if (!MatchCallSiteCacheNullCheck(ifInst.Condition, out var callSiteCacheField, out var callSiteDelegate, out bool invertBranches))
+				return;
+			if (!ifInst.TrueInst.MatchBranch(out var trueBlock))
+				return;
+			Block callSiteInitBlock, targetBlockAfterInit;
+			if (invertBranches)
+			{
+				callSiteInitBlock = branchAfterInit.TargetBlock;
+				targetBlockAfterInit = trueBlock;
+			}
+			else
+			{
+				callSiteInitBlock = trueBlock;
+				targetBlockAfterInit = branchAfterInit.TargetBlock;
+			}
+			if (!ScanCallSiteInitBlock(callSiteInitBlock, callSiteCacheField, callSiteDelegate, out var callSiteInfo, out var blockAfterInit, context))
+				return;
+			if (targetBlockAfterInit != blockAfterInit)
+				return;
+			callSiteInfo.DelegateType = callSiteDelegate;
+			callSiteInfo.ConditionalJumpToInit = ifInst;
+			callSiteInfo.Inverted = invertBranches;
+			callSiteInfo.BranchAfterInit = branchAfterInit;
+			callsites.Add(callSiteCacheField, callSiteInfo);
 		}
 
-		ILInstruction MakeDynamicInstruction(CallSiteInfo callsite, CallVirt targetInvokeCall, List<ILInstruction> deadArguments)
+		static ILInstruction MakeDynamicInstruction(CallSiteInfo callsite, CallVirt targetInvokeCall, List<ILInstruction> deadArguments)
 		{
 			switch (callsite.Kind)
 			{
@@ -272,7 +290,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			}
 		}
 
-		bool ScanCallSiteInitBlock(Block callSiteInitBlock, IField callSiteCacheField, IType callSiteDelegateType, out CallSiteInfo callSiteInfo, out Block blockAfterInit)
+		static bool ScanCallSiteInitBlock(Block callSiteInitBlock, IField callSiteCacheField, IType callSiteDelegateType, out CallSiteInfo callSiteInfo, out Block blockAfterInit, ILTransformContext context)
 		{
 			callSiteInfo = default(CallSiteInfo);
 			blockAfterInit = null;
@@ -397,7 +415,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 						return false;
 					if (!callSiteInitBlock.Instructions[4 + typeArgumentsOffset].MatchStLoc(variable, out value))
 						return false;
-					if (!ExtractArgumentInfo(value, ref callSiteInfo, 5 + typeArgumentsOffset, variable))
+					if (!ExtractArgumentInfo(value, ref callSiteInfo, 5 + typeArgumentsOffset, variable, context))
 						return false;
 					return true;
 				case "GetMember":
@@ -436,7 +454,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 						return false;
 					if (!callSiteInitBlock.Instructions[3].MatchStLoc(variable, out value))
 						return false;
-					if (!ExtractArgumentInfo(value, ref callSiteInfo, 4, variable))
+					if (!ExtractArgumentInfo(value, ref callSiteInfo, 4, variable, context))
 						return false;
 					return true;
 				case "GetIndex":
@@ -484,7 +502,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 						return false;
 					if (!callSiteInitBlock.Instructions[2].MatchStLoc(variable, out value))
 						return false;
-					if (!ExtractArgumentInfo(value, ref callSiteInfo, 3, variable))
+					if (!ExtractArgumentInfo(value, ref callSiteInfo, 3, variable, context))
 						return false;
 					return true;
 				case "UnaryOperation":
@@ -523,7 +541,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 						return false;
 					if (!callSiteInitBlock.Instructions[3].MatchStLoc(variable, out value))
 						return false;
-					if (!ExtractArgumentInfo(value, ref callSiteInfo, 4, variable))
+					if (!ExtractArgumentInfo(value, ref callSiteInfo, 4, variable, context))
 						return false;
 					return true;
 				default:
@@ -531,7 +549,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			}
 		}
 
-		bool ExtractArgumentInfo(ILInstruction value, ref CallSiteInfo callSiteInfo, int instructionOffset, ILVariable variable)
+		static bool ExtractArgumentInfo(ILInstruction value, ref CallSiteInfo callSiteInfo, int instructionOffset, ILVariable variable, ILTransformContext context)
 		{
 			if (!(value is NewArr newArr2 && newArr2.Type.FullName == "Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo" && newArr2.Indices.Count == 1 && newArr2.Indices[0].MatchLdcI4(out var numberOfArguments)))
 				return false;
@@ -560,7 +578,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			return true;
 		}
 
-		bool MatchCallSiteCacheNullCheck(ILInstruction condition, out IField callSiteCacheField, out IType callSiteDelegate, out bool invertBranches)
+		static bool MatchCallSiteCacheNullCheck(ILInstruction condition, out IField callSiteCacheField, out IType callSiteDelegate, out bool invertBranches)
 		{
 			callSiteCacheField = null;
 			callSiteDelegate = null;
@@ -579,7 +597,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			return true;
 		}
 
-		struct CallSiteInfo
+		internal struct CallSiteInfo
 		{
 			public bool Inverted;
 			public ILInstruction BranchAfterInit;
@@ -596,7 +614,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
 			public string MemberName;
 		}
 
-		enum BinderMethodKind
+		internal enum BinderMethodKind
 		{
 			BinaryOperation,
 			Convert,