Merge pull request #2964 from ludovic-henry/sgen-monocontext
[mono.git] / mcs / class / referencesource / System.Web / Util / AppVerifier.cs
index 4eb9b39b51e31aa65f5a6ef54c9301991fb0a43a..f45390c0d226f90c2f44a4b9897dcb1dba5b53e7 100644 (file)
             { 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
 
         // The declarative order of these two fields is important; don't swap them!
         private static Action<AppVerifierException> DefaultAppVerifierBehavior = GetAppVerifierBehaviorFromRegistry();
-        private static readonly bool IsAppVerifierEnabled = (DefaultAppVerifierBehavior != null);
+        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
                 bool captureBeginStack = (myBeginMask & (CallStackCollectionBitMasks)AppVerifierCollectCallStackMask) == myBeginMask;
 
                 InvocationInfo beginHandlerInvocationInfo = InvocationInfo.Capture(captureBeginStack);
-                Uri requestUrl = null;
+                string requestUrl = null;
                 RequestNotification? currentNotification = null;
                 bool isPostNotification = false;
                 Type httpHandlerType = null;
                     HttpContext context = httpApplication.Context;
                     if (context != null) {
                         if (!context.HideRequestResponse && context.Request != null) {
-                            requestUrl = context.Request.Unvalidated.Url;
+                            requestUrl = TryGetRequestUrl(context);
                         }
 
                         if (context.NotificationContext != null) {
         }
 
         // Gets a delegate that checks for application code trying to call into the SyncContext after
-        // the request is already completed. The Action returned by this method could be null.
-        public static Action GetSyncContextCheckDelegate(ISyncContext syncContext) {
+        // 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;
             }
         }
 
         /// <summary>
-        /// Returns an Action that determines whether SynchronizationContext.Send or Post was called after the underlying request finished.
+        /// 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 GetSyncContextCheckDelegateImpl(ISyncContext syncContext, Action<AppVerifierException> errorHandler) {
-            Uri requestUrl = null;
+        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 = originalHttpContext.Request.Unvalidated.Url;
+                    requestUrl = TryGetRequestUrl(originalHttpContext);
                 }
 
                 // This will be used as a surrogate for the captured HttpContext so that we don't
             }
 
             // If the condition passed to this method evaluates to false, we will raise an error to whoever is listening.
-            AssertDelegate assert = (condition, errorCode) => {
+            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;
                     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());
                     throw ex;
                 }
             };
-
-            return () => {
-                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);
-                }
-                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 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.")]
             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);
         }