5 // Jb Evain (jbevain@gmail.com)
7 // (C) 2005 - 2007 Jb Evain
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
29 namespace Mono
.Cecil
.Cil
{
32 using System
.Collections
;
35 using Mono
.Cecil
.Binary
;
36 using Mono
.Cecil
.Metadata
;
37 using Mono
.Cecil
.Signatures
;
39 sealed class CodeWriter
: BaseCodeVisitor
{
41 ReflectionWriter m_reflectWriter
;
42 MemoryBinaryWriter m_binaryWriter
;
43 MemoryBinaryWriter m_codeWriter
;
45 IDictionary m_localSigCache
;
46 IDictionary m_standaloneSigCache
;
48 IDictionary m_stackSizes
;
52 public bool Stripped
{
53 get { return stripped; }
54 set { stripped = value; }
57 public CodeWriter (ReflectionWriter reflectWriter
, MemoryBinaryWriter writer
)
59 m_reflectWriter
= reflectWriter
;
60 m_binaryWriter
= writer
;
61 m_codeWriter
= new MemoryBinaryWriter ();
63 m_localSigCache
= new Hashtable ();
64 m_standaloneSigCache
= new Hashtable ();
66 m_stackSizes
= new Hashtable ();
69 public RVA
WriteMethodBody (MethodDefinition meth
)
71 if (meth
.Body
== null)
74 RVA ret
= m_reflectWriter
.MetadataWriter
.GetDataCursor ();
75 meth
.Body
.Accept (this);
79 public override void VisitMethodBody (MethodBody body
)
81 m_codeWriter
.Empty ();
84 void WriteToken (MetadataToken token
)
87 m_codeWriter
.Write (0);
89 m_codeWriter
.Write (token
.ToUInt ());
92 static int GetParameterIndex (MethodBody body
, ParameterDefinition p
)
94 int idx
= body
.Method
.Parameters
.IndexOf (p
);
95 if (idx
== -1 && p
== body
.Method
.This
)
97 if (body
.Method
.HasThis
)
103 public override void VisitInstructionCollection (InstructionCollection instructions
)
105 MethodBody body
= instructions
.Container
;
106 long start
= m_codeWriter
.BaseStream
.Position
;
108 ComputeMaxStack (instructions
);
110 foreach (Instruction instr
in instructions
) {
112 instr
.Offset
= (int) (m_codeWriter
.BaseStream
.Position
- start
);
114 if (instr
.OpCode
.Size
== 1)
115 m_codeWriter
.Write (instr
.OpCode
.Op2
);
117 m_codeWriter
.Write (instr
.OpCode
.Op1
);
118 m_codeWriter
.Write (instr
.OpCode
.Op2
);
121 if (instr
.OpCode
.OperandType
!= OperandType
.InlineNone
&&
122 instr
.Operand
== null)
123 throw new ReflectionException ("OpCode {0} have null operand", instr
.OpCode
.Name
);
125 switch (instr
.OpCode
.OperandType
) {
126 case OperandType
.InlineNone
:
128 case OperandType
.InlineSwitch
:
129 Instruction
[] targets
= (Instruction
[]) instr
.Operand
;
130 for (int i
= 0; i
< targets
.Length
+ 1; i
++)
131 m_codeWriter
.Write ((uint) 0);
133 case OperandType
.ShortInlineBrTarget
:
134 m_codeWriter
.Write ((byte) 0);
136 case OperandType
.InlineBrTarget
:
137 m_codeWriter
.Write (0);
139 case OperandType
.ShortInlineI
:
140 if (instr
.OpCode
== OpCodes
.Ldc_I4_S
)
141 m_codeWriter
.Write ((sbyte) instr
.Operand
);
143 m_codeWriter
.Write ((byte) instr
.Operand
);
145 case OperandType
.ShortInlineVar
:
146 m_codeWriter
.Write ((byte) body
.Variables
.IndexOf (
147 (VariableDefinition
) instr
.Operand
));
149 case OperandType
.ShortInlineParam
:
150 m_codeWriter
.Write ((byte) GetParameterIndex (body
, (ParameterDefinition
) instr
.Operand
));
152 case OperandType
.InlineSig
:
153 WriteToken (GetCallSiteToken ((CallSite
) instr
.Operand
));
155 case OperandType
.InlineI
:
156 m_codeWriter
.Write ((int) instr
.Operand
);
158 case OperandType
.InlineVar
:
159 m_codeWriter
.Write ((short) body
.Variables
.IndexOf (
160 (VariableDefinition
) instr
.Operand
));
162 case OperandType
.InlineParam
:
163 m_codeWriter
.Write ((short) GetParameterIndex (
164 body
, (ParameterDefinition
) instr
.Operand
));
166 case OperandType
.InlineI8
:
167 m_codeWriter
.Write ((long) instr
.Operand
);
169 case OperandType
.ShortInlineR
:
170 m_codeWriter
.Write ((float) instr
.Operand
);
172 case OperandType
.InlineR
:
173 m_codeWriter
.Write ((double) instr
.Operand
);
175 case OperandType
.InlineString
:
176 WriteToken (new MetadataToken (TokenType
.String
,
177 m_reflectWriter
.MetadataWriter
.AddUserString (instr
.Operand
as string)));
179 case OperandType
.InlineField
:
180 case OperandType
.InlineMethod
:
181 case OperandType
.InlineType
:
182 case OperandType
.InlineTok
:
183 if (instr
.Operand
is TypeReference
)
184 WriteToken (GetTypeToken ((TypeReference
) instr
.Operand
));
185 else if (instr
.Operand
is GenericInstanceMethod
)
186 WriteToken (m_reflectWriter
.GetMethodSpecToken (instr
.Operand
as GenericInstanceMethod
));
187 else if (instr
.Operand
is MemberReference
)
188 WriteToken (m_reflectWriter
.GetMemberRefToken ((MemberReference
) instr
.Operand
));
189 else if (instr
.Operand
is IMetadataTokenProvider
)
190 WriteToken (((IMetadataTokenProvider
) instr
.Operand
).MetadataToken
);
192 throw new ReflectionException (
193 string.Format ("Wrong operand for {0} OpCode: {1}",
194 instr
.OpCode
.OperandType
,
195 instr
.Operand
.GetType ().FullName
));
201 long pos
= m_codeWriter
.BaseStream
.Position
;
203 foreach (Instruction instr
in instructions
) {
204 switch (instr
.OpCode
.OperandType
) {
205 case OperandType
.InlineSwitch
:
206 m_codeWriter
.BaseStream
.Position
= instr
.Offset
+ instr
.OpCode
.Size
;
207 Instruction
[] targets
= (Instruction
[]) instr
.Operand
;
208 m_codeWriter
.Write ((uint) targets
.Length
);
209 foreach (Instruction tgt
in targets
)
210 m_codeWriter
.Write ((tgt
.Offset
- (instr
.Offset
+
211 instr
.OpCode
.Size
+ (4 * (targets
.Length
+ 1)))));
213 case OperandType
.ShortInlineBrTarget
:
214 m_codeWriter
.BaseStream
.Position
= instr
.Offset
+ instr
.OpCode
.Size
;
215 m_codeWriter
.Write ((byte) (((Instruction
) instr
.Operand
).Offset
-
216 (instr
.Offset
+ instr
.OpCode
.Size
+ 1)));
218 case OperandType
.InlineBrTarget
:
219 m_codeWriter
.BaseStream
.Position
= instr
.Offset
+ instr
.OpCode
.Size
;
220 m_codeWriter
.Write(((Instruction
) instr
.Operand
).Offset
-
221 (instr
.Offset
+ instr
.OpCode
.Size
+ 4));
226 m_codeWriter
.BaseStream
.Position
= pos
;
229 MetadataToken
GetTypeToken (TypeReference type
)
231 return m_reflectWriter
.GetTypeDefOrRefToken (type
);
234 MetadataToken
GetCallSiteToken (CallSite cs
)
237 int sentinel
= cs
.GetSentinel ();
239 sig
= m_reflectWriter
.SignatureWriter
.AddMethodDefSig (
240 m_reflectWriter
.GetMethodDefSig (cs
));
242 sig
= m_reflectWriter
.SignatureWriter
.AddMethodRefSig (
243 m_reflectWriter
.GetMethodRefSig (cs
));
245 if (m_standaloneSigCache
.Contains (sig
))
246 return (MetadataToken
) m_standaloneSigCache
[sig
];
248 StandAloneSigTable sasTable
= m_reflectWriter
.MetadataTableWriter
.GetStandAloneSigTable ();
249 StandAloneSigRow sasRow
= m_reflectWriter
.MetadataRowWriter
.CreateStandAloneSigRow (sig
);
251 sasTable
.Rows
.Add(sasRow
);
253 MetadataToken token
= new MetadataToken (TokenType
.Signature
, (uint) sasTable
.Rows
.Count
);
254 m_standaloneSigCache
[sig
] = token
;
258 static int GetLength (Instruction start
, Instruction end
, InstructionCollection instructions
)
260 Instruction last
= instructions
[instructions
.Count
- 1];
261 return (end
== instructions
.Outside
? last
.Offset
+ last
.GetSize () : end
.Offset
) - start
.Offset
;
264 static bool IsRangeFat (Instruction start
, Instruction end
, InstructionCollection instructions
)
266 return GetLength (start
, end
, instructions
) >= 256 ||
267 start
.Offset
>= 65536;
270 static bool IsFat (ExceptionHandlerCollection seh
)
272 for (int i
= 0; i
< seh
.Count
; i
++) {
273 ExceptionHandler eh
= seh
[i
];
274 if (IsRangeFat (eh
.TryStart
, eh
.TryEnd
, seh
.Container
.Instructions
))
277 if (IsRangeFat (eh
.HandlerStart
, eh
.HandlerEnd
, seh
.Container
.Instructions
))
281 case ExceptionHandlerType
.Filter
:
282 if (IsRangeFat (eh
.FilterStart
, eh
.FilterEnd
, seh
.Container
.Instructions
))
291 void WriteExceptionHandlerCollection (ExceptionHandlerCollection seh
)
293 m_codeWriter
.QuadAlign ();
295 if (seh
.Count
< 0x15 && !IsFat (seh
)) {
296 m_codeWriter
.Write ((byte) MethodDataSection
.EHTable
);
297 m_codeWriter
.Write ((byte) (seh
.Count
* 12 + 4));
298 m_codeWriter
.Write (new byte [2]);
299 foreach (ExceptionHandler eh
in seh
) {
300 m_codeWriter
.Write ((ushort) eh
.Type
);
301 m_codeWriter
.Write ((ushort) eh
.TryStart
.Offset
);
302 m_codeWriter
.Write ((byte) (eh
.TryEnd
.Offset
- eh
.TryStart
.Offset
));
303 m_codeWriter
.Write ((ushort) eh
.HandlerStart
.Offset
);
304 m_codeWriter
.Write ((byte) GetLength (eh
.HandlerStart
, eh
.HandlerEnd
, seh
.Container
.Instructions
));
305 WriteHandlerSpecific (eh
);
308 m_codeWriter
.Write ((byte) (MethodDataSection
.FatFormat
| MethodDataSection
.EHTable
));
309 WriteFatBlockSize (seh
);
310 foreach (ExceptionHandler eh
in seh
) {
311 m_codeWriter
.Write ((uint) eh
.Type
);
312 m_codeWriter
.Write ((uint) eh
.TryStart
.Offset
);
313 m_codeWriter
.Write ((uint) (eh
.TryEnd
.Offset
- eh
.TryStart
.Offset
));
314 m_codeWriter
.Write ((uint) eh
.HandlerStart
.Offset
);
315 m_codeWriter
.Write ((uint) GetLength (eh
.HandlerStart
, eh
.HandlerEnd
, seh
.Container
.Instructions
));
316 WriteHandlerSpecific (eh
);
321 void WriteFatBlockSize (ExceptionHandlerCollection seh
)
323 int size
= seh
.Count
* 24 + 4;
324 m_codeWriter
.Write ((byte) (size
& 0xff));
325 m_codeWriter
.Write ((byte) ((size
>> 8) & 0xff));
326 m_codeWriter
.Write ((byte) ((size
>> 16) & 0xff));
329 void WriteHandlerSpecific (ExceptionHandler eh
)
332 case ExceptionHandlerType
.Catch
:
333 WriteToken (GetTypeToken (eh
.CatchType
));
335 case ExceptionHandlerType
.Filter
:
336 m_codeWriter
.Write ((uint) eh
.FilterStart
.Offset
);
339 m_codeWriter
.Write (0);
344 public override void VisitVariableDefinitionCollection (VariableDefinitionCollection variables
)
346 MethodBody body
= variables
.Container
as MethodBody
;
347 if (body
== null || stripped
)
350 uint sig
= m_reflectWriter
.SignatureWriter
.AddLocalVarSig (
351 GetLocalVarSig (variables
));
353 if (m_localSigCache
.Contains (sig
)) {
354 body
.LocalVarToken
= (int) m_localSigCache
[sig
];
358 StandAloneSigTable sasTable
= m_reflectWriter
.MetadataTableWriter
.GetStandAloneSigTable ();
359 StandAloneSigRow sasRow
= m_reflectWriter
.MetadataRowWriter
.CreateStandAloneSigRow (
362 sasTable
.Rows
.Add (sasRow
);
363 body
.LocalVarToken
= sasTable
.Rows
.Count
;
364 m_localSigCache
[sig
] = body
.LocalVarToken
;
367 public override void TerminateMethodBody (MethodBody body
)
369 long pos
= m_binaryWriter
.BaseStream
.Position
;
371 if (body
.HasVariables
|| body
.HasExceptionHandlers
372 || m_codeWriter
.BaseStream
.Length
>= 64 || body
.MaxStack
> 8) {
374 MethodHeader header
= MethodHeader
.FatFormat
;
376 header
|= MethodHeader
.InitLocals
;
377 if (body
.HasExceptionHandlers
)
378 header
|= MethodHeader
.MoreSects
;
380 m_binaryWriter
.Write ((byte) header
);
381 m_binaryWriter
.Write ((byte) 0x30); // (header size / 4) << 4
382 m_binaryWriter
.Write ((short) body
.MaxStack
);
383 m_binaryWriter
.Write ((int) m_codeWriter
.BaseStream
.Length
);
384 // the token should be zero if there are no variables
385 int token
= body
.HasVariables
? ((int) TokenType
.Signature
| body
.LocalVarToken
) : 0;
386 m_binaryWriter
.Write (token
);
388 if (body
.HasExceptionHandlers
)
389 WriteExceptionHandlerCollection (body
.ExceptionHandlers
);
391 m_binaryWriter
.Write ((byte) ((byte) MethodHeader
.TinyFormat
|
392 m_codeWriter
.BaseStream
.Length
<< 2));
394 m_binaryWriter
.Write (m_codeWriter
);
395 m_binaryWriter
.QuadAlign ();
397 m_reflectWriter
.MetadataWriter
.AddData (
398 (int) (m_binaryWriter
.BaseStream
.Position
- pos
));
401 public LocalVarSig
.LocalVariable
GetLocalVariableSig (VariableDefinition
var)
403 LocalVarSig
.LocalVariable lv
= new LocalVarSig
.LocalVariable ();
404 TypeReference type
= var.VariableType
;
406 lv
.CustomMods
= m_reflectWriter
.GetCustomMods (type
);
408 if (type
is PinnedType
) {
409 lv
.Constraint
|= Constraint
.Pinned
;
410 type
= (type
as PinnedType
).ElementType
;
413 if (type
is ReferenceType
) {
415 type
= (type
as ReferenceType
).ElementType
;
418 lv
.Type
= m_reflectWriter
.GetSigType (type
);
423 public LocalVarSig
GetLocalVarSig (VariableDefinitionCollection vars
)
425 LocalVarSig lvs
= new LocalVarSig ();
426 lvs
.CallingConvention
|= 0x7;
427 lvs
.Count
= vars
.Count
;
428 lvs
.LocalVariables
= new LocalVarSig
.LocalVariable
[lvs
.Count
];
429 for (int i
= 0; i
< lvs
.Count
; i
++) {
430 lvs
.LocalVariables
[i
] = GetLocalVariableSig (vars
[i
]);
436 void ComputeMaxStack (InstructionCollection instructions
)
440 m_stackSizes
.Clear ();
442 foreach (ExceptionHandler eh
in instructions
.Container
.ExceptionHandlers
) {
444 case ExceptionHandlerType
.Catch
:
445 case ExceptionHandlerType
.Filter
:
446 m_stackSizes
[eh
.HandlerStart
] = 1;
452 foreach (Instruction instr
in instructions
) {
454 object savedSize
= m_stackSizes
[instr
];
455 if (savedSize
!= null)
456 current
= (int) savedSize
;
458 current
-= GetPopDelta (instructions
.Container
.Method
, instr
, current
);
463 current
+= GetPushDelta (instr
);
468 // for forward branches, copy the stack size for the instruction that is being branched to
469 switch (instr
.OpCode
.OperandType
) {
470 case OperandType
.InlineBrTarget
:
471 case OperandType
.ShortInlineBrTarget
:
472 m_stackSizes
[instr
.Operand
] = current
;
474 case OperandType
.InlineSwitch
:
475 foreach (Instruction target
in (Instruction
[]) instr
.Operand
)
476 m_stackSizes
[target
] = current
;
480 switch (instr
.OpCode
.FlowControl
) {
481 case FlowControl
.Branch
:
482 case FlowControl
.Throw
:
483 case FlowControl
.Return
:
484 // next statement is not reachable from this statement, so reset the stack depth to 0
490 instructions
.Container
.MaxStack
= max
+ 1; // you never know
493 static int GetPushDelta (Instruction instruction
)
495 OpCode code
= instruction
.OpCode
;
496 switch (code
.StackBehaviourPush
) {
497 case StackBehaviour
.Push0
:
500 case StackBehaviour
.Push1
:
501 case StackBehaviour
.Pushi
:
502 case StackBehaviour
.Pushi8
:
503 case StackBehaviour
.Pushr4
:
504 case StackBehaviour
.Pushr8
:
505 case StackBehaviour
.Pushref
:
508 case StackBehaviour
.Push1_push1
:
511 case StackBehaviour
.Varpush
:
512 if (code
.FlowControl
!= FlowControl
.Call
)
515 IMethodSignature method
= (IMethodSignature
) instruction
.Operand
;
516 return IsVoid (method
.ReturnType
.ReturnType
) ? 0 : 1;
519 throw new NotSupportedException ();
522 static int GetPopDelta (MethodDefinition current
, Instruction instruction
, int height
)
524 OpCode code
= instruction
.OpCode
;
525 switch (code
.StackBehaviourPop
) {
526 case StackBehaviour
.Pop0
:
528 case StackBehaviour
.Popi
:
529 case StackBehaviour
.Popref
:
530 case StackBehaviour
.Pop1
:
533 case StackBehaviour
.Pop1_pop1
:
534 case StackBehaviour
.Popi_pop1
:
535 case StackBehaviour
.Popi_popi
:
536 case StackBehaviour
.Popi_popi8
:
537 case StackBehaviour
.Popi_popr4
:
538 case StackBehaviour
.Popi_popr8
:
539 case StackBehaviour
.Popref_pop1
:
540 case StackBehaviour
.Popref_popi
:
543 case StackBehaviour
.Popi_popi_popi
:
544 case StackBehaviour
.Popref_popi_popi
:
545 case StackBehaviour
.Popref_popi_popi8
:
546 case StackBehaviour
.Popref_popi_popr4
:
547 case StackBehaviour
.Popref_popi_popr8
:
548 case StackBehaviour
.Popref_popi_popref
:
551 case StackBehaviour
.PopAll
:
554 case StackBehaviour
.Varpop
:
555 if (code
== OpCodes
.Ret
)
556 return IsVoid (current
.ReturnType
.ReturnType
) ? 0 : 1;
558 if (code
.FlowControl
!= FlowControl
.Call
)
561 IMethodSignature method
= (IMethodSignature
) instruction
.Operand
;
562 int count
= method
.HasParameters
? method
.Parameters
.Count
: 0;
563 if (method
.HasThis
&& code
!= OpCodes
.Newobj
)
569 throw new NotSupportedException ();
572 static bool IsVoid (TypeReference type
)
574 return type
.FullName
== Constants
.Void
;