Merge pull request #3349 from lambdageek/handle-icalls-in-jit
[mono.git] / mcs / class / Mono.CodeContracts / Mono.CodeContracts.Rewrite / ContractsRuntime.cs
1 //
2 // ContractRuntime.cs
3 //
4 // Authors:
5 //      Chris Bacon (chrisbacon76@gmail.com)
6 //
7 // Copyright (C) 2010 Chris Bacon
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 using System.Collections.Generic;
31 using System.Linq;
32 using System.Text;
33 using Mono.Cecil;
34 using System.Diagnostics.Contracts;
35 using Mono.Cecil.Cil;
36 using System.Diagnostics;
37 using System.Runtime.ConstrainedExecution;
38 using System.Runtime.CompilerServices;
39
40 namespace Mono.CodeContracts.Rewrite {
41         class ContractsRuntime {
42
43                 private const string Namespace = "System.Diagnostics.Contracts";
44
45                 public ContractsRuntime (ModuleDefinition module, RewriterOptions options)
46                 {
47                         this.module = module;
48                         this.options = options;
49                 }
50
51                 private ModuleDefinition module;
52                 private RewriterOptions options;
53
54                 private TypeDefinition typeContractsRuntime = null;
55                 private TypeDefinition typeContractException = null;
56                 private MethodDefinition methodContractExceptionCons = null;
57                 private MethodDefinition methodTriggerFailure = null;
58                 private MethodDefinition methodReportFailure = null;
59                 private MethodDefinition methodRequires = null;
60
61                 private void EnsureTypeContractRuntime ()
62                 {
63                         if (this.typeContractsRuntime == null) {
64                                 // namespace System.Diagnostics.Contracts {
65                                 //     [CompilerGenerated]
66                                 //     private static class __ContractsRuntime {
67                                 //     }
68                                 // }
69                                 
70                                 // Create type
71                                 TypeReference typeObject = this.module.Import (typeof (object));
72                                 TypeDefinition type = new TypeDefinition ("__ContractsRuntime", Namespace,
73                                         TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.NotPublic | TypeAttributes.AnsiClass | TypeAttributes.AutoClass, // | TypeAttributes.BeforeFieldInit,
74                                         typeObject);
75                                 this.module.Types.Add (type);
76                                 // Attach custom attributes
77                                 var attrCompilerGeneratedCons = typeof (CompilerGeneratedAttribute).GetConstructor (Type.EmptyTypes);
78                                 CustomAttribute attrCompilerGenerated = new CustomAttribute (this.module.Import (attrCompilerGeneratedCons));
79                                 type.CustomAttributes.Add (attrCompilerGenerated);
80                                 // Store type
81                                 this.typeContractsRuntime = type;
82                         }
83                 }
84
85                 private void EnsureTypeContractException ()
86                 {
87                         if (this.options.ThrowOnFailure && this.typeContractException == null) {
88                                 // [CompilerGenerated]
89                                 // private class ContractException : Exception {
90                                 //     internal ContractException(ContractFailureKind kind, string usermsg, string condition, Exception inner)
91                                 //         : base(failure, inner)
92                                 //     {
93                                 //     }
94                                 // }
95                                 
96                                 // Prepare type references
97                                 TypeReference typeVoid = this.module.Import (typeof (void));
98                                 TypeReference typeContractFailureKind = this.module.Import (typeof (ContractFailureKind));
99                                 TypeReference typeString = this.module.Import (typeof (string));
100                                 TypeReference typeException = this.module.Import (typeof (Exception));
101                                 // Create type
102                                 TypeDefinition type = new TypeDefinition ("ContractException", Namespace,
103                                         TypeAttributes.NestedPrivate | TypeAttributes.AnsiClass | TypeAttributes.AutoClass, typeException);
104                                 //this.typeContractsRuntime.NestedTypes.Add (type);
105                                 this.module.Types.Add(type);
106                                 // Create constructor
107                                 MethodDefinition cons = new MethodDefinition (".ctor",
108                                         MethodAttributes.Assembly | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, typeVoid);
109                                 cons.Parameters.Add (new ParameterDefinition ("kind", ParameterAttributes.None, typeContractFailureKind));
110                                 cons.Parameters.Add (new ParameterDefinition ("failure", ParameterAttributes.None, typeString));
111                                 cons.Parameters.Add (new ParameterDefinition ("usermsg", ParameterAttributes.None, typeString));
112                                 cons.Parameters.Add (new ParameterDefinition ("condition", ParameterAttributes.None, typeString));
113                                 cons.Parameters.Add (new ParameterDefinition ("inner", ParameterAttributes.None, typeException));
114                                 var il = cons.Body.GetILProcessor ();
115                                 il.Emit (OpCodes.Ldarg_0);
116                                 il.Emit (OpCodes.Ldarg_2);
117                                 il.Emit (OpCodes.Ldarg_S, cons.Parameters [4]);
118                                 MethodReference mExceptionCons = this.module.Import (typeof (Exception).GetConstructor (new [] { typeof (string), typeof (Exception) }));
119                                 il.Emit (OpCodes.Call, mExceptionCons);
120                                 il.Emit (OpCodes.Ret);
121                                 type.Methods.Add (cons);
122                                 // Attach custom attributes
123                                 var attrCompilerGeneratedCons = typeof (CompilerGeneratedAttribute).GetConstructor (Type.EmptyTypes);
124                                 CustomAttribute attrCompilerGenerated = new CustomAttribute (this.module.Import (attrCompilerGeneratedCons));
125                                 type.CustomAttributes.Add (attrCompilerGenerated);
126                                 // Store constructor and type
127                                 this.methodContractExceptionCons = cons;
128                                 this.typeContractException = type;
129                         }
130                 }
131
132                 private void EnsureMethodTriggerFailure ()
133                 {
134                         if (this.methodTriggerFailure == null) {
135                                 // if the ThrowOnFailure option is true, then:
136                                 // internal static void TriggerFailure(ContractFailureKind kind, string message, string userMessage, string conditionText, Exception inner)
137                                 // {
138                                 //     throw new ContractException(kind, message, userMessage, conditionText, inner);
139                                 // }
140                                 
141                                 // if the ThrowOnFailure option is false, then:
142                                 // internal static void TriggerFailure(ContractFailureKind kind, string message, string userMessage, string conditionText, Exception inner)
143                                 // {
144                                 //     Debug.Fail(message, userMessage);
145                                 // }
146                                 
147                                 // Prepare type references
148                                 TypeReference typeVoid = this.module.Import (typeof (void));
149                                 TypeReference typeContractFailureKind = this.module.Import (typeof (ContractFailureKind));
150                                 TypeReference typeString = this.module.Import (typeof (string));
151                                 TypeReference typeException = this.module.Import (typeof (Exception));
152                                 // Create method
153                                 MethodDefinition method = new MethodDefinition ("TriggerFailure",
154                                         MethodAttributes.Assembly | MethodAttributes.Static, typeVoid);
155                                 method.Parameters.Add (new ParameterDefinition ("kind", ParameterAttributes.None, typeContractFailureKind));
156                                 method.Parameters.Add (new ParameterDefinition ("message", ParameterAttributes.None, typeString));
157                                 method.Parameters.Add (new ParameterDefinition ("userMessage", ParameterAttributes.None, typeString));
158                                 method.Parameters.Add (new ParameterDefinition ("conditionText", ParameterAttributes.None, typeString));
159                                 method.Parameters.Add (new ParameterDefinition ("inner", ParameterAttributes.None, typeException));
160                                 var il = method.Body.GetILProcessor ();
161                                 if (this.options.ThrowOnFailure) {
162                                         il.Emit (OpCodes.Ldarg_0);
163                                         il.Emit (OpCodes.Ldarg_1);
164                                         il.Emit (OpCodes.Ldarg_2);
165                                         il.Emit (OpCodes.Ldarg_3);
166                                         il.Emit (OpCodes.Ldarg_S, method.Parameters [4]);
167                                         il.Emit (OpCodes.Newobj, this.methodContractExceptionCons);
168                                         il.Emit (OpCodes.Throw);
169                                 } else {
170                                         var mDebugFail = typeof (Debug).GetMethod ("Fail", new [] { typeof (string), typeof(string) });
171                                         MethodReference methodDebugFail = this.module.Import (mDebugFail);
172                                         il.Emit (OpCodes.Ldarg_1);
173                                         il.Emit (OpCodes.Ldarg_2);
174                                         il.Emit (OpCodes.Call, methodDebugFail);
175                                         il.Emit (OpCodes.Ret);
176                                 }
177                                 this.typeContractsRuntime.Methods.Add (method);
178                                 this.methodTriggerFailure = method;
179                         }
180                 }
181
182                 private void EnsureMethodReportFailure ()
183                 {
184                         if (this.methodReportFailure == null) {
185                                 // internal static void ReportFailure(ContractFailureKind kind, string message, string conditionText, Exception inner)
186                                 // {
187                                 //     string s = ContractHelper.RaiseContractFailedEvent(kind, message, conditionText, inner);
188                                 //     if (s != null) {
189                                 //         TriggerFailure(kind, s, message, conditionText, inner);
190                                 //     }
191                                 // }
192                                 
193                                 // Prepare type references
194                                 TypeReference typeVoid = this.module.Import (typeof (void));
195                                 TypeReference typeContractFailureKind = this.module.Import (typeof (ContractFailureKind));
196                                 TypeReference typeString = this.module.Import (typeof (string));
197                                 TypeReference typeException = this.module.Import (typeof (Exception));
198                                 var helper = typeof (ContractHelper);
199                                 MethodReference mRaiseContractFailedEvent = this.module.Import (helper.GetMethod ("RaiseContractFailedEvent"));
200                                 // Create method
201                                 MethodDefinition method = new MethodDefinition ("ReportFailure",
202                                         MethodAttributes.Assembly | MethodAttributes.Static, typeVoid);
203                                 method.Parameters.Add (new ParameterDefinition ("kind", ParameterAttributes.None, typeContractFailureKind));
204                                 method.Parameters.Add (new ParameterDefinition ("message", ParameterAttributes.None, typeString));
205                                 method.Parameters.Add (new ParameterDefinition ("conditionText", ParameterAttributes.None, typeString));
206                                 method.Parameters.Add (new ParameterDefinition ("inner", ParameterAttributes.None, typeException));
207                                 VariableDefinition vMsg = new VariableDefinition (typeString);
208                                 method.Body.Variables.Add (vMsg);
209                                 method.Body.InitLocals = true;
210                                 var il = method.Body.GetILProcessor ();
211                                 il.Emit (OpCodes.Ldarg_0);
212                                 il.Emit (OpCodes.Ldarg_1);
213                                 il.Emit (OpCodes.Ldarg_2);
214                                 il.Emit (OpCodes.Ldarg_3);
215                                 il.Emit (OpCodes.Call, mRaiseContractFailedEvent);
216                                 il.Emit (OpCodes.Stloc_0);
217                                 il.Emit (OpCodes.Ldloc_0);
218                                 var instRet = il.Create (OpCodes.Ret);
219                                 il.Emit (OpCodes.Brfalse_S, instRet);
220                                 il.Emit (OpCodes.Ldarg_0);
221                                 il.Emit (OpCodes.Ldloc_0);
222                                 il.Emit (OpCodes.Ldarg_1);
223                                 il.Emit (OpCodes.Ldarg_2);
224                                 il.Emit (OpCodes.Ldarg_3);
225                                 il.Emit (OpCodes.Call, this.methodTriggerFailure);
226                                 il.Append (instRet);
227                                 this.typeContractsRuntime.Methods.Add (method);
228                                 this.methodReportFailure = method;
229                         }
230                 }
231
232                 private void EnsureGlobal ()
233                 {
234                         this.EnsureTypeContractRuntime ();
235                         this.EnsureTypeContractException ();
236                         this.EnsureMethodTriggerFailure ();
237                         this.EnsureMethodReportFailure ();
238                 }
239
240                 public MethodDefinition GetRequires ()
241                 {
242                         this.EnsureGlobal ();
243                         if (this.methodRequires == null) {
244                                 // [DebuggerNonUserCode]
245                                 // [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
246                                 // internal static void Requires(bool condition, string message, string conditionText)
247                                 // {
248                                 //     if (!condition) {
249                                 //         ReportFailure(ContractFailureKind.Precondition, message, conditionText, null);
250                                 //     }
251                                 // }
252                                 
253                                 // Prepare type references
254                                 TypeReference typeVoid = this.module.Import (typeof (void));
255                                 TypeReference typeBoolean = this.module.Import (typeof (bool));
256                                 TypeReference typeString = this.module.Import (typeof (string));
257                                 // Create method
258                                 MethodDefinition method = new MethodDefinition ("Requires",
259                                     MethodAttributes.Assembly | MethodAttributes.Static, typeVoid);
260                                 method.Parameters.Add (new ParameterDefinition ("condition", ParameterAttributes.None, typeBoolean));
261                                 method.Parameters.Add (new ParameterDefinition ("message", ParameterAttributes.None, typeString));
262                                 method.Parameters.Add (new ParameterDefinition ("conditionText", ParameterAttributes.None, typeString));
263                                 var il = method.Body.GetILProcessor ();
264                                 il.Emit (OpCodes.Ldarg_0);
265                                 var instRet = il.Create(OpCodes.Ret);
266                                 il.Emit (OpCodes.Brtrue_S, instRet);
267                                 il.Emit (OpCodes.Ldc_I4_0); // Assumes ContractFailureKind.Precondition == 0
268                                 il.Emit (OpCodes.Ldarg_1);
269                                 il.Emit (OpCodes.Ldarg_2);
270                                 il.Emit (OpCodes.Ldnull);
271                                 il.Emit (OpCodes.Call, this.methodReportFailure);
272                                 il.Append (instRet);
273                                 this.typeContractsRuntime.Methods.Add (method);
274                                 // Attach custom attributes
275                                 var attrDebugNonUserCodeCons = typeof (DebuggerNonUserCodeAttribute).GetConstructor (Type.EmptyTypes);
276                                 CustomAttribute attrDebugNonUserCode = new CustomAttribute (this.module.Import (attrDebugNonUserCodeCons));
277                                 method.CustomAttributes.Add (attrDebugNonUserCode);
278                                 var attrReliabilityContractCons = typeof (ReliabilityContractAttribute).GetConstructor (new [] { typeof (Consistency), typeof (Cer) });
279                                 // Blob for attribute: new ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)
280                                 byte [] blob = new byte [] { 1, 0, 3, 0, 0, 0, 1, 0, 0, 0 };
281                                 CustomAttribute attrReliabilityContract = new CustomAttribute (this.module.Import (attrReliabilityContractCons), blob);
282                                 method.CustomAttributes.Add (attrReliabilityContract);
283                                 // Store method
284                                 this.methodRequires = method;
285                         }
286                         return this.methodRequires;
287                 }
288
289         }
290 }