Merge remote branch 'upstream/master'
[mono.git] / mcs / class / Mono.Cecil / Mono.Cecil.Cil / CodeReader.cs
1 //
2 // CodeReader.cs
3 //
4 // Author:
5 //   Jb Evain (jbevain@gmail.com)
6 //
7 // Copyright (c) 2008 - 2010 Jb Evain
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30
31 using Mono.Cecil.PE;
32 using Mono.Collections.Generic;
33
34 using RVA = System.UInt32;
35
36 namespace Mono.Cecil.Cil {
37
38         sealed class CodeReader : ByteBuffer {
39
40                 readonly internal MetadataReader reader;
41
42                 int start;
43                 Section code_section;
44
45                 MethodDefinition method;
46                 MethodBody body;
47
48                 int Offset {
49                         get { return base.position - start; }
50                 }
51
52                 CodeReader (Section section, MetadataReader reader)
53                         : base (section.Data)
54                 {
55                         this.code_section = section;
56                         this.reader = reader;
57                 }
58
59                 public static CodeReader CreateCodeReader (MetadataReader metadata)
60                 {
61                         return new CodeReader (metadata.image.MetadataSection, metadata);
62                 }
63
64                 public MethodBody ReadMethodBody (MethodDefinition method)
65                 {
66                         this.method = method;
67                         this.body = new MethodBody (method);
68
69                         reader.context = method;
70
71                         ReadMethodBody ();
72
73                         return this.body;
74                 }
75
76                 public void MoveTo (int rva)
77                 {
78                         if (!IsInSection (rva)) {
79                                 code_section = reader.image.GetSectionAtVirtualAddress ((uint) rva);
80                                 Reset (code_section.Data);
81                         }
82
83                         base.position = rva - (int) code_section.VirtualAddress;
84                 }
85
86                 bool IsInSection (int rva)
87                 {
88                         return code_section.VirtualAddress <= rva && rva < code_section.VirtualAddress + code_section.SizeOfRawData;
89                 }
90
91                 void ReadMethodBody ()
92                 {
93                         MoveTo (method.RVA);
94
95                         var flags = ReadByte ();
96                         switch (flags & 0x3) {
97                         case 0x2: // tiny
98                                 body.code_size = flags >> 2;
99                                 body.MaxStackSize = 8;
100                                 ReadCode ();
101                                 break;
102                         case 0x3: // fat
103                                 base.position--;
104                                 ReadFatMethod ();
105                                 break;
106                         default:
107                                 throw new InvalidOperationException ();
108                         }
109
110                         var symbol_reader = reader.module.SymbolReader;
111
112                         if (symbol_reader != null) {
113                                 var instructions = body.Instructions;
114                                 symbol_reader.Read (body, offset => GetInstruction (instructions, offset));
115                         }
116                 }
117
118                 void ReadFatMethod ()
119                 {
120                         var flags = ReadUInt16 ();
121                         body.max_stack_size = ReadUInt16 ();
122                         body.code_size = (int) ReadUInt32 ();
123                         body.local_var_token = new MetadataToken (ReadUInt32 ());
124                         body.init_locals = (flags & 0x10) != 0;
125
126                         if (body.local_var_token.RID != 0)
127                                 body.variables = ReadVariables (body.local_var_token);
128
129                         ReadCode ();
130
131                         if ((flags & 0x8) != 0)
132                                 ReadSection ();
133                 }
134
135                 public VariableDefinitionCollection ReadVariables (MetadataToken local_var_token)
136                 {
137                         var position = reader.position;
138                         var variables = reader.ReadVariables (local_var_token);
139                         reader.position = position;
140
141                         return variables;
142                 }
143
144                 void ReadCode ()
145                 {
146                         start = position;
147                         var code_size = body.code_size;
148
149                         if (code_size < 0 || buffer.Length <= (uint) (code_size + position))
150                                 code_size = 0;
151
152                         var end = start + code_size;
153                         var instructions = body.instructions = new InstructionCollection (code_size / 3);
154
155                         while (position < end) {
156                                 var offset = base.position - start;
157                                 var opcode = ReadOpCode ();
158                                 var current = new Instruction (offset, opcode);
159
160                                 if (opcode.OperandType != OperandType.InlineNone)
161                                         current.operand = ReadOperand (current);
162
163                                 instructions.Add (current);
164                         }
165
166                         ResolveBranches (instructions);
167                 }
168
169                 OpCode ReadOpCode ()
170                 {
171                         var il_opcode = ReadByte ();
172                         return il_opcode != 0xfe
173                                 ? OpCodes.OneByteOpCode [il_opcode]
174                                 : OpCodes.TwoBytesOpCode [ReadByte ()];
175                 }
176
177                 object ReadOperand (Instruction instruction)
178                 {
179                         switch (instruction.opcode.OperandType) {
180                         case OperandType.InlineSwitch:
181                                 var length = ReadInt32 ();
182                                 var base_offset = Offset + (4 * length);
183                                 var branches = new int [length];
184                                 for (int i = 0; i < length; i++)
185                                         branches [i] = base_offset + ReadInt32 ();
186                                 return branches;
187                         case OperandType.ShortInlineBrTarget:
188                                 return ReadSByte () + Offset;
189                         case OperandType.InlineBrTarget:
190                                 return ReadInt32 () + Offset;
191                         case OperandType.ShortInlineI:
192                                 if (instruction.opcode == OpCodes.Ldc_I4_S)
193                                         return ReadSByte ();
194
195                                 return ReadByte ();
196                         case OperandType.InlineI:
197                                 return ReadInt32 ();
198                         case OperandType.ShortInlineR:
199                                 return ReadSingle ();
200                         case OperandType.InlineR:
201                                 return ReadDouble ();
202                         case OperandType.InlineI8:
203                                 return ReadInt64 ();
204                         case OperandType.ShortInlineVar:
205                                 return GetVariable (ReadByte ());
206                         case OperandType.InlineVar:
207                                 return GetVariable (ReadUInt16 ());
208                         case OperandType.ShortInlineArg:
209                                 return GetParameter (ReadByte ());
210                         case OperandType.InlineArg:
211                                 return GetParameter (ReadUInt16 ());
212                         case OperandType.InlineSig:
213                                 return GetCallSite (ReadToken ());
214                         case OperandType.InlineString:
215                                 return GetString (ReadToken ());
216                         case OperandType.InlineTok:
217                         case OperandType.InlineType:
218                         case OperandType.InlineMethod:
219                         case OperandType.InlineField:
220                                 return reader.LookupToken (ReadToken ());
221                         default:
222                                 throw new NotSupportedException ();
223                         }
224                 }
225
226                 public string GetString (MetadataToken token)
227                 {
228                         return reader.image.UserStringHeap.Read (token.RID);
229                 }
230
231                 public ParameterDefinition GetParameter (int index)
232                 {
233                         return body.GetParameter (index);
234                 }
235
236                 public VariableDefinition GetVariable (int index)
237                 {
238                         return body.GetVariable (index);
239                 }
240
241                 public CallSite GetCallSite (MetadataToken token)
242                 {
243                         return reader.ReadCallSite (token);
244                 }
245
246                 void ResolveBranches (Collection<Instruction> instructions)
247                 {
248                         var items = instructions.items;
249                         var size = instructions.size;
250
251                         for (int i = 0; i < size; i++) {
252                                 var instruction = items [i];
253                                 switch (instruction.opcode.OperandType) {
254                                 case OperandType.ShortInlineBrTarget:
255                                 case OperandType.InlineBrTarget:
256                                         instruction.operand = GetInstruction ((int) instruction.operand);
257                                         break;
258                                 case OperandType.InlineSwitch:
259                                         var offsets = (int []) instruction.operand;
260                                         var branches = new Instruction [offsets.Length];
261                                         for (int j = 0; j < offsets.Length; j++)
262                                                 branches [j] = GetInstruction (offsets [j]);
263
264                                         instruction.operand = branches;
265                                         break;
266                                 }
267                         }
268                 }
269
270                 Instruction GetInstruction (int offset)
271                 {
272                         return GetInstruction (body.Instructions, offset);
273                 }
274
275                 static Instruction GetInstruction (Collection<Instruction> instructions, int offset)
276                 {
277                         var size = instructions.size;
278                         var items = instructions.items;
279                         if (offset < 0 || offset > items [size - 1].offset)
280                                 return null;
281
282                         int min = 0;
283                         int max = size - 1;
284                         while (min <= max) {
285                                 int mid = min + ((max - min) / 2);
286                                 var instruction = items [mid];
287                                 var instruction_offset = instruction.offset;
288
289                                 if (offset == instruction_offset)
290                                         return instruction;
291
292                                 if (offset < instruction_offset)
293                                         max = mid - 1;
294                                 else
295                                         min = mid + 1;
296                         }
297
298                         return null;
299                 }
300
301                 void ReadSection ()
302                 {
303                         Align (4);
304
305                         const byte fat_format = 0x40;
306                         const byte more_sects = 0x80;
307
308                         var flags = ReadByte ();
309                         if ((flags & fat_format) == 0)
310                                 ReadSmallSection ();
311                         else
312                                 ReadFatSection ();
313
314                         if ((flags & more_sects) != 0)
315                                 ReadSection ();
316                 }
317
318                 void ReadSmallSection ()
319                 {
320                         var count = ReadByte () / 12;
321                         Advance (2);
322
323                         ReadExceptionHandlers (
324                                 count,
325                                 () => (int) ReadUInt16 (),
326                                 () => (int) ReadByte ());
327                 }
328
329                 void ReadFatSection ()
330                 {
331                         position--;
332                         var count = (ReadInt32 () >> 8) / 24;
333
334                         ReadExceptionHandlers (
335                                 count,
336                                 ReadInt32,
337                                 ReadInt32);
338                 }
339
340                 // inline ?
341                 void ReadExceptionHandlers (int count, Func<int> read_entry, Func<int> read_length)
342                 {
343                         for (int i = 0; i < count; i++) {
344                                 var handler = new ExceptionHandler (
345                                         (ExceptionHandlerType) (read_entry () & 0x7));
346
347                                 handler.TryStart = GetInstruction (read_entry ());
348                                 handler.TryEnd = GetInstruction (handler.TryStart.Offset + read_length ());
349
350                                 handler.HandlerStart = GetInstruction (read_entry ());
351                                 handler.HandlerEnd = GetInstruction (handler.HandlerStart.Offset + read_length ());
352
353                                 ReadExceptionHandlerSpecific (handler);
354
355                                 this.body.ExceptionHandlers.Add (handler);
356                         }
357                 }
358
359                 void ReadExceptionHandlerSpecific (ExceptionHandler handler)
360                 {
361                         switch (handler.HandlerType) {
362                         case ExceptionHandlerType.Catch:
363                                 handler.CatchType = (TypeReference) reader.LookupToken (ReadToken ());
364                                 break;
365                         case ExceptionHandlerType.Filter:
366                                 handler.FilterStart = GetInstruction (ReadInt32 ());
367                                 handler.FilterEnd = handler.HandlerStart.Previous;
368                                 break;
369                         default:
370                                 Advance (4);
371                                 break;
372                         }
373                 }
374
375                 void Align (int align)
376                 {
377                         align--;
378                         Advance (((position + align) & ~align) - position);
379                 }
380
381                 public MetadataToken ReadToken ()
382                 {
383                         return new MetadataToken (ReadUInt32 ());
384                 }
385
386 #if !READ_ONLY
387
388                 public ByteBuffer PatchRawMethodBody (MethodDefinition method, CodeWriter writer, out MethodSymbols symbols)
389                 {
390                         var buffer = new ByteBuffer ();
391                         symbols = new MethodSymbols (method.Name);
392
393                         this.method = method;
394                         reader.context = method;
395
396                         MoveTo (method.RVA);
397
398                         var flags = ReadByte ();
399
400                         MetadataToken local_var_token;
401
402                         switch (flags & 0x3) {
403                         case 0x2: // tiny
404                                 buffer.WriteByte (flags);
405                                 local_var_token = MetadataToken.Zero;
406                                 symbols.code_size = flags >> 2;
407                                 PatchRawCode (buffer, symbols.code_size, writer);
408                                 break;
409                         case 0x3: // fat
410                                 base.position--;
411
412                                 PatchRawFatMethod (buffer, symbols, writer, out local_var_token);
413                                 break;
414                         default:
415                                 throw new NotSupportedException ();
416                         }
417
418                         var symbol_reader = reader.module.SymbolReader;
419                         if (symbol_reader != null && writer.metadata.write_symbols) {
420                                 symbols.method_token = GetOriginalToken (writer.metadata, method);
421                                 symbols.local_var_token = local_var_token;
422                                 symbol_reader.Read (symbols);
423                         }
424
425                         return buffer;
426                 }
427
428                 void PatchRawFatMethod (ByteBuffer buffer, MethodSymbols symbols, CodeWriter writer, out MetadataToken local_var_token)
429                 {
430                         var flags = ReadUInt16 ();
431                         buffer.WriteUInt16 (flags);
432                         buffer.WriteUInt16 (ReadUInt16 ());
433                         symbols.code_size = ReadInt32 ();
434                         buffer.WriteInt32 (symbols.code_size);
435                         local_var_token = ReadToken ();
436
437                         if (local_var_token.RID > 0) {
438                                 var variables = symbols.variables = ReadVariables (local_var_token);
439                                 buffer.WriteUInt32 (variables != null
440                                         ? writer.GetStandAloneSignature (symbols.variables).ToUInt32 ()
441                                         : 0);
442                         } else
443                                 buffer.WriteUInt32 (0);
444
445                         PatchRawCode (buffer, symbols.code_size, writer);
446
447                         if ((flags & 0x8) != 0)
448                                 PatchRawSection (buffer, writer.metadata);
449                 }
450
451                 static MetadataToken GetOriginalToken (MetadataBuilder metadata, MethodDefinition method)
452                 {
453                         MetadataToken original;
454                         if (metadata.TryGetOriginalMethodToken (method.token, out original))
455                                 return original;
456
457                         return MetadataToken.Zero;
458                 }
459
460                 void PatchRawCode (ByteBuffer buffer, int code_size, CodeWriter writer)
461                 {
462                         var metadata = writer.metadata;
463                         buffer.WriteBytes (ReadBytes (code_size));
464                         var end = buffer.position;
465                         buffer.position -= code_size;
466
467                         while (buffer.position < end) {
468                                 OpCode opcode;
469                                 var il_opcode = buffer.ReadByte ();
470                                 if (il_opcode != 0xfe) {
471                                         opcode = OpCodes.OneByteOpCode [il_opcode];
472                                 } else {
473                                         var il_opcode2 = buffer.ReadByte ();
474                                         opcode = OpCodes.TwoBytesOpCode [il_opcode2];
475                                 }
476
477                                 switch (opcode.OperandType) {
478                                 case OperandType.ShortInlineI:
479                                 case OperandType.ShortInlineBrTarget:
480                                 case OperandType.ShortInlineVar:
481                                 case OperandType.ShortInlineArg:
482                                         buffer.position += 1;
483                                         break;
484                                 case OperandType.InlineVar:
485                                 case OperandType.InlineArg:
486                                         buffer.position += 2;
487                                         break;
488                                 case OperandType.InlineBrTarget:
489                                 case OperandType.ShortInlineR:
490                                 case OperandType.InlineI:
491                                         buffer.position += 4;
492                                         break;
493                                 case OperandType.InlineI8:
494                                 case OperandType.InlineR:
495                                         buffer.position += 8;
496                                         break;
497                                 case OperandType.InlineSwitch:
498                                         var length = buffer.ReadInt32 ();
499                                         buffer.position += length * 4;
500                                         break;
501                                 case OperandType.InlineString:
502                                         var @string = GetString (new MetadataToken (buffer.ReadUInt32 ()));
503                                         buffer.position -= 4;
504                                         buffer.WriteUInt32 (
505                                                 new MetadataToken (
506                                                         TokenType.String,
507                                                         metadata.user_string_heap.GetStringIndex (@string)).ToUInt32 ());
508                                         break;
509                                 case OperandType.InlineSig:
510                                         var call_site = GetCallSite (new MetadataToken (buffer.ReadUInt32 ()));
511                                         buffer.position -= 4;
512                                         buffer.WriteUInt32 (writer.GetStandAloneSignature (call_site).ToUInt32 ());
513                                         break;
514                                 case OperandType.InlineTok:
515                                 case OperandType.InlineType:
516                                 case OperandType.InlineMethod:
517                                 case OperandType.InlineField:
518                                         var provider = reader.LookupToken (new MetadataToken (buffer.ReadUInt32 ()));
519                                         buffer.position -= 4;
520                                         buffer.WriteUInt32 (metadata.LookupToken (provider).ToUInt32 ());
521                                         break;
522                                 }
523                         }
524                 }
525
526                 void PatchRawSection (ByteBuffer buffer, MetadataBuilder metadata)
527                 {
528                         var position = base.position;
529                         Align (4);
530                         buffer.WriteBytes (base.position - position);
531
532                         const byte fat_format = 0x40;
533                         const byte more_sects = 0x80;
534
535                         var flags = ReadByte ();
536                         if ((flags & fat_format) == 0) {
537                                 buffer.WriteByte (flags);
538                                 PatchRawSmallSection (buffer, metadata);
539                         } else
540                                 PatchRawFatSection (buffer, metadata);
541
542                         if ((flags & more_sects) != 0)
543                                 PatchRawSection (buffer, metadata);
544                 }
545
546                 void PatchRawSmallSection (ByteBuffer buffer, MetadataBuilder metadata)
547                 {
548                         var length = ReadByte ();
549                         buffer.WriteByte (length);
550                         Advance (2);
551
552                         buffer.WriteUInt16 (0);
553
554                         var count = length / 12;
555
556                         PatchRawExceptionHandlers (buffer, metadata, count, false);
557                 }
558
559                 void PatchRawFatSection (ByteBuffer buffer, MetadataBuilder metadata)
560                 {
561                         position--;
562                         var length = ReadInt32 ();
563                         buffer.WriteInt32 (length);
564
565                         var count = (length >> 8) / 24;
566
567                         PatchRawExceptionHandlers (buffer, metadata, count, true);
568                 }
569
570                 void PatchRawExceptionHandlers (ByteBuffer buffer, MetadataBuilder metadata, int count, bool fat_entry)
571                 {
572                         const int fat_entry_size = 16;
573                         const int small_entry_size = 6;
574
575                         for (int i = 0; i < count; i++) {
576                                 ExceptionHandlerType handler_type;
577                                 if (fat_entry) {
578                                         var type = ReadUInt32 ();
579                                         handler_type = (ExceptionHandlerType) (type & 0x7);
580                                         buffer.WriteUInt32 (type);
581                                 } else {
582                                         var type = ReadUInt16 ();
583                                         handler_type = (ExceptionHandlerType) (type & 0x7);
584                                         buffer.WriteUInt16 (type);
585                                 }
586
587                                 buffer.WriteBytes (ReadBytes (fat_entry ? fat_entry_size : small_entry_size));
588
589                                 switch (handler_type) {
590                                 case ExceptionHandlerType.Catch:
591                                         var exception = reader.LookupToken (ReadToken ());
592                                         buffer.WriteUInt32 (metadata.LookupToken (exception).ToUInt32 ());
593                                         break;
594                                 default:
595                                         buffer.WriteUInt32 (ReadUInt32 ());
596                                         break;
597                                 }
598                         }
599                 }
600
601 #endif
602
603         }
604 }