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.

447 lines
19 KiB

  1. // Copyright (c) 2015 Siegfried Pammer
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Linq;
  20. using ICSharpCode.Decompiler.TypeSystem;
  21. namespace ICSharpCode.Decompiler.IL.Transforms
  22. {
  23. /// <summary>
  24. /// Constructs compound assignments and inline assignments.
  25. /// </summary>
  26. public class TransformAssignment : IBlockTransform
  27. {
  28. BlockTransformContext context;
  29. void IBlockTransform.Run(Block block, BlockTransformContext context)
  30. {
  31. this.context = context;
  32. for (int i = block.Instructions.Count - 1; i >= 0; i--) {
  33. if (TransformPostIncDecOperatorOnAddress(block, i) || TransformPostIncDecOnStaticField(block, i) || TransformCSharp4PostIncDecOperatorOnAddress(block, i)) {
  34. block.Instructions.RemoveAt(i);
  35. continue;
  36. }
  37. if (TransformPostIncDecOperator(block, i)) {
  38. block.Instructions.RemoveAt(i);
  39. continue;
  40. }
  41. if (TransformInlineAssignmentStObj(block, i))
  42. continue;
  43. if (TransformInlineCompoundAssignmentCall(block, i))
  44. continue;
  45. if (TransformRoslynCompoundAssignmentCall(block, i))
  46. continue;
  47. if (TransformRoslynPostIncDecOperatorOnAddress(block, i))
  48. continue;
  49. }
  50. }
  51. /// <code>
  52. /// stloc s(value)
  53. /// stloc l(ldloc s)
  54. /// stobj(..., ldloc s)
  55. /// -->
  56. /// stloc l(stobj (..., value))
  57. /// </code>
  58. /// -or-
  59. /// <code>
  60. /// stloc s(value)
  61. /// stobj (..., ldloc s)
  62. /// -->
  63. /// stloc s(stobj (..., value))
  64. /// </code>
  65. bool TransformInlineAssignmentStObj(Block block, int i)
  66. {
  67. var inst = block.Instructions[i] as StLoc;
  68. // in some cases it can be a compiler-generated local
  69. if (inst == null || (inst.Variable.Kind != VariableKind.StackSlot && inst.Variable.Kind != VariableKind.Local))
  70. return false;
  71. var nextInst = block.Instructions.ElementAtOrDefault(i + 1);
  72. ILInstruction replacement;
  73. StObj fieldStore;
  74. ILVariable local;
  75. if (nextInst is StLoc) { // instance fields
  76. var localStore = (StLoc)nextInst;
  77. if (localStore.Variable.Kind == VariableKind.StackSlot || !localStore.Value.MatchLdLoc(inst.Variable))
  78. return false;
  79. var memberStore = block.Instructions.ElementAtOrDefault(i + 2);
  80. if (memberStore is StObj) {
  81. fieldStore = memberStore as StObj;
  82. if (!fieldStore.Value.MatchLdLoc(inst.Variable))
  83. return false;
  84. replacement = new StObj(fieldStore.Target, inst.Value, fieldStore.Type);
  85. } else { // otherwise it must be local
  86. return TransformInlineAssignmentLocal(block, i);
  87. }
  88. context.Step("Inline assignment to instance field", fieldStore);
  89. local = localStore.Variable;
  90. block.Instructions.RemoveAt(i + 1);
  91. } else if (nextInst is StObj) { // static fields
  92. fieldStore = (StObj)nextInst;
  93. if (!fieldStore.Value.MatchLdLoc(inst.Variable) || (fieldStore.Target.MatchLdFlda(out var target, out _) && target.MatchLdLoc(inst.Variable)))
  94. return false;
  95. context.Step("Inline assignment to static field", fieldStore);
  96. local = inst.Variable;
  97. replacement = new StObj(fieldStore.Target, inst.Value, fieldStore.Type);
  98. } else {
  99. return false;
  100. }
  101. block.Instructions.RemoveAt(i + 1);
  102. inst.ReplaceWith(new StLoc(local, replacement));
  103. return true;
  104. }
  105. /// <code>
  106. /// stloc s(binary(callvirt(getter), value))
  107. /// callvirt (setter, ldloc s)
  108. /// (followed by single usage of s in next instruction)
  109. /// -->
  110. /// stloc s(compound.op.new(callvirt(getter), value))
  111. /// </code>
  112. bool TransformInlineCompoundAssignmentCall(Block block, int i)
  113. {
  114. var mainStLoc = block.Instructions[i] as StLoc;
  115. // in some cases it can be a compiler-generated local
  116. if (mainStLoc == null || (mainStLoc.Variable.Kind != VariableKind.StackSlot && mainStLoc.Variable.Kind != VariableKind.Local))
  117. return false;
  118. BinaryNumericInstruction binary = mainStLoc.Value as BinaryNumericInstruction;
  119. ILVariable localVariable = mainStLoc.Variable;
  120. if (!localVariable.IsSingleDefinition)
  121. return false;
  122. if (localVariable.LoadCount != 2)
  123. return false;
  124. var getterCall = binary?.Left as CallInstruction;
  125. var setterCall = block.Instructions.ElementAtOrDefault(i + 1) as CallInstruction;
  126. if (!MatchingGetterAndSetterCalls(getterCall, setterCall))
  127. return false;
  128. if (!setterCall.Arguments.Last().MatchLdLoc(localVariable))
  129. return false;
  130. var next = block.Instructions.ElementAtOrDefault(i + 2);
  131. if (next == null)
  132. return false;
  133. if (next.Descendants.Where(d => d.MatchLdLoc(localVariable)).Count() != 1)
  134. return false;
  135. if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, getterCall.Method.ReturnType))
  136. return false;
  137. context.Step($"Inline compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall);
  138. block.Instructions.RemoveAt(i + 1); // remove setter call
  139. binary.ReplaceWith(new CompoundAssignmentInstruction(
  140. binary, getterCall, binary.Right,
  141. getterCall.Method.ReturnType, CompoundAssignmentType.EvaluatesToNewValue));
  142. return true;
  143. }
  144. /// <summary>
  145. /// Roslyn compound assignment that's not inline within another instruction.
  146. /// </summary>
  147. bool TransformRoslynCompoundAssignmentCall(Block block, int i)
  148. {
  149. // stloc variable(callvirt get_Property(ldloc obj))
  150. // callvirt set_Property(ldloc obj, binary.op(ldloc variable, ldc.i4 1))
  151. // => compound.op.new(callvirt get_Property(ldloc obj), ldc.i4 1)
  152. if (!(block.Instructions[i] is StLoc stloc))
  153. return false;
  154. if (!(stloc.Variable.IsSingleDefinition && stloc.Variable.LoadCount == 1))
  155. return false;
  156. var getterCall = stloc.Value as CallInstruction;
  157. var setterCall = block.Instructions[i + 1] as CallInstruction;
  158. if (!(MatchingGetterAndSetterCalls(getterCall, setterCall)))
  159. return false;
  160. var binary = setterCall.Arguments.Last() as BinaryNumericInstruction;
  161. if (binary == null || !binary.Left.MatchLdLoc(stloc.Variable))
  162. return false;
  163. if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, getterCall.Method.ReturnType))
  164. return false;
  165. context.Step($"Compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall);
  166. block.Instructions.RemoveAt(i + 1); // remove setter call
  167. stloc.ReplaceWith(new CompoundAssignmentInstruction(
  168. binary, getterCall, binary.Right,
  169. getterCall.Method.ReturnType, CompoundAssignmentType.EvaluatesToNewValue));
  170. return true;
  171. }
  172. static bool MatchingGetterAndSetterCalls(CallInstruction getterCall, CallInstruction setterCall)
  173. {
  174. if (getterCall == null || setterCall == null || !IsSameMember(getterCall.Method.AccessorOwner, setterCall.Method.AccessorOwner))
  175. return false;
  176. var owner = getterCall.Method.AccessorOwner as IProperty;
  177. if (owner == null || !IsSameMember(getterCall.Method, owner.Getter) || !IsSameMember(setterCall.Method, owner.Setter))
  178. return false;
  179. if (setterCall.Arguments.Count != getterCall.Arguments.Count + 1)
  180. return false;
  181. // Ensure that same arguments are passed to getterCall and setterCall:
  182. for (int j = 0; j < getterCall.Arguments.Count; j++) {
  183. if (!SemanticHelper.IsPure(getterCall.Arguments[j].Flags))
  184. return false;
  185. if (!getterCall.Arguments[j].Match(setterCall.Arguments[j]).Success)
  186. return false;
  187. }
  188. return true;
  189. }
  190. /// <summary>
  191. /// Transform compound assignments where the return value is not being used,
  192. /// or where there's an inlined assignment within the setter call.
  193. /// </summary>
  194. /// <remarks>
  195. /// Called by ExpressionTransforms.
  196. /// </remarks>
  197. internal static bool HandleCallCompoundAssign(CallInstruction setterCall, StatementTransformContext context)
  198. {
  199. // callvirt set_Property(ldloc S_1, binary.op(callvirt get_Property(ldloc S_1), value))
  200. // ==> compound.op.new(callvirt(callvirt get_Property(ldloc S_1)), value)
  201. var setterValue = setterCall.Arguments.LastOrDefault();
  202. var storeInSetter = setterValue as StLoc;
  203. if (storeInSetter != null) {
  204. // callvirt set_Property(ldloc S_1, stloc v(binary.op(callvirt get_Property(ldloc S_1), value)))
  205. // ==> stloc v(compound.op.new(callvirt(callvirt get_Property(ldloc S_1)), value))
  206. setterValue = storeInSetter.Value;
  207. }
  208. if (!(setterValue is BinaryNumericInstruction binary))
  209. return false;
  210. var getterCall = binary.Left as CallInstruction;
  211. if (!MatchingGetterAndSetterCalls(getterCall, setterCall))
  212. return false;
  213. if (!CompoundAssignmentInstruction.IsBinaryCompatibleWithType(binary, getterCall.Method.ReturnType))
  214. return false;
  215. context.Step($"Compound assignment to '{getterCall.Method.AccessorOwner.Name}'", setterCall);
  216. ILInstruction newInst = new CompoundAssignmentInstruction(
  217. binary, getterCall, binary.Right,
  218. getterCall.Method.ReturnType, CompoundAssignmentType.EvaluatesToNewValue);
  219. if (storeInSetter != null) {
  220. storeInSetter.Value = newInst;
  221. newInst = storeInSetter;
  222. context.RequestRerun(); // moving stloc to top-level might trigger inlining
  223. }
  224. setterCall.ReplaceWith(newInst);
  225. return true;
  226. }
  227. /// <code>
  228. /// stloc s(value)
  229. /// stloc l(ldloc s)
  230. /// -->
  231. /// stloc s(stloc l(value))
  232. /// </code>
  233. bool TransformInlineAssignmentLocal(Block block, int i)
  234. {
  235. var inst = block.Instructions[i] as StLoc;
  236. var nextInst = block.Instructions.ElementAtOrDefault(i + 1) as StLoc;
  237. if (inst == null || nextInst == null)
  238. return false;
  239. if (nextInst.Variable.Kind == VariableKind.StackSlot || !nextInst.Value.MatchLdLoc(inst.Variable))
  240. return false;
  241. context.Step("Inline assignment to local variable", inst);
  242. var value = inst.Value;
  243. var var = nextInst.Variable;
  244. var stackVar = inst.Variable;
  245. block.Instructions.RemoveAt(i);
  246. nextInst.ReplaceWith(new StLoc(stackVar, new StLoc(var, value)));
  247. return true;
  248. }
  249. /// <code>
  250. /// stloc s(ldloc l)
  251. /// stloc l(binary.op(ldloc s, ldc.i4 1))
  252. /// -->
  253. /// stloc s(block {
  254. /// stloc s2(ldloc l)
  255. /// stloc l(binary.op(ldloc s2, ldc.i4 1))
  256. /// final: ldloc s2
  257. /// })
  258. /// </code>
  259. bool TransformPostIncDecOperator(Block block, int i)
  260. {
  261. var inst = block.Instructions[i] as StLoc;
  262. var nextInst = block.Instructions.ElementAtOrDefault(i + 1) as StLoc;
  263. if (inst == null || nextInst == null || !inst.Value.MatchLdLoc(out var l) || !ILVariableEqualityComparer.Instance.Equals(l, nextInst.Variable))
  264. return false;
  265. var binary = nextInst.Value as BinaryNumericInstruction;
  266. if (inst.Variable.Kind != VariableKind.StackSlot || nextInst.Variable.Kind == VariableKind.StackSlot || binary == null)
  267. return false;
  268. if (binary.IsLifted)
  269. return false;
  270. if ((binary.Operator != BinaryNumericOperator.Add && binary.Operator != BinaryNumericOperator.Sub) || !binary.Left.MatchLdLoc(inst.Variable) || !binary.Right.MatchLdcI4(1))
  271. return false;
  272. context.Step($"TransformPostIncDecOperator", inst);
  273. var tempStore = context.Function.RegisterVariable(VariableKind.StackSlot, inst.Variable.Type);
  274. var assignment = new Block(BlockType.PostfixOperator);
  275. assignment.Instructions.Add(new StLoc(tempStore, new LdLoc(nextInst.Variable)));
  276. assignment.Instructions.Add(new StLoc(nextInst.Variable, new BinaryNumericInstruction(binary.Operator, new LdLoc(tempStore), new LdcI4(1), binary.CheckForOverflow, binary.Sign)));
  277. assignment.FinalInstruction = new LdLoc(tempStore);
  278. nextInst.ReplaceWith(new StLoc(inst.Variable, assignment));
  279. return true;
  280. }
  281. /// ldaddress ::= ldelema | ldflda | ldsflda;
  282. /// <code>
  283. /// stloc s(ldaddress)
  284. /// stloc l(ldobj(ldloc s))
  285. /// stobj(ldloc s, binary.op(ldloc l, ldc.i4 1))
  286. /// -->
  287. /// stloc l(compound.op.old(ldobj(ldaddress), ldc.i4 1))
  288. /// </code>
  289. bool TransformPostIncDecOperatorOnAddress(Block block, int i)
  290. {
  291. var inst = block.Instructions[i] as StLoc;
  292. var nextInst = block.Instructions.ElementAtOrDefault(i + 1) as StLoc;
  293. var stobj = block.Instructions.ElementAtOrDefault(i + 2) as StObj;
  294. if (inst == null || nextInst == null || stobj == null)
  295. return false;
  296. if (!inst.Variable.IsSingleDefinition || inst.Variable.LoadCount != 2)
  297. return false;
  298. if (!(inst.Value is LdElema || inst.Value is LdFlda || inst.Value is LdsFlda))
  299. return false;
  300. ILInstruction target;
  301. IType targetType;
  302. if (nextInst.Variable.Kind == VariableKind.StackSlot || !nextInst.Value.MatchLdObj(out target, out targetType) || !target.MatchLdLoc(inst.Variable))
  303. return false;
  304. if (!stobj.Target.MatchLdLoc(inst.Variable))
  305. return false;
  306. var binary = stobj.Value as BinaryNumericInstruction;
  307. if (binary == null || !binary.Left.MatchLdLoc(nextInst.Variable) || !binary.Right.MatchLdcI4(1)
  308. || (binary.Operator != BinaryNumericOperator.Add && binary.Operator != BinaryNumericOperator.Sub))
  309. return false;
  310. context.Step($"TransformPostIncDecOperator", inst);
  311. var assignment = new CompoundAssignmentInstruction(binary, new LdObj(inst.Value, targetType), binary.Right, targetType, CompoundAssignmentType.EvaluatesToOldValue);
  312. stobj.ReplaceWith(new StLoc(nextInst.Variable, assignment));
  313. block.Instructions.RemoveAt(i + 1);
  314. return true;
  315. }
  316. /// <code>
  317. /// stloc l(ldobj(ldflda(target)))
  318. /// stobj(ldflda(target), binary.op(ldloc l, ldc.i4 1))
  319. /// -->
  320. /// compound.op.old(ldobj(ldflda(target)), ldc.i4 1)
  321. /// </code>
  322. bool TransformRoslynPostIncDecOperatorOnAddress(Block block, int i)
  323. {
  324. var inst = block.Instructions[i] as StLoc;
  325. var stobj = block.Instructions.ElementAtOrDefault(i + 1) as StObj;
  326. if (inst == null || stobj == null)
  327. return false;
  328. if (!inst.Variable.IsSingleDefinition || inst.Variable.LoadCount != 1)
  329. return false;
  330. if (!inst.Value.MatchLdObj(out var loadTarget, out var loadType) || !loadTarget.MatchLdFlda(out var fieldTarget, out var field))
  331. return false;
  332. if (!stobj.Target.MatchLdFlda(out var fieldTarget2, out var field2))
  333. return false;
  334. if (!fieldTarget.Match(fieldTarget2).Success || !field.Equals(field2))
  335. return false;
  336. var binary = stobj.Value as BinaryNumericInstruction;
  337. if (binary == null || !binary.Left.MatchLdLoc(inst.Variable) || !binary.Right.MatchLdcI4(1)
  338. || (binary.Operator != BinaryNumericOperator.Add && binary.Operator != BinaryNumericOperator.Sub))
  339. return false;
  340. context.Step("TransformRoslynPostIncDecOperator", inst);
  341. stobj.ReplaceWith(new CompoundAssignmentInstruction(binary, inst.Value, binary.Right, loadType, CompoundAssignmentType.EvaluatesToOldValue));
  342. block.Instructions.RemoveAt(i);
  343. return true;
  344. }
  345. /// <code>
  346. /// stloc s(ldflda)
  347. /// stloc s2(ldobj(ldflda(ldloc s)))
  348. /// stloc l(ldloc s2)
  349. /// stobj (ldflda(ldloc s), binary.add(ldloc s2, ldc.i4 1))
  350. /// -->
  351. /// stloc l(compound.op.old(ldobj(ldflda(ldflda)), ldc.i4 1))
  352. /// </code>
  353. bool TransformCSharp4PostIncDecOperatorOnAddress(Block block, int i)
  354. {
  355. var baseFieldAddress = block.Instructions[i] as StLoc;
  356. var fieldValue = block.Instructions.ElementAtOrDefault(i + 1) as StLoc;
  357. var fieldValueCopyToLocal = block.Instructions.ElementAtOrDefault(i + 2) as StLoc;
  358. var stobj = block.Instructions.ElementAtOrDefault(i + 3) as StObj;
  359. if (baseFieldAddress == null || fieldValue == null || fieldValueCopyToLocal == null || stobj == null)
  360. return false;
  361. if (baseFieldAddress.Variable.Kind != VariableKind.StackSlot || fieldValue.Variable.Kind != VariableKind.StackSlot || fieldValueCopyToLocal.Variable.Kind != VariableKind.Local)
  362. return false;
  363. IType t;
  364. IField targetField;
  365. ILInstruction targetFieldLoad, baseFieldAddressLoad2;
  366. if (!fieldValue.Value.MatchLdObj(out targetFieldLoad, out t))
  367. return false;
  368. ILInstruction baseAddress;
  369. if (baseFieldAddress.Value is LdFlda) {
  370. IField targetField2;
  371. ILInstruction baseFieldAddressLoad3;
  372. if (!targetFieldLoad.MatchLdFlda(out baseFieldAddressLoad2, out targetField) || !baseFieldAddressLoad2.MatchLdLoc(baseFieldAddress.Variable))
  373. return false;
  374. if (!stobj.Target.MatchLdFlda(out baseFieldAddressLoad3, out targetField2) || !baseFieldAddressLoad3.MatchLdLoc(baseFieldAddress.Variable) || !IsSameMember(targetField, targetField2))
  375. return false;
  376. baseAddress = new LdFlda(baseFieldAddress.Value, targetField);
  377. } else if (baseFieldAddress.Value is LdElema) {
  378. if (!targetFieldLoad.MatchLdLoc(baseFieldAddress.Variable) || !stobj.Target.MatchLdLoc(baseFieldAddress.Variable))
  379. return false;
  380. baseAddress = baseFieldAddress.Value;
  381. } else {
  382. return false;
  383. }
  384. BinaryNumericInstruction binary = stobj.Value as BinaryNumericInstruction;
  385. if (binary == null || !binary.Left.MatchLdLoc(fieldValue.Variable) || !binary.Right.MatchLdcI4(1)
  386. || (binary.Operator != BinaryNumericOperator.Add && binary.Operator != BinaryNumericOperator.Sub))
  387. return false;
  388. context.Step($"TransformCSharp4PostIncDecOperatorOnAddress", baseFieldAddress);
  389. var assignment = new CompoundAssignmentInstruction(binary, new LdObj(baseAddress, t), binary.Right, t, CompoundAssignmentType.EvaluatesToOldValue);
  390. stobj.ReplaceWith(new StLoc(fieldValueCopyToLocal.Variable, assignment));
  391. block.Instructions.RemoveAt(i + 2);
  392. block.Instructions.RemoveAt(i + 1);
  393. return true;
  394. }
  395. /// <code>
  396. /// stloc s(ldobj(ldsflda))
  397. /// stobj (ldsflda, binary.op(ldloc s, ldc.i4 1))
  398. /// -->
  399. /// stloc s(compound.op.old(ldobj(ldsflda), ldc.i4 1))
  400. /// </code>
  401. bool TransformPostIncDecOnStaticField(Block block, int i)
  402. {
  403. var inst = block.Instructions[i] as StLoc;
  404. var stobj = block.Instructions.ElementAtOrDefault(i + 1) as StObj;
  405. if (inst == null || stobj == null)
  406. return false;
  407. ILInstruction target;
  408. IType type;
  409. IField field, field2;
  410. if (inst.Variable.Kind != VariableKind.StackSlot || !inst.Value.MatchLdObj(out target, out type) || !target.MatchLdsFlda(out field))
  411. return false;
  412. if (!stobj.Target.MatchLdsFlda(out field2) || !IsSameMember(field, field2))
  413. return false;
  414. var binary = stobj.Value as BinaryNumericInstruction;
  415. if (binary == null || !binary.Left.MatchLdLoc(inst.Variable) || !binary.Right.MatchLdcI4(1))
  416. return false;
  417. context.Step($"TransformPostIncDecOnStaticField", inst);
  418. var assignment = new CompoundAssignmentInstruction(binary, inst.Value, binary.Right, type, CompoundAssignmentType.EvaluatesToOldValue);
  419. stobj.ReplaceWith(new StLoc(inst.Variable, assignment));
  420. return true;
  421. }
  422. static bool IsSameMember(IMember a, IMember b)
  423. {
  424. if (a == null || b == null)
  425. return false;
  426. a = a.MemberDefinition;
  427. b = b.MemberDefinition;
  428. return a.Equals(b);
  429. }
  430. }
  431. }