3 // Copyright (c) Microsoft Corporation. All rights reserved.
6 /*============================================================
10 ** <OWNER>maf,mbarnett,Microsoft</OWNER>
12 ** Implementation details of CLR Contracts.
14 ===========================================================*/
15 #define DEBUG // The behavior of this contract library should be consistent regardless of build type.
18 #define FEATURE_UNTRUSTED_CALLERS
24 #define FEATURE_UNTRUSTED_CALLERS
25 #define FEATURE_RELIABILITY_CONTRACTS
26 #define FEATURE_SERIALIZATION
30 using System.Collections.Generic;
31 using System.Diagnostics;
32 using System.Diagnostics.CodeAnalysis;
33 using System.Diagnostics.Contracts;
34 using System.Reflection;
36 #if FEATURE_RELIABILITY_CONTRACTS
37 using System.Runtime.ConstrainedExecution;
39 #if FEATURE_UNTRUSTED_CALLERS
40 using System.Security;
41 using System.Security.Permissions;
44 namespace System.Diagnostics.Contracts {
46 public static partial class Contract
48 #region Private Methods
51 private static bool _assertingMustUseRewriter;
54 /// This method is used internally to trigger a failure indicating to the "programmer" that he is using the interface incorrectly.
55 /// It is NEVER used to indicate failure of actual contracts at runtime.
57 [SecuritySafeCritical]
58 static partial void AssertMustUseRewriter(ContractFailureKind kind, String contractKind)
60 if (_assertingMustUseRewriter)
61 System.Diagnostics.Assert.Fail("Asserting that we must use the rewriter went reentrant.", "Didn't rewrite this mscorlib?");
62 _assertingMustUseRewriter = true;
64 // For better diagnostics, report which assembly is at fault. Walk up stack and
65 // find the first non-mscorlib assembly.
66 Assembly thisAssembly = typeof(Contract).Assembly; // In case we refactor mscorlib, use Contract class instead of Object.
67 StackTrace stack = new StackTrace();
68 Assembly probablyNotRewritten = null;
69 for (int i = 0; i < stack.FrameCount; i++)
71 Assembly caller = stack.GetFrame(i).GetMethod().DeclaringType.Assembly;
72 if (caller != thisAssembly)
74 probablyNotRewritten = caller;
79 if (probablyNotRewritten == null)
80 probablyNotRewritten = thisAssembly;
81 String simpleName = probablyNotRewritten.GetName().Name;
82 System.Runtime.CompilerServices.ContractHelper.TriggerFailure(kind, Environment.GetResourceString("MustUseCCRewrite", contractKind, simpleName), null, null, null);
84 _assertingMustUseRewriter = false;
87 #endregion Private Methods
89 #region Failure Behavior
92 /// Without contract rewriting, failing Assert/Assumes end up calling this method.
93 /// Code going through the contract rewriter never calls this method. Instead, the rewriter produced failures call
94 /// System.Runtime.CompilerServices.ContractHelper.RaiseContractFailedEvent, followed by
95 /// System.Runtime.CompilerServices.ContractHelper.TriggerFailure.
97 [SuppressMessage("Microsoft.Portability", "CA1903:UseOnlyApiFromTargetedFramework", MessageId = "System.Security.SecuritySafeCriticalAttribute")]
98 [System.Diagnostics.DebuggerNonUserCode]
99 #if FEATURE_RELIABILITY_CONTRACTS
100 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
102 static partial void ReportFailure(ContractFailureKind failureKind, String userMessage, String conditionText, Exception innerException)
104 if (failureKind < ContractFailureKind.Precondition || failureKind > ContractFailureKind.Assume)
105 throw new ArgumentException(Environment.GetResourceString("Arg_EnumIllegalVal", failureKind), "failureKind");
106 Contract.EndContractBlock();
108 // displayMessage == null means: yes we handled it. Otherwise it is the localized failure message
109 var displayMessage = System.Runtime.CompilerServices.ContractHelper.RaiseContractFailedEvent(failureKind, userMessage, conditionText, innerException);
111 if (displayMessage == null) return;
113 System.Runtime.CompilerServices.ContractHelper.TriggerFailure(failureKind, displayMessage, userMessage, conditionText, innerException);
117 /// Allows a managed application environment such as an interactive interpreter (IronPython)
118 /// to be notified of contract failures and
119 /// potentially "handle" them, either by throwing a particular exception type, etc. If any of the
120 /// event handlers sets the Cancel flag in the ContractFailedEventArgs, then the Contract class will
121 /// not pop up an assert dialog box or trigger escalation policy. Hooking this event requires
122 /// full trust, because it will inform you of bugs in the appdomain and because the event handler
123 /// could allow you to continue execution.
125 public static event EventHandler<ContractFailedEventArgs> ContractFailed {
126 #if FEATURE_UNTRUSTED_CALLERS
128 #if FEATURE_LINK_DEMAND
129 [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
133 System.Runtime.CompilerServices.ContractHelper.InternalContractFailed += value;
135 #if FEATURE_UNTRUSTED_CALLERS
137 #if FEATURE_LINK_DEMAND
138 [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
142 System.Runtime.CompilerServices.ContractHelper.InternalContractFailed -= value;
145 #endregion FailureBehavior
148 public sealed class ContractFailedEventArgs : EventArgs
150 private ContractFailureKind _failureKind;
151 private String _message;
152 private String _condition;
153 private Exception _originalException;
154 private bool _handled;
155 private bool _unwind;
157 internal Exception thrownDuringHandler;
159 #if FEATURE_RELIABILITY_CONTRACTS
160 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
162 public ContractFailedEventArgs(ContractFailureKind failureKind, String message, String condition, Exception originalException)
164 Contract.Requires(originalException == null || failureKind == ContractFailureKind.PostconditionOnException);
165 _failureKind = failureKind;
167 _condition = condition;
168 _originalException = originalException;
171 public String Message { get { return _message; } }
172 public String Condition { get { return _condition; } }
173 public ContractFailureKind FailureKind { get { return _failureKind; } }
174 public Exception OriginalException { get { return _originalException; } }
176 // Whether the event handler "handles" this contract failure, or to fail via escalation policy.
177 public bool Handled {
178 get { return _handled; }
181 #if FEATURE_UNTRUSTED_CALLERS
183 #if FEATURE_LINK_DEMAND
184 [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
187 public void SetHandled()
193 get { return _unwind; }
196 #if FEATURE_UNTRUSTED_CALLERS
198 #if FEATURE_LINK_DEMAND
199 [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
202 public void SetUnwind()
208 #if FEATURE_SERIALIZATION
211 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors")]
213 [SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic")]
214 internal sealed class ContractException : Exception
216 readonly ContractFailureKind _Kind;
217 readonly string _UserMessage;
218 readonly string _Condition;
220 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
221 public ContractFailureKind Kind { get { return _Kind; } }
222 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
223 public string Failure { get { return this.Message; } }
224 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
225 public string UserMessage { get { return _UserMessage; } }
226 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
227 public string Condition { get { return _Condition; } }
229 // Called by COM Interop, if we see COR_E_CODECONTRACTFAILED as an HRESULT.
230 private ContractException()
232 HResult = System.Runtime.CompilerServices.ContractHelper.COR_E_CODECONTRACTFAILED;
235 public ContractException(ContractFailureKind kind, string failure, string userMessage, string condition, Exception innerException)
236 : base(failure, innerException)
238 HResult = System.Runtime.CompilerServices.ContractHelper.COR_E_CODECONTRACTFAILED;
240 this._UserMessage = userMessage;
241 this._Condition = condition;
244 #if FEATURE_SERIALIZATION
245 private ContractException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
246 : base(info, context)
248 _Kind = (ContractFailureKind)info.GetInt32("Kind");
249 _UserMessage = info.GetString("UserMessage");
250 _Condition = info.GetString("Condition");
252 #endif // FEATURE_SERIALIZATION
254 #if FEATURE_UNTRUSTED_CALLERS && FEATURE_SERIALIZATION
256 #if FEATURE_LINK_DEMAND && FEATURE_SERIALIZATION
257 [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
258 #endif // FEATURE_LINK_DEMAND
259 #endif // FEATURE_UNTRUSTED_CALLERS
260 public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
262 base.GetObjectData(info, context);
264 info.AddValue("Kind", _Kind);
265 info.AddValue("UserMessage", _UserMessage);
266 info.AddValue("Condition", _Condition);
272 namespace System.Runtime.CompilerServices
274 public static partial class ContractHelper
276 #region Private fields
278 private static volatile EventHandler<ContractFailedEventArgs> contractFailedEvent;
279 private static readonly Object lockObject = new Object();
281 internal const int COR_E_CODECONTRACTFAILED = unchecked((int)0x80131542);
286 /// Allows a managed application environment such as an interactive interpreter (IronPython) or a
287 /// web browser host (Jolt hosting Silverlight in IE) to be notified of contract failures and
288 /// potentially "handle" them, either by throwing a particular exception type, etc. If any of the
289 /// event handlers sets the Cancel flag in the ContractFailedEventArgs, then the Contract class will
290 /// not pop up an assert dialog box or trigger escalation policy. Hooking this event requires
293 internal static event EventHandler<ContractFailedEventArgs> InternalContractFailed
295 #if FEATURE_UNTRUSTED_CALLERS
299 // Eagerly prepare each event handler _marked with a reliability contract_, to
300 // attempt to reduce out of memory exceptions while reporting contract violations.
301 // This only works if the new handler obeys the constraints placed on
302 // constrained execution regions. Eagerly preparing non-reliable event handlers
303 // would be a perf hit and wouldn't significantly improve reliability.
304 // UE: Please mention reliable event handlers should also be marked with the
305 // PrePrepareMethodAttribute to avoid CER eager preparation work when ngen'ed.
306 System.Runtime.CompilerServices.RuntimeHelpers.PrepareContractedDelegate(value);
309 contractFailedEvent += value;
312 #if FEATURE_UNTRUSTED_CALLERS
318 contractFailedEvent -= value;
324 /// Rewriter will call this method on a contract failure to allow listeners to be notified.
325 /// The method should not perform any failure (assert/throw) itself.
326 /// This method has 3 functions:
327 /// 1. Call any contract hooks (such as listeners to Contract failed events)
328 /// 2. Determine if the listeneres deem the failure as handled (then resultFailureMessage should be set to null)
329 /// 3. Produce a localized resultFailureMessage used in advertising the failure subsequently.
331 /// <param name="resultFailureMessage">Should really be out (or the return value), but partial methods are not flexible enough.
332 /// On exit: null if the event was handled and should not trigger a failure.
333 /// Otherwise, returns the localized failure message</param>
334 [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate")]
335 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
336 [System.Diagnostics.DebuggerNonUserCode]
337 #if FEATURE_RELIABILITY_CONTRACTS
338 [SecuritySafeCritical]
340 static partial void RaiseContractFailedEventImplementation(ContractFailureKind failureKind, String userMessage, String conditionText, Exception innerException, ref string resultFailureMessage)
342 if (failureKind < ContractFailureKind.Precondition || failureKind > ContractFailureKind.Assume)
343 throw new ArgumentException(Environment.GetResourceString("Arg_EnumIllegalVal", failureKind), "failureKind");
344 Contract.EndContractBlock();
347 String displayMessage = "contract failed."; // Incomplete, but in case of OOM during resource lookup...
348 ContractFailedEventArgs eventArgs = null; // In case of OOM.
349 #if FEATURE_RELIABILITY_CONTRACTS
350 System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions();
354 displayMessage = GetDisplayMessage(failureKind, userMessage, conditionText);
355 EventHandler<ContractFailedEventArgs> contractFailedEventLocal = contractFailedEvent;
356 if (contractFailedEventLocal != null)
358 eventArgs = new ContractFailedEventArgs(failureKind, displayMessage, conditionText, innerException);
359 foreach (EventHandler<ContractFailedEventArgs> handler in contractFailedEventLocal.GetInvocationList())
363 handler(null, eventArgs);
367 eventArgs.thrownDuringHandler = e;
368 eventArgs.SetUnwind();
371 if (eventArgs.Unwind)
374 if (Environment.IsCLRHosted)
375 TriggerCodeContractEscalationPolicy(failureKind, displayMessage, conditionText, innerException);
378 if (innerException == null) { innerException = eventArgs.thrownDuringHandler; }
379 throw new ContractException(failureKind, displayMessage, userMessage, conditionText, innerException);
385 if (eventArgs != null && eventArgs.Handled)
387 returnValue = null; // handled
391 returnValue = displayMessage;
394 resultFailureMessage = returnValue;
398 /// Rewriter calls this method to get the default failure behavior.
400 [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "conditionText")]
401 [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "userMessage")]
402 [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "kind")]
403 [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "innerException")]
404 [System.Diagnostics.DebuggerNonUserCode]
405 #if FEATURE_UNTRUSTED_CALLERS && !FEATURE_CORECLR
406 [SecuritySafeCritical]
408 static partial void TriggerFailureImplementation(ContractFailureKind kind, String displayMessage, String userMessage, String conditionText, Exception innerException)
410 // If we're here, our intent is to pop up a dialog box (if we can). For developers
411 // interacting live with a debugger, this is a good experience. For Silverlight
412 // hosted in Internet Explorer, the assert window is great. If we cannot
413 // pop up a dialog box, throw an exception (consider a library compiled with
414 // "Assert On Failure" but used in a process that can't pop up asserts, like an
415 // NT Service). For the CLR hosted by server apps like SQL or Exchange, we should
416 // trigger escalation policy.
418 if (Environment.IsCLRHosted)
420 TriggerCodeContractEscalationPolicy(kind, displayMessage, conditionText, innerException);
421 // Hosts like SQL may choose to abort the thread, so we will not get here in all cases.
422 // But if the host's chosen action was to throw an exception, we should throw an exception
423 // here (which is easier to do in managed code with the right parameters).
424 throw new ContractException(kind, displayMessage, userMessage, conditionText, innerException);
426 #endif // !FEATURE_CORECLR
427 if (!Environment.UserInteractive) {
428 throw new ContractException(kind, displayMessage, userMessage, conditionText, innerException);
430 // May need to rethink Assert.Fail w/ TaskDialogIndirect as a model. Window title. Main instruction. Content. Expanded info.
431 // Optional info like string for collapsed text vs. expanded text.
432 String windowTitle = Environment.GetResourceString(GetResourceNameForFailure(kind));
433 const int numStackFramesToSkip = 2; // To make stack traces easier to read
434 System.Diagnostics.Assert.Fail(conditionText, displayMessage, windowTitle, COR_E_CODECONTRACTFAILED, StackTrace.TraceFormat.Normal, numStackFramesToSkip);
435 // If we got here, the user selected Ignore. Continue.
438 private static String GetResourceNameForFailure(ContractFailureKind failureKind, bool withCondition = false)
440 String resourceName = null;
443 case ContractFailureKind.Assert:
444 resourceName = withCondition ? "AssertionFailed_Cnd" : "AssertionFailed";
447 case ContractFailureKind.Assume:
448 resourceName = withCondition ? "AssumptionFailed_Cnd" : "AssumptionFailed";
451 case ContractFailureKind.Precondition:
452 resourceName = withCondition ? "PreconditionFailed_Cnd" : "PreconditionFailed";
455 case ContractFailureKind.Postcondition:
456 resourceName = withCondition ? "PostconditionFailed_Cnd" : "PostconditionFailed";
459 case ContractFailureKind.Invariant:
460 resourceName = withCondition ? "InvariantFailed_Cnd" : "InvariantFailed";
463 case ContractFailureKind.PostconditionOnException:
464 resourceName = withCondition ? "PostconditionOnExceptionFailed_Cnd" : "PostconditionOnExceptionFailed";
468 Contract.Assume(false, "Unreachable code");
469 resourceName = "AssumptionFailed";
475 #if FEATURE_RELIABILITY_CONTRACTS
476 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
478 private static String GetDisplayMessage(ContractFailureKind failureKind, String userMessage, String conditionText)
480 String resourceName = GetResourceNameForFailure(failureKind, !String.IsNullOrEmpty(conditionText));
481 // Well-formatted English messages will take one of four forms. A sentence ending in
482 // either a period or a colon, the condition string, then the message tacked
483 // on to the end with two spaces in front.
484 // Note that both the conditionText and userMessage may be null. Also,
485 // on Silverlight we may not be able to look up a friendly string for the
486 // error message. Let's leverage Silverlight's default error message there.
487 String failureMessage;
488 if (!String.IsNullOrEmpty(conditionText)) {
489 failureMessage = Environment.GetResourceString(resourceName, conditionText);
492 failureMessage = Environment.GetResourceString(resourceName);
495 // Now add in the user message, if present.
496 if (!String.IsNullOrEmpty(userMessage))
498 return failureMessage + " " + userMessage;
502 return failureMessage;
507 // Will trigger escalation policy, if hosted and the host requested us to do something (such as
508 // abort the thread or exit the process). Starting in Dev11, for hosted apps the default behavior
509 // is to throw an exception.
510 // Implementation notes:
511 // We implement our default behavior of throwing an exception by simply returning from our native
512 // method inside the runtime and falling through to throw an exception.
513 // We must call through this method before calling the method on the Environment class
514 // because our security team does not yet support SecuritySafeCritical on P/Invoke methods.
515 // Note this can be called in the context of throwing another exception (EnsuresOnThrow).
516 [SecuritySafeCritical]
517 [DebuggerNonUserCode]
518 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
519 private static void TriggerCodeContractEscalationPolicy(ContractFailureKind failureKind, String message, String conditionText, Exception innerException)
521 String exceptionAsString = null;
522 if (innerException != null)
523 exceptionAsString = innerException.ToString();
524 Environment.TriggerCodeContractFailure(failureKind, message, conditionText, exceptionAsString);
526 #endif // !FEATURE_CORECLR
528 } // namespace System.Runtime.CompilerServices