You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
8.5 KiB
188 lines
8.5 KiB
#if NET20 || NET30 || NET35 || NET40 || !NET_4_6 |
|
|
|
// Licensed to the .NET Foundation under one or more agreements. |
|
// The .NET Foundation licenses this file to you under the MIT license. |
|
// See the LICENSE file in the project root for more information. |
|
|
|
/*============================================================ |
|
** |
|
** |
|
** |
|
** Implementation details of CLR Contracts. |
|
** |
|
===========================================================*/ |
|
|
|
using System.Diagnostics; |
|
using System.Diagnostics.Contracts; |
|
using System.Runtime.ConstrainedExecution; |
|
using System.Security; |
|
|
|
namespace System.Runtime.CompilerServices |
|
{ |
|
public static class ContractHelper |
|
{ |
|
[DebuggerNonUserCode] |
|
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] |
|
public static string RaiseContractFailedEvent(ContractFailureKind failureKind, string userMessage, string conditionText, Exception innerException) |
|
{ |
|
var resultFailureMessage = "Contract failed"; // default in case implementation does not assign anything. |
|
RaiseContractFailedEventImplementation(failureKind, userMessage, conditionText, innerException, ref resultFailureMessage); |
|
return resultFailureMessage; |
|
} |
|
|
|
[DebuggerNonUserCode] |
|
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] |
|
public static void TriggerFailure(ContractFailureKind kind, string displayMessage, string userMessage, string conditionText, Exception innerException) |
|
{ |
|
TriggerFailureImplementation(kind, displayMessage, userMessage, conditionText, innerException); |
|
} |
|
|
|
internal const int Cor_E_Codecontractfailed = unchecked((int)0x80131542); |
|
private static readonly object _lockObject = new object(); |
|
private static volatile EventHandler<ContractFailedEventArgs> _contractFailedEvent; |
|
|
|
/// <summary> |
|
/// Allows a managed application environment such as an interactive interpreter (IronPython) or a |
|
/// web browser host (Jolt hosting Silverlight in IE) to be notified of contract failures and |
|
/// potentially "handle" them, either by throwing a particular exception type, etc. If any of the |
|
/// event handlers sets the Cancel flag in the ContractFailedEventArgs, then the Contract class will |
|
/// not pop up an assert dialog box or trigger escalation policy. Hooking this event requires |
|
/// full trust. |
|
/// </summary> |
|
internal static event EventHandler<ContractFailedEventArgs> InternalContractFailed |
|
{ |
|
[SecurityCritical] |
|
add |
|
{ |
|
// Eagerly prepare each event handler _marked with a reliability contract_, to |
|
// attempt to reduce out of memory exceptions while reporting contract violations. |
|
// This only works if the new handler obeys the constraints placed on |
|
// constrained execution regions. Eagerly preparing non-reliable event handlers |
|
// would be a perf hit and wouldn't significantly improve reliability. |
|
// UE: Please mention reliable event handlers should also be marked with the |
|
// PrePrepareMethodAttribute to avoid CER eager preparation work when ngen'ed. |
|
// System.Runtime.CompilerServices.RuntimeHelpers.PrepareContractedDelegate(value); // TODO? I'm afraid I can't do that. |
|
lock (_lockObject) |
|
{ |
|
_contractFailedEvent += value; |
|
} |
|
} |
|
[SecurityCritical] |
|
remove |
|
{ |
|
lock (_lockObject) |
|
{ |
|
_contractFailedEvent -= value; |
|
} |
|
} |
|
} |
|
|
|
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] |
|
private static string GetDisplayMessage(ContractFailureKind failureKind, string userMessage, |
|
string conditionText) |
|
{ |
|
// Well-formatted English messages will take one of four forms. A sentence ending in |
|
// either a period or a colon, the condition string, then the message tacked |
|
// on to the end with two spaces in front. |
|
// Note that both the conditionText and userMessage may be null. Also, |
|
// on Silverlight we may not be able to look up a friendly string for the |
|
// error message. Let's leverage Silverlight's default error message there. |
|
var failureMessage = ContractHelperEx.GetFailureMessage(failureKind, conditionText); |
|
|
|
// Now add in the user message, if present. |
|
if (!string.IsNullOrEmpty(userMessage)) |
|
{ |
|
return failureMessage + " " + userMessage; |
|
} |
|
return failureMessage; |
|
} |
|
|
|
[DebuggerNonUserCode] |
|
[SecuritySafeCritical] |
|
private static void RaiseContractFailedEventImplementation(ContractFailureKind failureKind, string userMessage, |
|
string conditionText, Exception innerException, ref string resultFailureMessage) |
|
{ |
|
if (failureKind < ContractFailureKind.Precondition || failureKind > ContractFailureKind.Assume) |
|
{ |
|
throw new ArgumentException(string.Format("Invalid enum value: {0}", failureKind), "failureKind"); |
|
} |
|
|
|
Contract.EndContractBlock(); |
|
|
|
string returnValue; |
|
var displayMessage = "contract failed."; // Incomplete, but in case of OOM during resource lookup... |
|
ContractFailedEventArgs eventArgs = null; // In case of OOM. |
|
RuntimeHelpers.PrepareConstrainedRegions(); |
|
try |
|
{ |
|
displayMessage = GetDisplayMessage(failureKind, userMessage, conditionText); |
|
var contractFailedEventLocal = _contractFailedEvent; |
|
if (contractFailedEventLocal != null) |
|
{ |
|
eventArgs = new ContractFailedEventArgs(failureKind, displayMessage, conditionText, innerException); |
|
foreach (var @delegate in contractFailedEventLocal.GetInvocationList()) |
|
{ |
|
var handler = (EventHandler<ContractFailedEventArgs>)@delegate; |
|
try |
|
{ |
|
handler(null, eventArgs); |
|
} |
|
catch (Exception e) |
|
{ |
|
#if NET20 || NET30 || NET35 |
|
eventArgs.ThrownDuringHandler = e; |
|
#else |
|
GC.KeepAlive(e); |
|
#endif |
|
eventArgs.SetUnwind(); |
|
} |
|
} |
|
if (eventArgs.Unwind) |
|
{ |
|
#if NET20 || NET30 || NET35 |
|
// unwind |
|
if (innerException == null) |
|
{ |
|
innerException = eventArgs.ThrownDuringHandler; |
|
} |
|
#endif |
|
throw new ContractException(failureKind, displayMessage, userMessage, conditionText, innerException); |
|
} |
|
} |
|
} |
|
finally |
|
{ |
|
if (eventArgs != null && eventArgs.Handled) |
|
{ |
|
returnValue = null; // handled |
|
} |
|
else |
|
{ |
|
returnValue = displayMessage; |
|
} |
|
} |
|
resultFailureMessage = returnValue; |
|
} |
|
|
|
[DebuggerNonUserCode] |
|
[SecuritySafeCritical] |
|
private static void TriggerFailureImplementation(ContractFailureKind kind, string displayMessage, |
|
string userMessage, string conditionText, Exception innerException) |
|
{ |
|
// If we're here, our intent is to pop up a dialog box (if we can). For developers |
|
// interacting live with a debugger, this is a good experience. For Silverlight |
|
// hosted in Internet Explorer, the assert window is great. If we cannot |
|
// pop up a dialog box, throw an exception (consider a library compiled with |
|
// "Assert On Failure" but used in a process that can't pop up asserts, like an |
|
// NT Service). For the CLR hosted by server apps like SQL or Exchange, we should |
|
// trigger escalation policy. |
|
if (!Environment.UserInteractive) |
|
{ |
|
throw new ContractException(kind, displayMessage, userMessage, conditionText, innerException); |
|
} |
|
ContractHelperEx.Fail(displayMessage); |
|
} |
|
} |
|
} |
|
|
|
#endif |