File: Util\AppVerifier.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
namespace System.Web.Util {
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Permissions;
    using System.Text;
    using System.Threading;
    using System.Web;
 
    internal static class AppVerifier {
 
        // It's possible that multiple error codes might get mapped to the same description string.
        // This can happen if there might be multiple ways for a single problem to manifest itself.
        private static readonly Dictionary<AppVerifierErrorCode, string> _errorStringMappings = new Dictionary<AppVerifierErrorCode, string>() {
            { AppVerifierErrorCode.HttpApplicationInstanceWasNull, SR.AppVerifier_Errors_HttpApplicationInstanceWasNull },
            { AppVerifierErrorCode.BeginHandlerDelegateWasNull, SR.AppVerifier_Errors_BeginHandlerDelegateWasNull },
            { AppVerifierErrorCode.AsyncCallbackInvokedMultipleTimes, SR.AppVerifier_Errors_AsyncCallbackInvokedMultipleTimes },
            { AppVerifierErrorCode.AsyncCallbackInvokedWithNullParameter, SR.AppVerifier_Errors_AsyncCallbackInvokedWithNullParameter },
            { AppVerifierErrorCode.AsyncCallbackGivenAsyncResultWhichWasNotCompleted, SR.AppVerifier_Errors_AsyncCallbackGivenAsyncResultWhichWasNotCompleted },
            { AppVerifierErrorCode.AsyncCallbackInvokedSynchronouslyButAsyncResultWasNotMarkedCompletedSynchronously, SR.AppVerifier_Errors_AsyncCallbackInvokedSynchronouslyButAsyncResultWasNotMarkedCompletedSynchronously },
            { AppVerifierErrorCode.AsyncCallbackInvokedAsynchronouslyButAsyncResultWasMarkedCompletedSynchronously, SR.AppVerifier_Errors_AsyncCallbackInvokedAsynchronouslyButAsyncResultWasMarkedCompletedSynchronously },
            { AppVerifierErrorCode.AsyncCallbackInvokedWithUnexpectedAsyncResultInstance, SR.AppVerifier_Errors_AsyncCallbackInvokedWithUnexpectedAsyncResultInstance },
            { AppVerifierErrorCode.AsyncCallbackInvokedAsynchronouslyThenBeginHandlerThrew, SR.AppVerifier_Errors_AsyncCallbackInvokedEvenThoughBeginHandlerThrew },
            { AppVerifierErrorCode.BeginHandlerThrewThenAsyncCallbackInvokedAsynchronously, SR.AppVerifier_Errors_AsyncCallbackInvokedEvenThoughBeginHandlerThrew },
            { AppVerifierErrorCode.AsyncCallbackInvokedSynchronouslyThenBeginHandlerThrew, SR.AppVerifier_Errors_AsyncCallbackInvokedEvenThoughBeginHandlerThrew },
            { AppVerifierErrorCode.AsyncCallbackInvokedWithUnexpectedAsyncResultAsyncState, SR.AppVerifier_Errors_AsyncCallbackInvokedWithUnexpectedAsyncResultAsyncState },
            { AppVerifierErrorCode.AsyncCallbackCalledAfterHttpApplicationReassigned, SR.AppVerifier_Errors_AsyncCallbackCalledAfterHttpApplicationReassigned },
            { AppVerifierErrorCode.BeginHandlerReturnedNull, SR.AppVerifier_Errors_BeginHandlerReturnedNull },
            { AppVerifierErrorCode.BeginHandlerReturnedAsyncResultMarkedCompletedSynchronouslyButWhichWasNotCompleted, SR.AppVerifier_Errors_BeginHandlerReturnedAsyncResultMarkedCompletedSynchronouslyButWhichWasNotCompleted },
            { AppVerifierErrorCode.BeginHandlerReturnedAsyncResultMarkedCompletedSynchronouslyButAsyncCallbackNeverCalled, SR.AppVerifier_Errors_BeginHandlerReturnedAsyncResultMarkedCompletedSynchronouslyButAsyncCallbackNeverCalled },
            { AppVerifierErrorCode.BeginHandlerReturnedUnexpectedAsyncResultInstance, SR.AppVerifier_Errors_AsyncCallbackInvokedWithUnexpectedAsyncResultInstance },
            { AppVerifierErrorCode.BeginHandlerReturnedUnexpectedAsyncResultAsyncState, SR.AppVerifier_Errors_BeginHandlerReturnedUnexpectedAsyncResultAsyncState },
            { AppVerifierErrorCode.SyncContextSendOrPostCalledAfterRequestCompleted, SR.AppVerifier_Errors_SyncContextSendOrPostCalledAfterRequestCompleted },
            { AppVerifierErrorCode.SyncContextSendOrPostCalledBetweenNotifications, SR.AppVerifier_Errors_SyncContextSendOrPostCalledBetweenNotifications },
            { AppVerifierErrorCode.SyncContextPostCalledInNestedNotification, SR.AppVerifier_Errors_SyncContextPostCalledInNestedNotification },
            { AppVerifierErrorCode.RequestNotificationCompletedSynchronouslyWithNotificationContextPending, SR.AppVerifier_Errors_RequestNotificationCompletedSynchronouslyWithNotificationContextPending },
            { AppVerifierErrorCode.NotificationContextHasChangedAfterSynchronouslyProcessingNotification, SR.AppVerifier_Errors_NotificationContextHasChangedAfterSynchronouslyProcessingNotification },
            { AppVerifierErrorCode.PendingProcessRequestNotificationStatusAfterCompletingNestedNotification, SR.AppVerifier_Errors_PendingProcessRequestNotificationStatusAfterCompletingNestedNotification },
        };
 
        // Provides an option for different wrappers to specify whether to collect the call stacks traces
        [FlagsAttribute]
        internal enum CallStackCollectionBitMasks : int {
            AllMask = -1,
 
            // used for a 3-parameter Begin* method [(T, AsyncCallback, object) -> IAsyncResult] wrapper
            BeginCallHandlerMask = 1,
            CallHandlerCallbackMask = 2,
 
            // used for a BeginEventHandler method [(object, sender, EventArgs, object) -> IAsyncResult] wrapper
            BeginExecutionStepMask = 4,
            ExecutionStepCallbackMask = 8,
 
            // when adding new bits above also update the following:
            AllHandlerMask = BeginCallHandlerMask | CallHandlerCallbackMask,
            AllStepMask = BeginExecutionStepMask | ExecutionStepCallbackMask,
        
            AllBeginMask = BeginCallHandlerMask | BeginExecutionStepMask,
            AllCallbackMask = CallHandlerCallbackMask | ExecutionStepCallbackMask
        };
 
        // The declarative order of these two fields is important; don't swap them!
        private static Action<AppVerifierException> DefaultAppVerifierBehavior = GetAppVerifierBehaviorFromRegistry();
        internal static readonly bool IsAppVerifierEnabled = (DefaultAppVerifierBehavior != null);
        private static long AppVerifierErrorCodeCollectCallStackMask;
        private static long AppVerifierErrorCodeEnableAssertMask;
        private static CallStackCollectionBitMasks AppVerifierCollectCallStackMask;
 
        private delegate void AssertDelegate(bool condition, AppVerifierErrorCode errorCode);
        private delegate void AppendAdditionalInfoDelegate(StringBuilder errorString);
 
        // Returns an AppVerifier handler (something that can record exceptions appropriately)
        // appropriate to what was set in the system registry. If the key we're looking for
        // doesn't exist or doesn't have a known value, we return 'null', signifying that
        // AppVerifier is disabled.
        private static Action<AppVerifierException> GetAppVerifierBehaviorFromRegistry() {
            // use 0 as the default value if the key doesn't exist or is of the wrong type
            int valueFromRegistry = (Misc.GetAspNetRegValue(subKey: null, valueName: "RuntimeVerificationBehavior", defaultValue: null) as int?) ?? 0;
 
            // REG_QWORD used as a mask to disable individual asserts. No key means all asserts are enabled
            AppVerifierErrorCodeEnableAssertMask = (Misc.GetAspNetRegValue(subKey: null, valueName: "AppVerifierErrorCodeEnableAssertMask", defaultValue: (long)(-1)) as long?) ?? (long)(-1);
 
            // REG_QWORD used as a mask to control call stack collection on individual asserts (useful if we event log only). No key means all asserts will collect stack traces
            AppVerifierErrorCodeCollectCallStackMask = (Misc.GetAspNetRegValue(subKey: null, valueName: "AppVerifierErrorCodeCollectCallstackMask", defaultValue: (long)(-1)) as long?) ?? (long)(-1);
 
            // REG_DWORD mask to disable call stack collection on begin* / end* methods. No key means all call stacks are collected
            AppVerifierCollectCallStackMask = (CallStackCollectionBitMasks)((Misc.GetAspNetRegValue(subKey: null, valueName: "AppVerifierCollectCallStackMask", defaultValue: (int)(-1)) as int?) ?? (int)(-1));
            
            switch (valueFromRegistry) {
                case 1:
                    // Just write to the event log
                    return WriteToEventLog;
 
                case 2:
                    // Write to the event log and Debugger.Launch / Debugger.Break
                    return WriteToEventLogAndSoftBreak;
 
                case 3:
                    // Write to the event log and kernel32!DebugBreak
                    return WriteToEventLogAndHardBreak;
 
                default:
                    // not enabled
                    return null;
            }
        }
 
        // Writes an exception to the Windows Event Log (Application section)
        private static void WriteToEventLog(AppVerifierException ex) {
            Misc.WriteUnhandledExceptionToEventLog(AppDomain.CurrentDomain, ex); // method won't throw
        }
 
        [SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)] // safe since AppVerifier can only be enabled via registry, which already requires admin privileges
        private static void WriteToEventLogAndSoftBreak(AppVerifierException ex) {
            // A "soft" break means that we prompt to launch a debugger, and if one is attached we'll signal it.
            WriteToEventLog(ex);
            if (Debugger.Launch()) {
                Debugger.Break();
            }
        }
 
        [SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)] // safe since AppVerifier can only be enabled via registry, which already requires admin privileges
        private static void WriteToEventLogAndHardBreak(AppVerifierException ex) {
            // A "hard" break means that we'll signal any attached debugger, and if none is attached
            // we'll just INT 3 and hope for the best. (This may cause a Watson dump depending on environment.)
            WriteToEventLog(ex);
            if (Debugger.IsAttached) {
                Debugger.Break();
            }
            else {
                NativeMethods.DebugBreak();
            }
        }
 
        // Instruments a 3-parameter Begin* method [(T, AsyncCallback, object) -> IAsyncResult].
        // If AppVerifier is disabled, returns the original method unmodified.
        public static Func<T, AsyncCallback, object, IAsyncResult> WrapBeginMethod<T>(HttpApplication httpApplication, Func<T, AsyncCallback, object, IAsyncResult> originalDelegate) {
            if (!IsAppVerifierEnabled) {
                return originalDelegate;
            }
 
            return (arg, callback, state) => WrapBeginMethodImpl(
                httpApplication: httpApplication,
                beginMethod: (innerCallback, innerState) => originalDelegate(arg, innerCallback, innerState),
                originalDelegate: originalDelegate,
                errorHandler: HandleAppVerifierException,
                callStackMask: CallStackCollectionBitMasks.AllHandlerMask)
                (callback, state);
        }
 
        // Instruments a BeginEventHandler method [(object, sender, EventArgs, object) -> IAsyncResult].
        // This pattern is commonly used, such as by IHttpModule, PageAsyncTask, and others.
        // If AppVerifier is disabled, returns the original method unmodified.
        public static BeginEventHandler WrapBeginMethod(HttpApplication httpApplication, BeginEventHandler originalDelegate) {
            if (!IsAppVerifierEnabled) {
                return originalDelegate;
            }
 
            return (sender, e, cb, extraData) => WrapBeginMethodImpl(
                httpApplication: httpApplication,
                beginMethod: (innerCallback, innerState) => originalDelegate(sender, e, innerCallback, innerState),
                originalDelegate: originalDelegate,
                errorHandler: HandleAppVerifierException,
                callStackMask: CallStackCollectionBitMasks.AllStepMask)
                (cb, extraData);
        }
 
        /// <summary>
        /// Wraps the Begin* part of a Begin / End method pair to allow for signaling when assertions have been violated.
        /// The instrumentation can be a performance hit, so this method should not be called if AppVerifier is not enabled.
        /// </summary>
        /// <param name="httpApplication">The HttpApplication instance for this request, used to get HttpContext and related items.</param>
        /// <param name="beginMethod">The Begin* part of a Begin / End method pair, likely wrapped in a lambda so only the AsyncCallback and object parameters are exposed.</param>
        /// <param name="originalDelegate">The original user-provided delegate, e.g. the thing that 'beginMethod' wraps. Provided so that we can show correct methods when asserting.</param>
        /// <param name="errorHandler">The listener that can handle verification failures.</param>
        /// <returns>The instrumented Begin* method.</returns>
        internal static Func<AsyncCallback, object, IAsyncResult> WrapBeginMethodImpl(HttpApplication httpApplication, Func<AsyncCallback, object, IAsyncResult> beginMethod, Delegate originalDelegate, Action<AppVerifierException> errorHandler, CallStackCollectionBitMasks callStackMask) {
            return (callback, state) => {
                // basic diagnostic info goes at the top since it's used during generation of the error message
                AsyncCallbackInvocationHelper asyncCallbackInvocationHelper = new AsyncCallbackInvocationHelper();
                CallStackCollectionBitMasks myBeginMask = callStackMask & CallStackCollectionBitMasks.AllBeginMask;
                bool captureBeginStack = (myBeginMask & (CallStackCollectionBitMasks)AppVerifierCollectCallStackMask) == myBeginMask;
 
                InvocationInfo beginHandlerInvocationInfo = InvocationInfo.Capture(captureBeginStack);
                string requestUrl = null;
                RequestNotification? currentNotification = null;
                bool isPostNotification = false;
                Type httpHandlerType = null;
 
                // need to collect all this up-front since it might go away during the async operation
                if (httpApplication != null) {
                    HttpContext context = httpApplication.Context;
                    if (context != null) {
                        if (!context.HideRequestResponse && context.Request != null) {
                            requestUrl = TryGetRequestUrl(context);
                        }
 
                        if (context.NotificationContext != null) {
                            currentNotification = context.NotificationContext.CurrentNotification;
                            isPostNotification = context.NotificationContext.IsPostNotification;
                        }
 
                        if (context.Handler != null) {
                            httpHandlerType = context.Handler.GetType();
                        }
                    }
                }
 
                // If the condition passed to this method evaluates to false, we will raise an error to whoever is listening.
                AssertDelegate assert = (condition, errorCode) => {
                    long mask = 1L<<(int)errorCode;
                    // assert only if it was not masked out by a bit set
                    bool enableAssert = (AppVerifierErrorCodeEnableAssertMask & mask) == mask;
 
                    if (!condition && enableAssert) {
                        // capture the stack only if it was not masked out by a bit set
                        bool captureStack = (AppVerifierErrorCodeCollectCallStackMask & mask) == mask;
 
                        InvocationInfo assertInvocationInfo = InvocationInfo.Capture(captureStack);
 
                        // header
                        StringBuilder errorString = new StringBuilder();
                        errorString.AppendLine(FormatErrorString(SR.AppVerifier_Title));
                        errorString.AppendLine(FormatErrorString(SR.AppVerifier_Subtitle));
                        errorString.AppendLine();
 
                        // basic info (about the assert)
                        errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_URL, requestUrl));
                        errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_ErrorCode, (int)errorCode));
                        errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_Description, GetLocalizedDescriptionStringForError(errorCode)));
                        errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_ThreadInfo, assertInvocationInfo.ThreadId, assertInvocationInfo.Timestamp.ToLocalTime()));
                        errorString.AppendLine(assertInvocationInfo.StackTrace.ToString());
 
                        // Begin* method info
                        errorString.AppendLine(FormatErrorString(SR.AppVerifier_BeginMethodInfo_EntryMethod, PrettyPrintDelegate(originalDelegate)));
                        if (currentNotification != null) {
                            errorString.AppendLine(FormatErrorString(SR.AppVerifier_BeginMethodInfo_RequestNotification_Integrated, currentNotification, isPostNotification));
                        }
                        else {
                            errorString.AppendLine(FormatErrorString(SR.AppVerifier_BeginMethodInfo_RequestNotification_NotIntegrated));
                        }
                        errorString.AppendLine(FormatErrorString(SR.AppVerifier_BeginMethodInfo_CurrentHandler, httpHandlerType));
                        errorString.AppendLine(FormatErrorString(SR.AppVerifier_BeginMethodInfo_ThreadInfo, beginHandlerInvocationInfo.ThreadId, beginHandlerInvocationInfo.Timestamp.ToLocalTime()));
                        errorString.AppendLine(beginHandlerInvocationInfo.StackTrace.ToString());
 
                        // AsyncCallback info
                        int totalAsyncInvocationCount;
                        InvocationInfo firstAsyncInvocation = asyncCallbackInvocationHelper.GetFirstInvocationInfo(out totalAsyncInvocationCount);
                        errorString.AppendLine(FormatErrorString(SR.AppVerifier_AsyncCallbackInfo_InvocationCount, totalAsyncInvocationCount));
                        if (firstAsyncInvocation != null) {
                            errorString.AppendLine(FormatErrorString(SR.AppVerifier_AsyncCallbackInfo_FirstInvocation_ThreadInfo, firstAsyncInvocation.ThreadId, firstAsyncInvocation.Timestamp.ToLocalTime()));
                            errorString.AppendLine(firstAsyncInvocation.StackTrace.ToString());
                        }
 
                        AppVerifierException ex = new AppVerifierException(errorCode, errorString.ToString());
                        errorHandler(ex);
                        throw ex;
                    }
                };
 
                assert(httpApplication != null, AppVerifierErrorCode.HttpApplicationInstanceWasNull);
                assert(originalDelegate != null, AppVerifierErrorCode.BeginHandlerDelegateWasNull);
 
                object lockObj = new object(); // used to synchronize access to certain locals which can be touched by multiple threads
                IAsyncResult asyncResultReturnedByBeginHandler = null;
                IAsyncResult asyncResultPassedToCallback = null;
                object beginHandlerReturnValueHolder = null; // used to hold the IAsyncResult returned by or Exception thrown by BeginHandler; see comments on Holder<T> for more info
                Thread threadWhichCalledBeginHandler = Thread.CurrentThread; // used to determine whether the callback was invoked synchronously
                bool callbackRanToCompletion = false; // don't need to lock when touching this local since it's only read in the synchronous case
 
                HttpContext assignedContextUponCallingBeginHandler = httpApplication.Context; // used to determine whether the underlying request disappeared
 
                try {
                    asyncResultReturnedByBeginHandler = beginMethod(
                       asyncResult => {
                           try {
                               CallStackCollectionBitMasks myCallbackMask = callStackMask & CallStackCollectionBitMasks.AllCallbackMask;
                               bool captureEndCallStack = (myCallbackMask & AppVerifierCollectCallStackMask ) == myCallbackMask;
                               // The callback must never be called more than once.
                               int newAsyncCallbackInvocationCount = asyncCallbackInvocationHelper.RecordInvocation(captureEndCallStack);
                               assert(newAsyncCallbackInvocationCount == 1, AppVerifierErrorCode.AsyncCallbackInvokedMultipleTimes);
 
                               // The 'asyncResult' parameter must never be null.
                               assert(asyncResult != null, AppVerifierErrorCode.AsyncCallbackInvokedWithNullParameter);
 
                               object tempBeginHandlerReturnValueHolder;
                               Thread tempThreadWhichCalledBeginHandler;
                               lock (lockObj) {
                                   asyncResultPassedToCallback = asyncResult;
                                   tempBeginHandlerReturnValueHolder = beginHandlerReturnValueHolder;
                                   tempThreadWhichCalledBeginHandler = threadWhichCalledBeginHandler;
                               }
 
                               // At this point, 'IsCompleted = true' is mandatory.
                               assert(asyncResult.IsCompleted, AppVerifierErrorCode.AsyncCallbackGivenAsyncResultWhichWasNotCompleted);
 
                               if (tempBeginHandlerReturnValueHolder == null) {
                                   // BeginHandler hasn't yet returned, so this call may be synchronous or asynchronous.
                                   // We can tell by comparing the current thread with the thread which called BeginHandler.
                                   // From a correctness perspective, it is valid to invoke the AsyncCallback delegate either
                                   // synchronously or asynchronously. From Microsoft: if 'CompletedSynchronously = true', then
                                   // AsyncCallback invocation can happen either on the same thread or on a different thread,
                                   // just as long as BeginHandler hasn't yet returned (which in true in this case).
                                   if (!asyncResult.CompletedSynchronously) {
                                       // If 'CompletedSynchronously = false', we must be on a different thread than the BeginHandler invocation.
                                       assert(tempThreadWhichCalledBeginHandler != Thread.CurrentThread, AppVerifierErrorCode.AsyncCallbackInvokedSynchronouslyButAsyncResultWasNotMarkedCompletedSynchronously);
                                   }
                               }
                               else {
                                   // BeginHandler already returned, so this invocation is definitely asynchronous.
 
                                   Holder<IAsyncResult> asyncResultHolder = tempBeginHandlerReturnValueHolder as Holder<IAsyncResult>;
                                   if (asyncResultHolder != null) {
                                       // We need to verify that the IAsyncResult we're given is the same that was returned by BeginHandler
                                       // and that the IAsyncResult is marked 'CompletedSynchronously = false'.
                                       assert(asyncResult == asyncResultHolder.Value, AppVerifierErrorCode.AsyncCallbackInvokedWithUnexpectedAsyncResultInstance);
                                       assert(!asyncResult.CompletedSynchronously, AppVerifierErrorCode.AsyncCallbackInvokedAsynchronouslyButAsyncResultWasMarkedCompletedSynchronously);
                                   }
                                   else {
                                       // If we reached this point, BeginHandler threw an exception.
                                       // The AsyncCallback should never be invoked if BeginHandler has already failed.
                                       assert(false, AppVerifierErrorCode.BeginHandlerThrewThenAsyncCallbackInvokedAsynchronously);
                                   }
                               }
 
                               // AsyncState must match the 'state' parameter passed to BeginHandler
                               assert(asyncResult.AsyncState == state, AppVerifierErrorCode.AsyncCallbackInvokedWithUnexpectedAsyncResultAsyncState);
 
                               // Make sure the underlying HttpApplication is still assigned to the captured HttpContext instance.
                               // If not, this AsyncCallback invocation could end up completing *some other request's* operation,
                               // resulting in data corruption.
                               assert(assignedContextUponCallingBeginHandler == httpApplication.Context, AppVerifierErrorCode.AsyncCallbackCalledAfterHttpApplicationReassigned);
                           }
                           catch (AppVerifierException) {
                               // We want to ---- any exceptions thrown by our verification logic, as the failure
                               // has already been recorded by the appropriate listener. Just go straight to
                               // invoking the callback.
                           }
 
                           // all checks complete - delegate control to the actual callback
                           if (callback != null) {
                               callback(asyncResult);
                           }
                           callbackRanToCompletion = true;
                       },
                       state);
 
                    // The return value must never be null.
                    assert(asyncResultReturnedByBeginHandler != null, AppVerifierErrorCode.BeginHandlerReturnedNull);
 
                    lock (lockObj) {
                        beginHandlerReturnValueHolder = new Holder<IAsyncResult>(asyncResultReturnedByBeginHandler);
                    }
 
                    if (asyncResultReturnedByBeginHandler.CompletedSynchronously) {
                        // If 'CompletedSynchronously = true', the IAsyncResult must be marked 'IsCompleted = true'
                        // and the AsyncCallback must have been invoked synchronously (checked in the AsyncCallback verification logic).
                        assert(asyncResultReturnedByBeginHandler.IsCompleted, AppVerifierErrorCode.BeginHandlerReturnedAsyncResultMarkedCompletedSynchronouslyButWhichWasNotCompleted);
                        assert(asyncCallbackInvocationHelper.TotalInvocations != 0, AppVerifierErrorCode.BeginHandlerReturnedAsyncResultMarkedCompletedSynchronouslyButAsyncCallbackNeverCalled);
                    }
 
                    IAsyncResult tempAsyncResultPassedToCallback;
                    lock (lockObj) {
                        tempAsyncResultPassedToCallback = asyncResultPassedToCallback;
                    }
 
                    // The AsyncCallback may have been invoked (either synchronously or asynchronously). If it has been
                    // invoked, we need to verify that it was given the same IAsyncResult returned by BeginHandler.
                    // If the AsyncCallback hasn't yet been called, we skip this check, as the AsyncCallback verification
                    // logic will eventually perform the check at the appropriate time.
                    if (tempAsyncResultPassedToCallback != null) {
                        assert(tempAsyncResultPassedToCallback == asyncResultReturnedByBeginHandler, AppVerifierErrorCode.BeginHandlerReturnedUnexpectedAsyncResultInstance);
                    }
 
                    // AsyncState must match the 'state' parameter passed to BeginHandler
                    assert(asyncResultReturnedByBeginHandler.AsyncState == state, AppVerifierErrorCode.BeginHandlerReturnedUnexpectedAsyncResultAsyncState);
 
                    // all checks complete
                    return asyncResultReturnedByBeginHandler;
                }
                catch (AppVerifierException) {
                    // We want to ---- any exceptions thrown by our verification logic, as the failure
                    // has already been recorded by the appropriate listener. Just return the original
                    // IAsyncResult so that the application continues to run.
                    return asyncResultReturnedByBeginHandler;
                }
                catch (Exception ex) {
                    if (asyncResultReturnedByBeginHandler == null) {
                        // If we reached this point, an exception was thrown by BeginHandler, so we need to
                        // record it and rethrow it.
 
                        IAsyncResult tempAsyncResultPassedToCallback;
                        lock (lockObj) {
                            beginHandlerReturnValueHolder = new Holder<Exception>(ex);
                            tempAsyncResultPassedToCallback = asyncResultPassedToCallback;
                        }
 
                        try {
                            // The AsyncCallback should only be invoked if BeginHandler ran to completion.
                            if (tempAsyncResultPassedToCallback != null) {
 
                                // If AsyncCallback was invoked asynchronously, then by definition it was
                                // scheduled prematurely since BeginHandler hadn't yet run to completion
                                // (since whatever additional work it did after invoking the callback failed).
                                // Therefore it is always wrong for BeginHandler to both throw and
                                // asynchronously invoke AsyncCallback.
                                assert(tempAsyncResultPassedToCallback.CompletedSynchronously, AppVerifierErrorCode.AsyncCallbackInvokedAsynchronouslyThenBeginHandlerThrew);
 
                                // If AsyncCallback was invoked synchronously, then it must have been invoked
                                // before BeginHandler surfaced the exception (since otherwise BeginHandler
                                // wouldn't have reached the line of code that invoked AsyncCallback). But
                                // AsyncCallback itself could have thrown, bubbling the exception up through
                                // BeginHandler and back to us. If AsyncCallback ran to completion, then this
                                // means BeginHandler did extra work (which failed) after invoking AsyncCallback,
                                // so BeginHandler by definition hadn't yet run to completion.
                                assert(!callbackRanToCompletion, AppVerifierErrorCode.AsyncCallbackInvokedSynchronouslyThenBeginHandlerThrew);
                            }
                        }
                        catch (AppVerifierException) {
                            // We want to ---- any exceptions thrown by our verification logic, as the failure
                            // has already been recorded by the appropriate listener. Propagate the original
                            // exception upward.
                        }
 
                        throw;
                    }
                    else {
                        // We want to ---- any exceptions thrown by our verification logic, as the failure
                        // has already been recorded by the appropriate listener. Just return the original
                        // IAsyncResult so that the application continues to run.
                        return asyncResultReturnedByBeginHandler;
                    }
                }
                finally {
                    // Since our local variables are GC-rooted in an anonymous object, we should
                    // clear references to objects we no longer need so that the GC can reclaim
                    // them if appropriate.
                    lock (lockObj) {
                        threadWhichCalledBeginHandler = null;
                    }
                }
            };
        }
 
        // Gets a delegate that checks for application code trying to call into the SyncContext after
        // the request or the request notification is already completed. 
        // The Action returned by this method could be null.
        public static Action<bool> GetSyncContextCheckDelegate(ISyncContext syncContext) {
            if (!IsAppVerifierEnabled) {
                return null;
            }
 
            return GetSyncContextCheckDelegateImpl(syncContext, HandleAppVerifierException);
        }
 
        /// <summary>
        /// Returns an Action<bool> that determines whether SynchronizationContext.Send or Post was called after the underlying request 
        /// or the request notification finished. The bool parameter controls whether to check if Post is attempted in nested notification.
        /// The instrumentation can be a performance hit, so this method should not be called if AppVerifier is not enabled.
        /// </summary>
        /// <param name="syncContext">The ISyncContext (HttpApplication, WebSocketPipeline, etc.) on which to perform the check.</param>
        /// <param name="errorHandler">The listener that can handle verification failures.</param>
        /// <returns>A callback which performs the verification.</returns>
        internal static Action<bool> GetSyncContextCheckDelegateImpl(ISyncContext syncContext, Action<AppVerifierException> errorHandler) {
            string requestUrl = null;
            object originalThreadContextId = null;
 
            // collect all of the diagnostic information upfront
            HttpContext originalHttpContext = (syncContext != null) ? syncContext.HttpContext : null;
            if (originalHttpContext != null) {
                if (!originalHttpContext.HideRequestResponse && originalHttpContext.Request != null) {
                    requestUrl = TryGetRequestUrl(originalHttpContext);
                }
 
                // This will be used as a surrogate for the captured HttpContext so that we don't
                // have a long-lived reference to a heavy object graph. See comments on ThreadContextId
                // for more info.
                originalThreadContextId = originalHttpContext.ThreadContextId;
                originalHttpContext = null;
            }
 
            // If the condition passed to this method evaluates to false, we will raise an error to whoever is listening.
            AssertDelegate assert = GetAssertDelegateImpl(requestUrl, errorHandler, appendAdditionalInfoDelegate: null);
 
            return (checkForReEntry) => {
                try {
                    // Make sure that the ISyncContext is still associated with the same HttpContext that
                    // we captured earlier.
                    HttpContext currentHttpContext = (syncContext != null) ? syncContext.HttpContext : null;
                    object currentThreadContextId = (currentHttpContext != null) ? currentHttpContext.ThreadContextId : null;
                    assert(currentThreadContextId != null && ReferenceEquals(originalThreadContextId, currentThreadContextId), AppVerifierErrorCode.SyncContextSendOrPostCalledAfterRequestCompleted);
 
                    if (HttpRuntime.UsingIntegratedPipeline && !currentHttpContext.HasWebSocketRequestTransitionCompleted) {
                        var notificationContext = (currentHttpContext != null) ? currentHttpContext.NotificationContext : null;
                        assert(notificationContext != null, AppVerifierErrorCode.SyncContextSendOrPostCalledBetweenNotifications);
 
                        if (checkForReEntry && notificationContext != null) {
                            assert(!notificationContext.IsReEntry, AppVerifierErrorCode.SyncContextPostCalledInNestedNotification);
                        }
                    }
                }
                catch (AppVerifierException) {
                    // We want to ---- any exceptions thrown by our verification logic, as the failure
                    // has already been recorded by the appropriate listener. Propagate the original
                    // exception upward.
                }
            };
        }
 
        // This generic method invokes a delegate that was created by AppVerifier at an earlier time.
        // It is safe to call it even if the returned delegate is null (e.g. AppVerifier is off).
        // Here is the typical usage scenario:
        //      var verifierCheck = AppVerifier.Get*CheckDelegate(...);         // get the delegate which can capture some state
        //      T result = <...>                                                // the result of some code execution
        //      AppVerifier.InvokeVerifierCheck(verifierCheckDelegate, result); // invoke the verification of the result
        internal static void InvokeVerifierCheck<T>(Action<T> verifierCheckDelegate, T result)
        {
            if (verifierCheckDelegate != null) {
                try {
                    verifierCheckDelegate(result);
                }
                catch (AppVerifierException) {
                    // We want to ---- any exceptions thrown by our verification logic, as the failure
                    // has already been recorded by the appropriate listener.
                }
            }
        }
 
        // Gets a delegate that checks for inconsistencies after managed code finished processing one or more request notifications.
        // The Action returned by this method could be null.
        internal static Action<RequestNotificationStatus> GetRequestNotificationStatusCheckDelegate(HttpContext context, RequestNotification currentNotification, bool isPostNotification) {
            if (!IsAppVerifierEnabled) {
                return null;
            }
            return GetRequestNotificationStatusCheckDelegateImpl(context, currentNotification, isPostNotification, HandleAppVerifierException);
        }
 
        /// <summary>
        /// Returns an Action<RequestNotificationStatus> that will check for inconsistencies after 
        /// managed code has finished processing one or more notifications and about to return back to IIS. 
        /// </summary>
        /// <returns>A callback which performs the verification.</returns>
        internal static Action<RequestNotificationStatus> GetRequestNotificationStatusCheckDelegateImpl(HttpContext context, RequestNotification currentNotification, bool isPostNotification, Action<AppVerifierException> errorHandler) {
            // collect all of the diagnostic information upfront
            NotificationContext originalNotificationContext = context.NotificationContext;
            bool isReentry = originalNotificationContext.IsReEntry;
            string requestUrl = null;
            if (!context.HideRequestResponse && context.Request != null) {
                requestUrl = TryGetRequestUrl(context);
            }
 
            AppendAdditionalInfoDelegate appendCurrentNotificationInfo = (errorString) => {
                errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_NotificationInfo, currentNotification, isPostNotification, isReentry));
            };
 
            AssertDelegate assert = GetAssertDelegateImpl(requestUrl, errorHandler, appendAdditionalInfoDelegate: appendCurrentNotificationInfo);
                
            return (RequestNotificationStatus status) => {
                if (status == RequestNotificationStatus.Pending) {
                    // We don't expect nested notifications to complete asynchronously
                    assert(!isReentry, AppVerifierErrorCode.PendingProcessRequestNotificationStatusAfterCompletingNestedNotification);
                }
                else {
                    // Completing synchronously with pending NotificationContext means a bug in either user code or the pipeline.
                    // NotificationContext being null means we already completed asynchronously before completing synchronously.
                    // Both cases indicate that we have some async operations we failed to account for.
                    assert(context.NotificationContext != null && !context.NotificationContext.PendingAsyncCompletion,
                            AppVerifierErrorCode.RequestNotificationCompletedSynchronouslyWithNotificationContextPending);
 
                    // Can't have a different NotificationContext after finishing the notification
                    // Even if it was changed while processing nested notifications it should be restored back before we unwind
                    assert(context.NotificationContext == originalNotificationContext,
                            AppVerifierErrorCode.NotificationContextHasChangedAfterSynchronouslyProcessingNotification);
                }
            };
        }
 
        /// <summary>
        /// This method returns the default implementation of the assert code which takes care of 
        /// evaluating the assert contition, handing assert and stack collection enabling masks,
        /// and creating an AppVerifierException with basic information
        /// </summary>
        /// <param name="requestUrl">The Url of the request.</param>
        /// <param name="errorHandler">The listener that can handle verification failures.</param>
        /// <param name="appendAdditionalInfoDelegate">The caller can provide this delegate to append additional information to the exception. Could be null.</param>
        /// <returns>A callback which performs the verification.</returns>
        private static AssertDelegate GetAssertDelegateImpl(string requestUrl, Action<AppVerifierException> errorHandler, AppendAdditionalInfoDelegate appendAdditionalInfoDelegate) {
            // If the condition passed to this method evaluates to false, we will raise an error to whoever is listening.
            return (condition, errorCode) => {
                long mask = 1L << (int)errorCode;
                // assert only if it was not masked out by a bit set
                bool enableAssert = (AppVerifierErrorCodeEnableAssertMask & mask) == mask;
 
                if (!condition && enableAssert) {
                    // capture the stack only if it was not masked out by a bit set
                    bool captureStack = (AppVerifierErrorCodeCollectCallStackMask & mask) == mask;
                    InvocationInfo assertInvocationInfo = InvocationInfo.Capture(captureStack);
 
                    // header
                    StringBuilder errorString = new StringBuilder();
                    errorString.AppendLine(FormatErrorString(SR.AppVerifier_Title));
                    errorString.AppendLine(FormatErrorString(SR.AppVerifier_Subtitle));
                    errorString.AppendLine();
 
                    // basic info (about the assert)
                    errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_URL, requestUrl));
                    errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_ErrorCode, (int)errorCode));
                    errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_Description, GetLocalizedDescriptionStringForError(errorCode)));
                    errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_ThreadInfo, assertInvocationInfo.ThreadId, assertInvocationInfo.Timestamp.ToLocalTime()));
 
                    // append additional info if needed
                    if (appendAdditionalInfoDelegate != null) {
                        appendAdditionalInfoDelegate(errorString);
                    }
 
                    // append the stack trace
                    errorString.AppendLine(assertInvocationInfo.StackTrace.ToString());
 
                    AppVerifierException ex = new AppVerifierException(errorCode, errorString.ToString());
                    errorHandler(ex);
                    throw ex;
                }
            };
        }
 
 
        // This is the default implementation of an AppVerifierException handler;
        // it just delegates to the configured behavior.
        [SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive", Justification = "Want to keep these locals on the stack to assist with debugging.")]
        [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
        private static void HandleAppVerifierException(AppVerifierException ex) {
            // This method is specifically written to maximize the chance of
            // useful information being on the stack as a local, where it's more
            // easily observed by the debugger.
 
            AppVerifierErrorCode errorCode = ex.ErrorCode;
            string fullMessage = ex.Message;
 
            DefaultAppVerifierBehavior(ex);
 
            GC.KeepAlive(errorCode);
            GC.KeepAlive(fullMessage);
            GC.KeepAlive(ex);
        }
 
        private static string TryGetRequestUrl(HttpContext context) {
            try {
                return context.Request.EnsureRawUrl();
            }
            catch (HttpException) {
                return null;
            }
        }
 
        internal static string PrettyPrintDelegate(Delegate del) {
            return PrettyPrintMemberInfo((del != null) ? del.Method : null);
        }
 
        // prints "TResult MethodName(TArg1, TArg2, ...) [Module.dll!Namespace.TypeName]"
        internal static string PrettyPrintMemberInfo(MethodInfo method) {
            if (method == null) {
                return null;
            }
 
            string retVal = method.ToString();
 
            Type type = method.ReflectedType;
            if (type != null) {
                retVal = retVal + " [";
                if (type.Module != null) {
                    retVal += type.Module.Name + "!";
                }
 
                retVal += type.FullName + "]";
            }
 
            return retVal;
        }
 
        internal static string GetLocalizedDescriptionStringForError(AppVerifierErrorCode errorCode) {
            return FormatErrorString(_errorStringMappings[errorCode]);
        }
 
        // We use InstalledUICulture rather than CurrentCulture / CurrentUICulture since these strings will
        // be stored in the system event log.
        [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.IFormatProvider,System.String,System.Object[])",
            Justification = "Matches culture specified in Misc.WriteUnhandledExceptionToEventLog.")]
        internal static string FormatErrorString(string name, params object[] args) {
            return String.Format(CultureInfo.InstalledUICulture, SR.Resources.GetString(name, CultureInfo.InstalledUICulture), args);
        }
 
        // contains a counter and invocation information for an AsyncCallback delegate
        private sealed class AsyncCallbackInvocationHelper {
            private InvocationInfo _firstInvocationInfo;
            private int _totalInvocationCount;
 
            public int TotalInvocations {
                [MethodImpl(MethodImplOptions.Synchronized)]
                get { return _totalInvocationCount; }
            }
 
            [MethodImpl(MethodImplOptions.Synchronized)]
            public InvocationInfo GetFirstInvocationInfo(out int totalInvocationCount) {
                totalInvocationCount = _totalInvocationCount;
                return _firstInvocationInfo;
            }
 
            [MethodImpl(MethodImplOptions.Synchronized)]
            public int RecordInvocation(bool captureCallStack) {
                _totalInvocationCount++;
                if (_firstInvocationInfo == null) {
                    _firstInvocationInfo = InvocationInfo.Capture(captureCallStack);
                }
                return _totalInvocationCount;
            }
        }
 
        // We use a special class for holding data so that we can store the local's
        // intended type alongside its real value. Prevents us from misinterpreting
        // the degenerate case of "----CustomType : Exception, IAsyncResult" so that
        // we know whether it was returned as an IAsyncResult or thrown as an Exception.
        private sealed class Holder<T> {
            public readonly T Value;
 
            public Holder(T value) {
                Value = value;
            }
        }
 
        // holds diagnostic information about a particular invocation
        private sealed class InvocationInfo {
            public readonly int ThreadId;
            public readonly DateTimeOffset Timestamp;
            public readonly string StackTrace;
 
            private InvocationInfo(bool captureStack) {
                ThreadId = Thread.CurrentThread.ManagedThreadId;
                Timestamp = DateTimeOffset.UtcNow; // UTC is faster, will convert to local on error
                StackTrace = captureStack? CaptureStackTrace(): "n/a";
            }
 
            public static InvocationInfo Capture(bool captureStack) {
                return new InvocationInfo(captureStack);
            }
 
            // captures a stack trace, removing AppVerifier.* frames from the top of the stack to minimize noise
            private static string CaptureStackTrace() {
                StackTrace fullStackTrace = new StackTrace(fNeedFileInfo: true);
                string[] traceLines = fullStackTrace.ToString().Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
                for (int i = 0; i < fullStackTrace.FrameCount && i < traceLines.Length; i++) {
                    StackFrame thisFrame = fullStackTrace.GetFrame(i);
                    if (thisFrame.GetMethod().Module == typeof(AppVerifier).Module
                        && thisFrame.GetMethod().DeclaringType.FullName.StartsWith("System.Web.Util.AppVerifier", StringComparison.Ordinal)) {
                        // we want to skip this frame since it's an AppVerifier.* frame
                        continue;
                    }
                    else {
                        // this is the first frame that is not an AppVerifier.* frame, so start the stack trace from here
                        return String.Join(Environment.NewLine, traceLines.Skip(i));
                    }
                }
 
                // if we reached this point, not sure what happened, so just return the original stack trace
                return fullStackTrace.ToString();
            }
        }
 
        [SuppressUnmanagedCodeSecurityAttribute]
        private static class NativeMethods {
            [DllImport("kernel32.dll")]
            internal extern static void DebugBreak();
        }
 
    }
}