MethodBody.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. //
  2. // Author:
  3. // Jb Evain (jbevain@gmail.com)
  4. //
  5. // Copyright (c) 2008 - 2015 Jb Evain
  6. // Copyright (c) 2008 - 2011 Novell, Inc.
  7. //
  8. // Licensed under the MIT/X11 license.
  9. //
  10. using System;
  11. using System.Threading;
  12. using ILRuntime.Mono.Collections.Generic;
  13. namespace ILRuntime.Mono.Cecil.Cil {
  14. public sealed class MethodBody {
  15. readonly internal MethodDefinition method;
  16. internal ParameterDefinition this_parameter;
  17. internal int max_stack_size;
  18. internal int code_size;
  19. internal bool init_locals;
  20. internal MetadataToken local_var_token;
  21. internal Collection<Instruction> instructions;
  22. internal Collection<ExceptionHandler> exceptions;
  23. internal Collection<VariableDefinition> variables;
  24. public MethodDefinition Method {
  25. get { return method; }
  26. }
  27. public int MaxStackSize {
  28. get { return max_stack_size; }
  29. set { max_stack_size = value; }
  30. }
  31. public int CodeSize {
  32. get { return code_size; }
  33. }
  34. public bool InitLocals {
  35. get { return init_locals; }
  36. set { init_locals = value; }
  37. }
  38. public MetadataToken LocalVarToken {
  39. get { return local_var_token; }
  40. set { local_var_token = value; }
  41. }
  42. public Collection<Instruction> Instructions {
  43. get {
  44. if (instructions == null)
  45. Interlocked.CompareExchange (ref instructions, new InstructionCollection (method), null);
  46. return instructions;
  47. }
  48. }
  49. public bool HasExceptionHandlers {
  50. get { return !exceptions.IsNullOrEmpty (); }
  51. }
  52. public Collection<ExceptionHandler> ExceptionHandlers {
  53. get {
  54. if (exceptions == null)
  55. Interlocked.CompareExchange (ref exceptions, new Collection<ExceptionHandler> (), null);
  56. return exceptions;
  57. }
  58. }
  59. public bool HasVariables {
  60. get { return !variables.IsNullOrEmpty (); }
  61. }
  62. public Collection<VariableDefinition> Variables {
  63. get {
  64. if (variables == null)
  65. Interlocked.CompareExchange (ref variables, new VariableDefinitionCollection (this.method), null);
  66. return variables;
  67. }
  68. }
  69. public ParameterDefinition ThisParameter {
  70. get {
  71. if (method == null || method.DeclaringType == null)
  72. throw new NotSupportedException ();
  73. if (!method.HasThis)
  74. return null;
  75. if (this_parameter == null)
  76. Interlocked.CompareExchange (ref this_parameter, CreateThisParameter (method), null);
  77. return this_parameter;
  78. }
  79. }
  80. static ParameterDefinition CreateThisParameter (MethodDefinition method)
  81. {
  82. var parameter_type = method.DeclaringType as TypeReference;
  83. if (parameter_type.HasGenericParameters) {
  84. var instance = new GenericInstanceType (parameter_type, parameter_type.GenericParameters.Count);
  85. for (int i = 0; i < parameter_type.GenericParameters.Count; i++)
  86. instance.GenericArguments.Add (parameter_type.GenericParameters [i]);
  87. parameter_type = instance;
  88. }
  89. if (parameter_type.IsValueType || parameter_type.IsPrimitive)
  90. parameter_type = new ByReferenceType (parameter_type);
  91. return new ParameterDefinition (parameter_type, method);
  92. }
  93. public MethodBody (MethodDefinition method)
  94. {
  95. this.method = method;
  96. }
  97. public ILProcessor GetILProcessor ()
  98. {
  99. return new ILProcessor (this);
  100. }
  101. }
  102. sealed class VariableDefinitionCollection : Collection<VariableDefinition> {
  103. readonly MethodDefinition method;
  104. internal VariableDefinitionCollection (MethodDefinition method)
  105. {
  106. this.method = method;
  107. }
  108. internal VariableDefinitionCollection (MethodDefinition method, int capacity)
  109. : base (capacity)
  110. {
  111. this.method = method;
  112. }
  113. protected override void OnAdd (VariableDefinition item, int index)
  114. {
  115. item.index = index;
  116. }
  117. protected override void OnInsert (VariableDefinition item, int index)
  118. {
  119. item.index = index;
  120. UpdateVariableIndices (index, 1);
  121. }
  122. protected override void OnSet (VariableDefinition item, int index)
  123. {
  124. item.index = index;
  125. }
  126. protected override void OnRemove (VariableDefinition item, int index)
  127. {
  128. UpdateVariableIndices (index + 1, -1, item);
  129. item.index = -1;
  130. }
  131. void UpdateVariableIndices (int startIndex, int offset, VariableDefinition variableToRemove = null)
  132. {
  133. for (int i = startIndex; i < size; i++)
  134. items [i].index = i + offset;
  135. var debug_info = method == null ? null : method.debug_info;
  136. if (debug_info == null || debug_info.Scope == null)
  137. return;
  138. foreach (var scope in debug_info.GetScopes ()) {
  139. if (!scope.HasVariables)
  140. continue;
  141. var variables = scope.Variables;
  142. int variableDebugInfoIndexToRemove = -1;
  143. for (int i = 0; i < variables.Count; i++) {
  144. var variable = variables [i];
  145. // If a variable is being removed detect if it has debug info counterpart, if so remove that as well.
  146. // Note that the debug info can be either resolved (has direct reference to the VariableDefinition)
  147. // or unresolved (has only the number index of the variable) - this needs to handle both cases.
  148. if (variableToRemove != null &&
  149. ((variable.index.IsResolved && variable.index.ResolvedVariable == variableToRemove) ||
  150. (!variable.index.IsResolved && variable.Index == variableToRemove.Index))) {
  151. variableDebugInfoIndexToRemove = i;
  152. continue;
  153. }
  154. // For unresolved debug info updates indeces to keep them pointing to the same variable.
  155. if (!variable.index.IsResolved && variable.Index >= startIndex) {
  156. variable.index = new VariableIndex (variable.Index + offset);
  157. }
  158. }
  159. if (variableDebugInfoIndexToRemove >= 0)
  160. variables.RemoveAt (variableDebugInfoIndexToRemove);
  161. }
  162. }
  163. }
  164. class InstructionCollection : Collection<Instruction> {
  165. readonly MethodDefinition method;
  166. internal InstructionCollection (MethodDefinition method)
  167. {
  168. this.method = method;
  169. }
  170. internal InstructionCollection (MethodDefinition method, int capacity)
  171. : base (capacity)
  172. {
  173. this.method = method;
  174. }
  175. protected override void OnAdd (Instruction item, int index)
  176. {
  177. if (index == 0)
  178. return;
  179. var previous = items [index - 1];
  180. previous.next = item;
  181. item.previous = previous;
  182. }
  183. protected override void OnInsert (Instruction item, int index)
  184. {
  185. int startOffset = 0;
  186. if (size != 0) {
  187. var current = items [index];
  188. if (current == null) {
  189. var last = items [index - 1];
  190. last.next = item;
  191. item.previous = last;
  192. return;
  193. }
  194. startOffset = current.Offset;
  195. var previous = current.previous;
  196. if (previous != null) {
  197. previous.next = item;
  198. item.previous = previous;
  199. }
  200. current.previous = item;
  201. item.next = current;
  202. }
  203. UpdateLocalScopes (null, null);
  204. }
  205. protected override void OnSet (Instruction item, int index)
  206. {
  207. var current = items [index];
  208. item.previous = current.previous;
  209. item.next = current.next;
  210. current.previous = null;
  211. current.next = null;
  212. UpdateLocalScopes (item, current);
  213. }
  214. protected override void OnRemove (Instruction item, int index)
  215. {
  216. var previous = item.previous;
  217. if (previous != null)
  218. previous.next = item.next;
  219. var next = item.next;
  220. if (next != null)
  221. next.previous = item.previous;
  222. RemoveSequencePoint (item);
  223. UpdateLocalScopes (item, next ?? previous);
  224. item.previous = null;
  225. item.next = null;
  226. }
  227. void RemoveSequencePoint (Instruction instruction)
  228. {
  229. var debug_info = method.debug_info;
  230. if (debug_info == null || !debug_info.HasSequencePoints)
  231. return;
  232. var sequence_points = debug_info.sequence_points;
  233. for (int i = 0; i < sequence_points.Count; i++) {
  234. if (sequence_points [i].Offset == instruction.offset) {
  235. sequence_points.RemoveAt (i);
  236. return;
  237. }
  238. }
  239. }
  240. void UpdateLocalScopes (Instruction removedInstruction, Instruction existingInstruction)
  241. {
  242. var debug_info = method.debug_info;
  243. if (debug_info == null)
  244. return;
  245. // Local scopes store start/end pair of "instruction offsets". Instruction offset can be either resolved, in which case it
  246. // has a reference to Instruction, or unresolved in which case it stores numerical offset (instruction offset in the body).
  247. // Typically local scopes loaded from PE/PDB files will be resolved, but it's not a requirement.
  248. // Each instruction has its own offset, which is populated on load, but never updated (this would be pretty expensive to do).
  249. // Instructions created during the editting will typically have offset 0 (so incorrect).
  250. // Local scopes created during editing will also likely be resolved (so no numerical offsets).
  251. // So while local scopes which are unresolved are relatively rare if they appear, manipulating them based
  252. // on the offsets allone is pretty hard (since we can't rely on correct offsets of instructions).
  253. // On the other hand resolved local scopes are easy to maintain, since they point to instructions and thus inserting
  254. // instructions is basically a no-op and removing instructions is as easy as changing the pointer.
  255. // For this reason the algorithm here is:
  256. // - First make sure that all instruction offsets are resolved - if not - resolve them
  257. // - First time this will be relatively expensinve as it will walk the entire method body to convert offsets to instruction pointers
  258. // Almost all local scopes are stored in the "right" order (sequentially per start offsets), so the code uses a simple one-item
  259. // cache instruction<->offset to avoid walking instructions multiple times (that would only happen for scopes which are out of order).
  260. // - Subsequent calls should be cheap as it will only walk all local scopes without doing anything
  261. // - If there was an edit on local scope which makes some of them unresolved, the cost is proportional
  262. // - Then update as necessary by manipulaitng instruction references alone
  263. InstructionOffsetCache cache = new InstructionOffsetCache () {
  264. Offset = 0,
  265. Index = 0,
  266. Instruction = items [0]
  267. };
  268. UpdateLocalScope (debug_info.Scope, removedInstruction, existingInstruction, ref cache);
  269. }
  270. void UpdateLocalScope (ScopeDebugInformation scope, Instruction removedInstruction, Instruction existingInstruction, ref InstructionOffsetCache cache)
  271. {
  272. if (scope == null)
  273. return;
  274. if (!scope.Start.IsResolved)
  275. scope.Start = ResolveInstructionOffset (scope.Start, ref cache);
  276. if (!scope.Start.IsEndOfMethod && scope.Start.ResolvedInstruction == removedInstruction)
  277. scope.Start = new InstructionOffset (existingInstruction);
  278. if (scope.HasScopes) {
  279. foreach (var subScope in scope.Scopes)
  280. UpdateLocalScope (subScope, removedInstruction, existingInstruction, ref cache);
  281. }
  282. if (!scope.End.IsResolved)
  283. scope.End = ResolveInstructionOffset (scope.End, ref cache);
  284. if (!scope.End.IsEndOfMethod && scope.End.ResolvedInstruction == removedInstruction)
  285. scope.End = new InstructionOffset (existingInstruction);
  286. }
  287. struct InstructionOffsetCache {
  288. public int Offset;
  289. public int Index;
  290. public Instruction Instruction;
  291. }
  292. InstructionOffset ResolveInstructionOffset(InstructionOffset inputOffset, ref InstructionOffsetCache cache)
  293. {
  294. if (inputOffset.IsResolved)
  295. return inputOffset;
  296. int offset = inputOffset.Offset;
  297. if (cache.Offset == offset)
  298. return new InstructionOffset (cache.Instruction);
  299. if (cache.Offset > offset) {
  300. // This should be rare - we're resolving offset pointing to a place before the current cache position
  301. // resolve by walking the instructions from start and don't cache the result.
  302. int size = 0;
  303. for (int i = 0; i < items.Length; i++) {
  304. if (size == offset)
  305. return new InstructionOffset (items [i]);
  306. if (size > offset)
  307. return new InstructionOffset (items [i - 1]);
  308. size += items [i].GetSize ();
  309. }
  310. // Offset is larger than the size of the body - so it points after the end
  311. return new InstructionOffset ();
  312. } else {
  313. // The offset points after the current cache position - so continue counting and update the cache
  314. int size = cache.Offset;
  315. for (int i = cache.Index; i < items.Length; i++) {
  316. cache.Index = i;
  317. cache.Offset = size;
  318. cache.Instruction = items [i];
  319. if (cache.Offset == offset)
  320. return new InstructionOffset (cache.Instruction);
  321. if (cache.Offset > offset)
  322. return new InstructionOffset (items [i - 1]);
  323. size += items [i].GetSize ();
  324. }
  325. return new InstructionOffset ();
  326. }
  327. }
  328. }
  329. }