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.

129 lines
4.9 KiB

  1. // Copyright (c) 2021 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.Diagnostics;
  20. using ICSharpCode.Decompiler.TypeSystem;
  21. namespace ICSharpCode.Decompiler.IL.Transforms
  22. {
  23. public class InterpolatedStringTransform : IStatementTransform
  24. {
  25. void IStatementTransform.Run(Block block, int pos, StatementTransformContext context)
  26. {
  27. if (!context.Settings.StringInterpolation)
  28. return;
  29. int interpolationStart = pos;
  30. int interpolationEnd;
  31. ILInstruction insertionPoint;
  32. // stloc v(newobj DefaultInterpolatedStringHandler..ctor(ldc.i4 literalLength, ldc.i4 formattedCount))
  33. if (block.Instructions[pos] is StLoc {
  34. Variable: ILVariable { Kind: VariableKind.Local } v,
  35. Value: NewObj { Arguments: { Count: 2 } } newObj
  36. } stloc
  37. && v.Type.IsKnownType(KnownTypeCode.DefaultInterpolatedStringHandler)
  38. && newObj.Method.DeclaringType.IsKnownType(KnownTypeCode.DefaultInterpolatedStringHandler)
  39. && newObj.Arguments[0].MatchLdcI4(out _)
  40. && newObj.Arguments[1].MatchLdcI4(out _))
  41. {
  42. // { call MethodName(ldloca v, ...) }
  43. do
  44. {
  45. pos++;
  46. }
  47. while (IsKnownCall(block, pos, v));
  48. interpolationEnd = pos;
  49. // ... call ToStringAndClear(ldloca v) ...
  50. if (!FindToStringAndClear(block, pos, interpolationStart, interpolationEnd, v, out insertionPoint))
  51. {
  52. return;
  53. }
  54. if (!(v.StoreCount == 1 && v.AddressCount == interpolationEnd - interpolationStart && v.LoadCount == 0))
  55. {
  56. return;
  57. }
  58. }
  59. else
  60. {
  61. return;
  62. }
  63. context.Step($"Transform DefaultInterpolatedStringHandler {v.Name}", stloc);
  64. v.Kind = VariableKind.InitializerTarget;
  65. var replacement = new Block(BlockKind.InterpolatedString);
  66. for (int i = interpolationStart; i < interpolationEnd; i++)
  67. {
  68. replacement.Instructions.Add(block.Instructions[i]);
  69. }
  70. var callToStringAndClear = insertionPoint;
  71. insertionPoint.ReplaceWith(replacement);
  72. replacement.FinalInstruction = callToStringAndClear;
  73. block.Instructions.RemoveRange(interpolationStart, interpolationEnd - interpolationStart);
  74. }
  75. private bool IsKnownCall(Block block, int pos, ILVariable v)
  76. {
  77. if (pos >= block.Instructions.Count - 1)
  78. return false;
  79. if (!(block.Instructions[pos] is Call call))
  80. return false;
  81. if (!(call.Arguments.Count > 1))
  82. return false;
  83. if (!call.Arguments[0].MatchLdLoca(v))
  84. return false;
  85. if (call.Method.IsStatic)
  86. return false;
  87. if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.DefaultInterpolatedStringHandler))
  88. return false;
  89. switch (call.Method.Name)
  90. {
  91. case "AppendLiteral" when call.Arguments.Count == 2 && call.Arguments[1] is LdStr:
  92. case "AppendFormatted" when call.Arguments.Count == 2:
  93. case "AppendFormatted" when call.Arguments.Count == 3 && call.Arguments[2] is LdStr:
  94. case "AppendFormatted" when call.Arguments.Count == 3 && call.Arguments[2] is LdcI4:
  95. case "AppendFormatted" when call.Arguments.Count == 4 && call.Arguments[2] is LdcI4 && call.Arguments[3] is LdStr:
  96. break;
  97. default:
  98. return false;
  99. }
  100. return true;
  101. }
  102. private bool FindToStringAndClear(Block block, int pos, int interpolationStart, int interpolationEnd, ILVariable v, out ILInstruction insertionPoint)
  103. {
  104. insertionPoint = null;
  105. if (pos >= block.Instructions.Count)
  106. return false;
  107. // find
  108. // ... call ToStringAndClear(ldloca v) ...
  109. // in block.Instructions[pos]
  110. for (int i = interpolationStart; i < interpolationEnd; i++)
  111. {
  112. var result = ILInlining.FindLoadInNext(block.Instructions[pos], v, block.Instructions[i], InliningOptions.None);
  113. if (result.Type != ILInlining.FindResultType.Found)
  114. return false;
  115. insertionPoint ??= result.LoadInst.Parent;
  116. Debug.Assert(insertionPoint == result.LoadInst.Parent);
  117. }
  118. return insertionPoint is Call {
  119. Arguments: { Count: 1 },
  120. Method: { Name: "ToStringAndClear", IsStatic: false }
  121. };
  122. }
  123. }
  124. }