90f6e3eec32f6b184908313a5289260e4659ea46
[mono.git] / mcs / class / referencesource / System.Web / Util / AppVerifier.cs
1 namespace System.Web.Util {
2     using System;
3     using System.Collections.Generic;
4     using System.Diagnostics;
5     using System.Diagnostics.CodeAnalysis;
6     using System.Globalization;
7     using System.Linq;
8     using System.Reflection;
9     using System.Runtime.CompilerServices;
10     using System.Runtime.InteropServices;
11     using System.Security;
12     using System.Security.Permissions;
13     using System.Text;
14     using System.Threading;
15     using System.Web;
16
17     internal static class AppVerifier {
18
19         // It's possible that multiple error codes might get mapped to the same description string.
20         // This can happen if there might be multiple ways for a single problem to manifest itself.
21         private static readonly Dictionary<AppVerifierErrorCode, string> _errorStringMappings = new Dictionary<AppVerifierErrorCode, string>() {
22             { AppVerifierErrorCode.HttpApplicationInstanceWasNull, SR.AppVerifier_Errors_HttpApplicationInstanceWasNull },
23             { AppVerifierErrorCode.BeginHandlerDelegateWasNull, SR.AppVerifier_Errors_BeginHandlerDelegateWasNull },
24             { AppVerifierErrorCode.AsyncCallbackInvokedMultipleTimes, SR.AppVerifier_Errors_AsyncCallbackInvokedMultipleTimes },
25             { AppVerifierErrorCode.AsyncCallbackInvokedWithNullParameter, SR.AppVerifier_Errors_AsyncCallbackInvokedWithNullParameter },
26             { AppVerifierErrorCode.AsyncCallbackGivenAsyncResultWhichWasNotCompleted, SR.AppVerifier_Errors_AsyncCallbackGivenAsyncResultWhichWasNotCompleted },
27             { AppVerifierErrorCode.AsyncCallbackInvokedSynchronouslyButAsyncResultWasNotMarkedCompletedSynchronously, SR.AppVerifier_Errors_AsyncCallbackInvokedSynchronouslyButAsyncResultWasNotMarkedCompletedSynchronously },
28             { AppVerifierErrorCode.AsyncCallbackInvokedAsynchronouslyButAsyncResultWasMarkedCompletedSynchronously, SR.AppVerifier_Errors_AsyncCallbackInvokedAsynchronouslyButAsyncResultWasMarkedCompletedSynchronously },
29             { AppVerifierErrorCode.AsyncCallbackInvokedWithUnexpectedAsyncResultInstance, SR.AppVerifier_Errors_AsyncCallbackInvokedWithUnexpectedAsyncResultInstance },
30             { AppVerifierErrorCode.AsyncCallbackInvokedAsynchronouslyThenBeginHandlerThrew, SR.AppVerifier_Errors_AsyncCallbackInvokedEvenThoughBeginHandlerThrew },
31             { AppVerifierErrorCode.BeginHandlerThrewThenAsyncCallbackInvokedAsynchronously, SR.AppVerifier_Errors_AsyncCallbackInvokedEvenThoughBeginHandlerThrew },
32             { AppVerifierErrorCode.AsyncCallbackInvokedSynchronouslyThenBeginHandlerThrew, SR.AppVerifier_Errors_AsyncCallbackInvokedEvenThoughBeginHandlerThrew },
33             { AppVerifierErrorCode.AsyncCallbackInvokedWithUnexpectedAsyncResultAsyncState, SR.AppVerifier_Errors_AsyncCallbackInvokedWithUnexpectedAsyncResultAsyncState },
34             { AppVerifierErrorCode.AsyncCallbackCalledAfterHttpApplicationReassigned, SR.AppVerifier_Errors_AsyncCallbackCalledAfterHttpApplicationReassigned },
35             { AppVerifierErrorCode.BeginHandlerReturnedNull, SR.AppVerifier_Errors_BeginHandlerReturnedNull },
36             { AppVerifierErrorCode.BeginHandlerReturnedAsyncResultMarkedCompletedSynchronouslyButWhichWasNotCompleted, SR.AppVerifier_Errors_BeginHandlerReturnedAsyncResultMarkedCompletedSynchronouslyButWhichWasNotCompleted },
37             { AppVerifierErrorCode.BeginHandlerReturnedAsyncResultMarkedCompletedSynchronouslyButAsyncCallbackNeverCalled, SR.AppVerifier_Errors_BeginHandlerReturnedAsyncResultMarkedCompletedSynchronouslyButAsyncCallbackNeverCalled },
38             { AppVerifierErrorCode.BeginHandlerReturnedUnexpectedAsyncResultInstance, SR.AppVerifier_Errors_AsyncCallbackInvokedWithUnexpectedAsyncResultInstance },
39             { AppVerifierErrorCode.BeginHandlerReturnedUnexpectedAsyncResultAsyncState, SR.AppVerifier_Errors_BeginHandlerReturnedUnexpectedAsyncResultAsyncState },
40             { AppVerifierErrorCode.SyncContextSendOrPostCalledAfterRequestCompleted, SR.AppVerifier_Errors_SyncContextSendOrPostCalledAfterRequestCompleted },
41             { AppVerifierErrorCode.SyncContextSendOrPostCalledBetweenNotifications, SR.AppVerifier_Errors_SyncContextSendOrPostCalledBetweenNotifications },
42             { AppVerifierErrorCode.SyncContextPostCalledInNestedNotification, SR.AppVerifier_Errors_SyncContextPostCalledInNestedNotification },
43             { AppVerifierErrorCode.RequestNotificationCompletedSynchronouslyWithNotificationContextPending, SR.AppVerifier_Errors_RequestNotificationCompletedSynchronouslyWithNotificationContextPending },
44             { AppVerifierErrorCode.NotificationContextHasChangedAfterSynchronouslyProcessingNotification, SR.AppVerifier_Errors_NotificationContextHasChangedAfterSynchronouslyProcessingNotification },
45             { AppVerifierErrorCode.PendingProcessRequestNotificationStatusAfterCompletingNestedNotification, SR.AppVerifier_Errors_PendingProcessRequestNotificationStatusAfterCompletingNestedNotification },
46         };
47
48         // Provides an option for different wrappers to specify whether to collect the call stacks traces
49         [FlagsAttribute]
50         internal enum CallStackCollectionBitMasks : int {
51             AllMask = -1,
52
53             // used for a 3-parameter Begin* method [(T, AsyncCallback, object) -> IAsyncResult] wrapper
54             BeginCallHandlerMask = 1,
55             CallHandlerCallbackMask = 2,
56
57             // used for a BeginEventHandler method [(object, sender, EventArgs, object) -> IAsyncResult] wrapper
58             BeginExecutionStepMask = 4,
59             ExecutionStepCallbackMask = 8,
60
61             // when adding new bits above also update the following:
62             AllHandlerMask = BeginCallHandlerMask | CallHandlerCallbackMask,
63             AllStepMask = BeginExecutionStepMask | ExecutionStepCallbackMask,
64         
65             AllBeginMask = BeginCallHandlerMask | BeginExecutionStepMask,
66             AllCallbackMask = CallHandlerCallbackMask | ExecutionStepCallbackMask
67         };
68
69         // The declarative order of these two fields is important; don't swap them!
70         private static Action<AppVerifierException> DefaultAppVerifierBehavior = GetAppVerifierBehaviorFromRegistry();
71         internal static readonly bool IsAppVerifierEnabled = (DefaultAppVerifierBehavior != null);
72         private static long AppVerifierErrorCodeCollectCallStackMask;
73         private static long AppVerifierErrorCodeEnableAssertMask;
74         private static CallStackCollectionBitMasks AppVerifierCollectCallStackMask;
75
76         private delegate void AssertDelegate(bool condition, AppVerifierErrorCode errorCode);
77         private delegate void AppendAdditionalInfoDelegate(StringBuilder errorString);
78
79         // Returns an AppVerifier handler (something that can record exceptions appropriately)
80         // appropriate to what was set in the system registry. If the key we're looking for
81         // doesn't exist or doesn't have a known value, we return 'null', signifying that
82         // AppVerifier is disabled.
83         private static Action<AppVerifierException> GetAppVerifierBehaviorFromRegistry() {
84             // use 0 as the default value if the key doesn't exist or is of the wrong type
85             int valueFromRegistry = (Misc.GetAspNetRegValue(subKey: null, valueName: "RuntimeVerificationBehavior", defaultValue: null) as int?) ?? 0;
86
87             // REG_QWORD used as a mask to disable individual asserts. No key means all asserts are enabled
88             AppVerifierErrorCodeEnableAssertMask = (Misc.GetAspNetRegValue(subKey: null, valueName: "AppVerifierErrorCodeEnableAssertMask", defaultValue: (long)(-1)) as long?) ?? (long)(-1);
89
90             // 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
91             AppVerifierErrorCodeCollectCallStackMask = (Misc.GetAspNetRegValue(subKey: null, valueName: "AppVerifierErrorCodeCollectCallstackMask", defaultValue: (long)(-1)) as long?) ?? (long)(-1);
92
93             // REG_DWORD mask to disable call stack collection on begin* / end* methods. No key means all call stacks are collected
94             AppVerifierCollectCallStackMask = (CallStackCollectionBitMasks)((Misc.GetAspNetRegValue(subKey: null, valueName: "AppVerifierCollectCallStackMask", defaultValue: (int)(-1)) as int?) ?? (int)(-1));
95             
96             switch (valueFromRegistry) {
97                 case 1:
98                     // Just write to the event log
99                     return WriteToEventLog;
100
101                 case 2:
102                     // Write to the event log and Debugger.Launch / Debugger.Break
103                     return WriteToEventLogAndSoftBreak;
104
105                 case 3:
106                     // Write to the event log and kernel32!DebugBreak
107                     return WriteToEventLogAndHardBreak;
108
109                 default:
110                     // not enabled
111                     return null;
112             }
113         }
114
115         // Writes an exception to the Windows Event Log (Application section)
116         private static void WriteToEventLog(AppVerifierException ex) {
117             Misc.WriteUnhandledExceptionToEventLog(AppDomain.CurrentDomain, ex); // method won't throw
118         }
119
120         [SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)] // safe since AppVerifier can only be enabled via registry, which already requires admin privileges
121         private static void WriteToEventLogAndSoftBreak(AppVerifierException ex) {
122             // A "soft" break means that we prompt to launch a debugger, and if one is attached we'll signal it.
123             WriteToEventLog(ex);
124             if (Debugger.Launch()) {
125                 Debugger.Break();
126             }
127         }
128
129         [SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)] // safe since AppVerifier can only be enabled via registry, which already requires admin privileges
130         private static void WriteToEventLogAndHardBreak(AppVerifierException ex) {
131             // A "hard" break means that we'll signal any attached debugger, and if none is attached
132             // we'll just INT 3 and hope for the best. (This may cause a Watson dump depending on environment.)
133             WriteToEventLog(ex);
134             if (Debugger.IsAttached) {
135                 Debugger.Break();
136             }
137             else {
138                 NativeMethods.DebugBreak();
139             }
140         }
141
142         // Instruments a 3-parameter Begin* method [(T, AsyncCallback, object) -> IAsyncResult].
143         // If AppVerifier is disabled, returns the original method unmodified.
144         public static Func<T, AsyncCallback, object, IAsyncResult> WrapBeginMethod<T>(HttpApplication httpApplication, Func<T, AsyncCallback, object, IAsyncResult> originalDelegate) {
145             if (!IsAppVerifierEnabled) {
146                 return originalDelegate;
147             }
148
149             return (arg, callback, state) => WrapBeginMethodImpl(
150                 httpApplication: httpApplication,
151                 beginMethod: (innerCallback, innerState) => originalDelegate(arg, innerCallback, innerState),
152                 originalDelegate: originalDelegate,
153                 errorHandler: HandleAppVerifierException,
154                 callStackMask: CallStackCollectionBitMasks.AllHandlerMask)
155                 (callback, state);
156         }
157
158         // Instruments a BeginEventHandler method [(object, sender, EventArgs, object) -> IAsyncResult].
159         // This pattern is commonly used, such as by IHttpModule, PageAsyncTask, and others.
160         // If AppVerifier is disabled, returns the original method unmodified.
161         public static BeginEventHandler WrapBeginMethod(HttpApplication httpApplication, BeginEventHandler originalDelegate) {
162             if (!IsAppVerifierEnabled) {
163                 return originalDelegate;
164             }
165
166             return (sender, e, cb, extraData) => WrapBeginMethodImpl(
167                 httpApplication: httpApplication,
168                 beginMethod: (innerCallback, innerState) => originalDelegate(sender, e, innerCallback, innerState),
169                 originalDelegate: originalDelegate,
170                 errorHandler: HandleAppVerifierException,
171                 callStackMask: CallStackCollectionBitMasks.AllStepMask)
172                 (cb, extraData);
173         }
174
175         /// <summary>
176         /// Wraps the Begin* part of a Begin / End method pair to allow for signaling when assertions have been violated.
177         /// The instrumentation can be a performance hit, so this method should not be called if AppVerifier is not enabled.
178         /// </summary>
179         /// <param name="httpApplication">The HttpApplication instance for this request, used to get HttpContext and related items.</param>
180         /// <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>
181         /// <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>
182         /// <param name="errorHandler">The listener that can handle verification failures.</param>
183         /// <returns>The instrumented Begin* method.</returns>
184         internal static Func<AsyncCallback, object, IAsyncResult> WrapBeginMethodImpl(HttpApplication httpApplication, Func<AsyncCallback, object, IAsyncResult> beginMethod, Delegate originalDelegate, Action<AppVerifierException> errorHandler, CallStackCollectionBitMasks callStackMask) {
185             return (callback, state) => {
186                 // basic diagnostic info goes at the top since it's used during generation of the error message
187                 AsyncCallbackInvocationHelper asyncCallbackInvocationHelper = new AsyncCallbackInvocationHelper();
188                 CallStackCollectionBitMasks myBeginMask = callStackMask & CallStackCollectionBitMasks.AllBeginMask;
189                 bool captureBeginStack = (myBeginMask & (CallStackCollectionBitMasks)AppVerifierCollectCallStackMask) == myBeginMask;
190
191                 InvocationInfo beginHandlerInvocationInfo = InvocationInfo.Capture(captureBeginStack);
192                 Uri requestUrl = null;
193                 RequestNotification? currentNotification = null;
194                 bool isPostNotification = false;
195                 Type httpHandlerType = null;
196
197                 // need to collect all this up-front since it might go away during the async operation
198                 if (httpApplication != null) {
199                     HttpContext context = httpApplication.Context;
200                     if (context != null) {
201                         if (!context.HideRequestResponse && context.Request != null) {
202                             requestUrl = context.Request.Unvalidated.Url;
203                         }
204
205                         if (context.NotificationContext != null) {
206                             currentNotification = context.NotificationContext.CurrentNotification;
207                             isPostNotification = context.NotificationContext.IsPostNotification;
208                         }
209
210                         if (context.Handler != null) {
211                             httpHandlerType = context.Handler.GetType();
212                         }
213                     }
214                 }
215
216                 // If the condition passed to this method evaluates to false, we will raise an error to whoever is listening.
217                 AssertDelegate assert = (condition, errorCode) => {
218                     long mask = 1L<<(int)errorCode;
219                     // assert only if it was not masked out by a bit set
220                     bool enableAssert = (AppVerifierErrorCodeEnableAssertMask & mask) == mask;
221
222                     if (!condition && enableAssert) {
223                         // capture the stack only if it was not masked out by a bit set
224                         bool captureStack = (AppVerifierErrorCodeCollectCallStackMask & mask) == mask;
225
226                         InvocationInfo assertInvocationInfo = InvocationInfo.Capture(captureStack);
227
228                         // header
229                         StringBuilder errorString = new StringBuilder();
230                         errorString.AppendLine(FormatErrorString(SR.AppVerifier_Title));
231                         errorString.AppendLine(FormatErrorString(SR.AppVerifier_Subtitle));
232                         errorString.AppendLine();
233
234                         // basic info (about the assert)
235                         errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_URL, requestUrl));
236                         errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_ErrorCode, (int)errorCode));
237                         errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_Description, GetLocalizedDescriptionStringForError(errorCode)));
238                         errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_ThreadInfo, assertInvocationInfo.ThreadId, assertInvocationInfo.Timestamp.ToLocalTime()));
239                         errorString.AppendLine(assertInvocationInfo.StackTrace.ToString());
240
241                         // Begin* method info
242                         errorString.AppendLine(FormatErrorString(SR.AppVerifier_BeginMethodInfo_EntryMethod, PrettyPrintDelegate(originalDelegate)));
243                         if (currentNotification != null) {
244                             errorString.AppendLine(FormatErrorString(SR.AppVerifier_BeginMethodInfo_RequestNotification_Integrated, currentNotification, isPostNotification));
245                         }
246                         else {
247                             errorString.AppendLine(FormatErrorString(SR.AppVerifier_BeginMethodInfo_RequestNotification_NotIntegrated));
248                         }
249                         errorString.AppendLine(FormatErrorString(SR.AppVerifier_BeginMethodInfo_CurrentHandler, httpHandlerType));
250                         errorString.AppendLine(FormatErrorString(SR.AppVerifier_BeginMethodInfo_ThreadInfo, beginHandlerInvocationInfo.ThreadId, beginHandlerInvocationInfo.Timestamp.ToLocalTime()));
251                         errorString.AppendLine(beginHandlerInvocationInfo.StackTrace.ToString());
252
253                         // AsyncCallback info
254                         int totalAsyncInvocationCount;
255                         InvocationInfo firstAsyncInvocation = asyncCallbackInvocationHelper.GetFirstInvocationInfo(out totalAsyncInvocationCount);
256                         errorString.AppendLine(FormatErrorString(SR.AppVerifier_AsyncCallbackInfo_InvocationCount, totalAsyncInvocationCount));
257                         if (firstAsyncInvocation != null) {
258                             errorString.AppendLine(FormatErrorString(SR.AppVerifier_AsyncCallbackInfo_FirstInvocation_ThreadInfo, firstAsyncInvocation.ThreadId, firstAsyncInvocation.Timestamp.ToLocalTime()));
259                             errorString.AppendLine(firstAsyncInvocation.StackTrace.ToString());
260                         }
261
262                         AppVerifierException ex = new AppVerifierException(errorCode, errorString.ToString());
263                         errorHandler(ex);
264                         throw ex;
265                     }
266                 };
267
268                 assert(httpApplication != null, AppVerifierErrorCode.HttpApplicationInstanceWasNull);
269                 assert(originalDelegate != null, AppVerifierErrorCode.BeginHandlerDelegateWasNull);
270
271                 object lockObj = new object(); // used to synchronize access to certain locals which can be touched by multiple threads
272                 IAsyncResult asyncResultReturnedByBeginHandler = null;
273                 IAsyncResult asyncResultPassedToCallback = null;
274                 object beginHandlerReturnValueHolder = null; // used to hold the IAsyncResult returned by or Exception thrown by BeginHandler; see comments on Holder<T> for more info
275                 Thread threadWhichCalledBeginHandler = Thread.CurrentThread; // used to determine whether the callback was invoked synchronously
276                 bool callbackRanToCompletion = false; // don't need to lock when touching this local since it's only read in the synchronous case
277
278                 HttpContext assignedContextUponCallingBeginHandler = httpApplication.Context; // used to determine whether the underlying request disappeared
279
280                 try {
281                     asyncResultReturnedByBeginHandler = beginMethod(
282                        asyncResult => {
283                            try {
284                                CallStackCollectionBitMasks myCallbackMask = callStackMask & CallStackCollectionBitMasks.AllCallbackMask;
285                                bool captureEndCallStack = (myCallbackMask & AppVerifierCollectCallStackMask ) == myCallbackMask;
286                                // The callback must never be called more than once.
287                                int newAsyncCallbackInvocationCount = asyncCallbackInvocationHelper.RecordInvocation(captureEndCallStack);
288                                assert(newAsyncCallbackInvocationCount == 1, AppVerifierErrorCode.AsyncCallbackInvokedMultipleTimes);
289
290                                // The 'asyncResult' parameter must never be null.
291                                assert(asyncResult != null, AppVerifierErrorCode.AsyncCallbackInvokedWithNullParameter);
292
293                                object tempBeginHandlerReturnValueHolder;
294                                Thread tempThreadWhichCalledBeginHandler;
295                                lock (lockObj) {
296                                    asyncResultPassedToCallback = asyncResult;
297                                    tempBeginHandlerReturnValueHolder = beginHandlerReturnValueHolder;
298                                    tempThreadWhichCalledBeginHandler = threadWhichCalledBeginHandler;
299                                }
300
301                                // At this point, 'IsCompleted = true' is mandatory.
302                                assert(asyncResult.IsCompleted, AppVerifierErrorCode.AsyncCallbackGivenAsyncResultWhichWasNotCompleted);
303
304                                if (tempBeginHandlerReturnValueHolder == null) {
305                                    // BeginHandler hasn't yet returned, so this call may be synchronous or asynchronous.
306                                    // We can tell by comparing the current thread with the thread which called BeginHandler.
307                                    // From a correctness perspective, it is valid to invoke the AsyncCallback delegate either
308                                    // synchronously or asynchronously. From Microsoft: if 'CompletedSynchronously = true', then
309                                    // AsyncCallback invocation can happen either on the same thread or on a different thread,
310                                    // just as long as BeginHandler hasn't yet returned (which in true in this case).
311                                    if (!asyncResult.CompletedSynchronously) {
312                                        // If 'CompletedSynchronously = false', we must be on a different thread than the BeginHandler invocation.
313                                        assert(tempThreadWhichCalledBeginHandler != Thread.CurrentThread, AppVerifierErrorCode.AsyncCallbackInvokedSynchronouslyButAsyncResultWasNotMarkedCompletedSynchronously);
314                                    }
315                                }
316                                else {
317                                    // BeginHandler already returned, so this invocation is definitely asynchronous.
318
319                                    Holder<IAsyncResult> asyncResultHolder = tempBeginHandlerReturnValueHolder as Holder<IAsyncResult>;
320                                    if (asyncResultHolder != null) {
321                                        // We need to verify that the IAsyncResult we're given is the same that was returned by BeginHandler
322                                        // and that the IAsyncResult is marked 'CompletedSynchronously = false'.
323                                        assert(asyncResult == asyncResultHolder.Value, AppVerifierErrorCode.AsyncCallbackInvokedWithUnexpectedAsyncResultInstance);
324                                        assert(!asyncResult.CompletedSynchronously, AppVerifierErrorCode.AsyncCallbackInvokedAsynchronouslyButAsyncResultWasMarkedCompletedSynchronously);
325                                    }
326                                    else {
327                                        // If we reached this point, BeginHandler threw an exception.
328                                        // The AsyncCallback should never be invoked if BeginHandler has already failed.
329                                        assert(false, AppVerifierErrorCode.BeginHandlerThrewThenAsyncCallbackInvokedAsynchronously);
330                                    }
331                                }
332
333                                // AsyncState must match the 'state' parameter passed to BeginHandler
334                                assert(asyncResult.AsyncState == state, AppVerifierErrorCode.AsyncCallbackInvokedWithUnexpectedAsyncResultAsyncState);
335
336                                // Make sure the underlying HttpApplication is still assigned to the captured HttpContext instance.
337                                // If not, this AsyncCallback invocation could end up completing *some other request's* operation,
338                                // resulting in data corruption.
339                                assert(assignedContextUponCallingBeginHandler == httpApplication.Context, AppVerifierErrorCode.AsyncCallbackCalledAfterHttpApplicationReassigned);
340                            }
341                            catch (AppVerifierException) {
342                                // We want to ---- any exceptions thrown by our verification logic, as the failure
343                                // has already been recorded by the appropriate listener. Just go straight to
344                                // invoking the callback.
345                            }
346
347                            // all checks complete - delegate control to the actual callback
348                            if (callback != null) {
349                                callback(asyncResult);
350                            }
351                            callbackRanToCompletion = true;
352                        },
353                        state);
354
355                     // The return value must never be null.
356                     assert(asyncResultReturnedByBeginHandler != null, AppVerifierErrorCode.BeginHandlerReturnedNull);
357
358                     lock (lockObj) {
359                         beginHandlerReturnValueHolder = new Holder<IAsyncResult>(asyncResultReturnedByBeginHandler);
360                     }
361
362                     if (asyncResultReturnedByBeginHandler.CompletedSynchronously) {
363                         // If 'CompletedSynchronously = true', the IAsyncResult must be marked 'IsCompleted = true'
364                         // and the AsyncCallback must have been invoked synchronously (checked in the AsyncCallback verification logic).
365                         assert(asyncResultReturnedByBeginHandler.IsCompleted, AppVerifierErrorCode.BeginHandlerReturnedAsyncResultMarkedCompletedSynchronouslyButWhichWasNotCompleted);
366                         assert(asyncCallbackInvocationHelper.TotalInvocations != 0, AppVerifierErrorCode.BeginHandlerReturnedAsyncResultMarkedCompletedSynchronouslyButAsyncCallbackNeverCalled);
367                     }
368
369                     IAsyncResult tempAsyncResultPassedToCallback;
370                     lock (lockObj) {
371                         tempAsyncResultPassedToCallback = asyncResultPassedToCallback;
372                     }
373
374                     // The AsyncCallback may have been invoked (either synchronously or asynchronously). If it has been
375                     // invoked, we need to verify that it was given the same IAsyncResult returned by BeginHandler.
376                     // If the AsyncCallback hasn't yet been called, we skip this check, as the AsyncCallback verification
377                     // logic will eventually perform the check at the appropriate time.
378                     if (tempAsyncResultPassedToCallback != null) {
379                         assert(tempAsyncResultPassedToCallback == asyncResultReturnedByBeginHandler, AppVerifierErrorCode.BeginHandlerReturnedUnexpectedAsyncResultInstance);
380                     }
381
382                     // AsyncState must match the 'state' parameter passed to BeginHandler
383                     assert(asyncResultReturnedByBeginHandler.AsyncState == state, AppVerifierErrorCode.BeginHandlerReturnedUnexpectedAsyncResultAsyncState);
384
385                     // all checks complete
386                     return asyncResultReturnedByBeginHandler;
387                 }
388                 catch (AppVerifierException) {
389                     // We want to ---- any exceptions thrown by our verification logic, as the failure
390                     // has already been recorded by the appropriate listener. Just return the original
391                     // IAsyncResult so that the application continues to run.
392                     return asyncResultReturnedByBeginHandler;
393                 }
394                 catch (Exception ex) {
395                     if (asyncResultReturnedByBeginHandler == null) {
396                         // If we reached this point, an exception was thrown by BeginHandler, so we need to
397                         // record it and rethrow it.
398
399                         IAsyncResult tempAsyncResultPassedToCallback;
400                         lock (lockObj) {
401                             beginHandlerReturnValueHolder = new Holder<Exception>(ex);
402                             tempAsyncResultPassedToCallback = asyncResultPassedToCallback;
403                         }
404
405                         try {
406                             // The AsyncCallback should only be invoked if BeginHandler ran to completion.
407                             if (tempAsyncResultPassedToCallback != null) {
408
409                                 // If AsyncCallback was invoked asynchronously, then by definition it was
410                                 // scheduled prematurely since BeginHandler hadn't yet run to completion
411                                 // (since whatever additional work it did after invoking the callback failed).
412                                 // Therefore it is always wrong for BeginHandler to both throw and
413                                 // asynchronously invoke AsyncCallback.
414                                 assert(tempAsyncResultPassedToCallback.CompletedSynchronously, AppVerifierErrorCode.AsyncCallbackInvokedAsynchronouslyThenBeginHandlerThrew);
415
416                                 // If AsyncCallback was invoked synchronously, then it must have been invoked
417                                 // before BeginHandler surfaced the exception (since otherwise BeginHandler
418                                 // wouldn't have reached the line of code that invoked AsyncCallback). But
419                                 // AsyncCallback itself could have thrown, bubbling the exception up through
420                                 // BeginHandler and back to us. If AsyncCallback ran to completion, then this
421                                 // means BeginHandler did extra work (which failed) after invoking AsyncCallback,
422                                 // so BeginHandler by definition hadn't yet run to completion.
423                                 assert(!callbackRanToCompletion, AppVerifierErrorCode.AsyncCallbackInvokedSynchronouslyThenBeginHandlerThrew);
424                             }
425                         }
426                         catch (AppVerifierException) {
427                             // We want to ---- any exceptions thrown by our verification logic, as the failure
428                             // has already been recorded by the appropriate listener. Propagate the original
429                             // exception upward.
430                         }
431
432                         throw;
433                     }
434                     else {
435                         // We want to ---- any exceptions thrown by our verification logic, as the failure
436                         // has already been recorded by the appropriate listener. Just return the original
437                         // IAsyncResult so that the application continues to run.
438                         return asyncResultReturnedByBeginHandler;
439                     }
440                 }
441                 finally {
442                     // Since our local variables are GC-rooted in an anonymous object, we should
443                     // clear references to objects we no longer need so that the GC can reclaim
444                     // them if appropriate.
445                     lock (lockObj) {
446                         threadWhichCalledBeginHandler = null;
447                     }
448                 }
449             };
450         }
451
452         // Gets a delegate that checks for application code trying to call into the SyncContext after
453         // the request or the request notification is already completed. 
454         // The Action returned by this method could be null.
455         public static Action<bool> GetSyncContextCheckDelegate(ISyncContext syncContext) {
456             if (!IsAppVerifierEnabled) {
457                 return null;
458             }
459
460             return GetSyncContextCheckDelegateImpl(syncContext, HandleAppVerifierException);
461         }
462
463         /// <summary>
464         /// Returns an Action<bool> that determines whether SynchronizationContext.Send or Post was called after the underlying request 
465         /// or the request notification finished. The bool parameter controls whether to check if Post is attempted in nested notification.
466         /// The instrumentation can be a performance hit, so this method should not be called if AppVerifier is not enabled.
467         /// </summary>
468         /// <param name="syncContext">The ISyncContext (HttpApplication, WebSocketPipeline, etc.) on which to perform the check.</param>
469         /// <param name="errorHandler">The listener that can handle verification failures.</param>
470         /// <returns>A callback which performs the verification.</returns>
471         internal static Action<bool> GetSyncContextCheckDelegateImpl(ISyncContext syncContext, Action<AppVerifierException> errorHandler) {
472             Uri requestUrl = null;
473             object originalThreadContextId = null;
474
475             // collect all of the diagnostic information upfront
476             HttpContext originalHttpContext = (syncContext != null) ? syncContext.HttpContext : null;
477             if (originalHttpContext != null) {
478                 if (!originalHttpContext.HideRequestResponse && originalHttpContext.Request != null) {
479                     requestUrl = originalHttpContext.Request.Unvalidated.Url;
480                 }
481
482                 // This will be used as a surrogate for the captured HttpContext so that we don't
483                 // have a long-lived reference to a heavy object graph. See comments on ThreadContextId
484                 // for more info.
485                 originalThreadContextId = originalHttpContext.ThreadContextId;
486                 originalHttpContext = null;
487             }
488
489             // If the condition passed to this method evaluates to false, we will raise an error to whoever is listening.
490             AssertDelegate assert = GetAssertDelegateImpl(requestUrl, errorHandler, appendAdditionalInfoDelegate: null);
491
492             return (checkForReEntry) => {
493                 try {
494                     // Make sure that the ISyncContext is still associated with the same HttpContext that
495                     // we captured earlier.
496                     HttpContext currentHttpContext = (syncContext != null) ? syncContext.HttpContext : null;
497                     object currentThreadContextId = (currentHttpContext != null) ? currentHttpContext.ThreadContextId : null;
498                     assert(currentThreadContextId != null && ReferenceEquals(originalThreadContextId, currentThreadContextId), AppVerifierErrorCode.SyncContextSendOrPostCalledAfterRequestCompleted);
499
500                     if (HttpRuntime.UsingIntegratedPipeline && !currentHttpContext.HasWebSocketRequestTransitionCompleted) {
501                         var notificationContext = (currentHttpContext != null) ? currentHttpContext.NotificationContext : null;
502                         assert(notificationContext != null, AppVerifierErrorCode.SyncContextSendOrPostCalledBetweenNotifications);
503
504                         if (checkForReEntry && notificationContext != null) {
505                             assert(!notificationContext.IsReEntry, AppVerifierErrorCode.SyncContextPostCalledInNestedNotification);
506                         }
507                     }
508                 }
509                 catch (AppVerifierException) {
510                     // We want to ---- any exceptions thrown by our verification logic, as the failure
511                     // has already been recorded by the appropriate listener. Propagate the original
512                     // exception upward.
513                 }
514             };
515         }
516
517         // This generic method invokes a delegate that was created by AppVerifier at an earlier time.
518         // It is safe to call it even if the returned delegate is null (e.g. AppVerifier is off).
519         // Here is the typical usage scenario:
520         //      var verifierCheck = AppVerifier.Get*CheckDelegate(...);         // get the delegate which can capture some state
521         //      T result = <...>                                                // the result of some code execution
522         //      AppVerifier.InvokeVerifierCheck(verifierCheckDelegate, result); // invoke the verification of the result
523         internal static void InvokeVerifierCheck<T>(Action<T> verifierCheckDelegate, T result)
524         {
525             if (verifierCheckDelegate != null) {
526                 try {
527                     verifierCheckDelegate(result);
528                 }
529                 catch (AppVerifierException) {
530                     // We want to ---- any exceptions thrown by our verification logic, as the failure
531                     // has already been recorded by the appropriate listener.
532                 }
533             }
534         }
535
536         // Gets a delegate that checks for inconsistencies after managed code finished processing one or more request notifications.
537         // The Action returned by this method could be null.
538         internal static Action<RequestNotificationStatus> GetRequestNotificationStatusCheckDelegate(HttpContext context, RequestNotification currentNotification, bool isPostNotification) {
539             if (!IsAppVerifierEnabled) {
540                 return null;
541             }
542             return GetRequestNotificationStatusCheckDelegateImpl(context, currentNotification, isPostNotification, HandleAppVerifierException);
543         }
544
545         /// <summary>
546         /// Returns an Action<RequestNotificationStatus> that will check for inconsistencies after 
547         /// managed code has finished processing one or more notifications and about to return back to IIS. 
548         /// </summary>
549         /// <returns>A callback which performs the verification.</returns>
550         internal static Action<RequestNotificationStatus> GetRequestNotificationStatusCheckDelegateImpl(HttpContext context, RequestNotification currentNotification, bool isPostNotification, Action<AppVerifierException> errorHandler) {
551             // collect all of the diagnostic information upfront
552             NotificationContext originalNotificationContext = context.NotificationContext;
553             bool isReentry = originalNotificationContext.IsReEntry;
554             Uri requestUrl = null;
555             if (!context.HideRequestResponse && context.Request != null) {
556                 requestUrl = context.Request.Unvalidated.Url;
557             }
558
559             AppendAdditionalInfoDelegate appendCurrentNotificationInfo = (errorString) => {
560                 errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_NotificationInfo, currentNotification, isPostNotification, isReentry));
561             };
562
563             AssertDelegate assert = GetAssertDelegateImpl(requestUrl, errorHandler, appendAdditionalInfoDelegate: appendCurrentNotificationInfo);
564                 
565             return (RequestNotificationStatus status) => {
566                 if (status == RequestNotificationStatus.Pending) {
567                     // We don't expect nested notifications to complete asynchronously
568                     assert(!isReentry, AppVerifierErrorCode.PendingProcessRequestNotificationStatusAfterCompletingNestedNotification);
569                 }
570                 else {
571                     // Completing synchronously with pending NotificationContext means a 
572
573
574                     assert(context.NotificationContext != null && !context.NotificationContext.PendingAsyncCompletion,
575                             AppVerifierErrorCode.RequestNotificationCompletedSynchronouslyWithNotificationContextPending);
576
577                     // Can't have a different NotificationContext after finishing the notification
578                     // Even if it was changed while processing nested notifications it should be restored back before we unwind
579                     assert(context.NotificationContext == originalNotificationContext,
580                             AppVerifierErrorCode.NotificationContextHasChangedAfterSynchronouslyProcessingNotification);
581                 }
582             };
583         }
584
585         /// <summary>
586         /// This method returns the default implementation of the assert code which takes care of 
587         /// evaluating the assert contition, handing assert and stack collection enabling masks,
588         /// and creating an AppVerifierException with basic information
589         /// </summary>
590         /// <param name="requestUrl">The Url of the request.</param>
591         /// <param name="errorHandler">The listener that can handle verification failures.</param>
592         /// <param name="appendAdditionalInfoDelegate">The caller can provide this delegate to append additional information to the exception. Could be null.</param>
593         /// <returns>A callback which performs the verification.</returns>
594         private static AssertDelegate GetAssertDelegateImpl(Uri requestUrl, Action<AppVerifierException> errorHandler, AppendAdditionalInfoDelegate appendAdditionalInfoDelegate) {
595             // If the condition passed to this method evaluates to false, we will raise an error to whoever is listening.
596             return (condition, errorCode) => {
597                 long mask = 1L << (int)errorCode;
598                 // assert only if it was not masked out by a bit set
599                 bool enableAssert = (AppVerifierErrorCodeEnableAssertMask & mask) == mask;
600
601                 if (!condition && enableAssert) {
602                     // capture the stack only if it was not masked out by a bit set
603                     bool captureStack = (AppVerifierErrorCodeCollectCallStackMask & mask) == mask;
604                     InvocationInfo assertInvocationInfo = InvocationInfo.Capture(captureStack);
605
606                     // header
607                     StringBuilder errorString = new StringBuilder();
608                     errorString.AppendLine(FormatErrorString(SR.AppVerifier_Title));
609                     errorString.AppendLine(FormatErrorString(SR.AppVerifier_Subtitle));
610                     errorString.AppendLine();
611
612                     // basic info (about the assert)
613                     errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_URL, requestUrl));
614                     errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_ErrorCode, (int)errorCode));
615                     errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_Description, GetLocalizedDescriptionStringForError(errorCode)));
616                     errorString.AppendLine(FormatErrorString(SR.AppVerifier_BasicInfo_ThreadInfo, assertInvocationInfo.ThreadId, assertInvocationInfo.Timestamp.ToLocalTime()));
617
618                     // append additional info if needed
619                     if (appendAdditionalInfoDelegate != null) {
620                         appendAdditionalInfoDelegate(errorString);
621                     }
622
623                     // append the stack trace
624                     errorString.AppendLine(assertInvocationInfo.StackTrace.ToString());
625
626                     AppVerifierException ex = new AppVerifierException(errorCode, errorString.ToString());
627                     errorHandler(ex);
628                     throw ex;
629                 }
630             };
631         }
632
633
634         // This is the default implementation of an AppVerifierException handler;
635         // it just delegates to the configured behavior.
636         [SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive", Justification = "Want to keep these locals on the stack to assist with debugging.")]
637         [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
638         private static void HandleAppVerifierException(AppVerifierException ex) {
639             // This method is specifically written to maximize the chance of
640             // useful information being on the stack as a local, where it's more
641             // easily observed by the debugger.
642
643             AppVerifierErrorCode errorCode = ex.ErrorCode;
644             string fullMessage = ex.Message;
645
646             DefaultAppVerifierBehavior(ex);
647
648             GC.KeepAlive(errorCode);
649             GC.KeepAlive(fullMessage);
650             GC.KeepAlive(ex);
651         }
652
653         internal static string PrettyPrintDelegate(Delegate del) {
654             return PrettyPrintMemberInfo((del != null) ? del.Method : null);
655         }
656
657         // prints "TResult MethodName(TArg1, TArg2, ...) [Module.dll!Namespace.TypeName]"
658         internal static string PrettyPrintMemberInfo(MethodInfo method) {
659             if (method == null) {
660                 return null;
661             }
662
663             string retVal = method.ToString();
664
665             Type type = method.ReflectedType;
666             if (type != null) {
667                 retVal = retVal + " [";
668                 if (type.Module != null) {
669                     retVal += type.Module.Name + "!";
670                 }
671
672                 retVal += type.FullName + "]";
673             }
674
675             return retVal;
676         }
677
678         internal static string GetLocalizedDescriptionStringForError(AppVerifierErrorCode errorCode) {
679             return FormatErrorString(_errorStringMappings[errorCode]);
680         }
681
682         // We use InstalledUICulture rather than CurrentCulture / CurrentUICulture since these strings will
683         // be stored in the system event log.
684         [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.IFormatProvider,System.String,System.Object[])",
685             Justification = "Matches culture specified in Misc.WriteUnhandledExceptionToEventLog.")]
686         internal static string FormatErrorString(string name, params object[] args) {
687             return String.Format(CultureInfo.InstalledUICulture, SR.Resources.GetString(name, CultureInfo.InstalledUICulture), args);
688         }
689
690         // contains a counter and invocation information for an AsyncCallback delegate
691         private sealed class AsyncCallbackInvocationHelper {
692             private InvocationInfo _firstInvocationInfo;
693             private int _totalInvocationCount;
694
695             public int TotalInvocations {
696                 [MethodImpl(MethodImplOptions.Synchronized)]
697                 get { return _totalInvocationCount; }
698             }
699
700             [MethodImpl(MethodImplOptions.Synchronized)]
701             public InvocationInfo GetFirstInvocationInfo(out int totalInvocationCount) {
702                 totalInvocationCount = _totalInvocationCount;
703                 return _firstInvocationInfo;
704             }
705
706             [MethodImpl(MethodImplOptions.Synchronized)]
707             public int RecordInvocation(bool captureCallStack) {
708                 _totalInvocationCount++;
709                 if (_firstInvocationInfo == null) {
710                     _firstInvocationInfo = InvocationInfo.Capture(captureCallStack);
711                 }
712                 return _totalInvocationCount;
713             }
714         }
715
716         // We use a special class for holding data so that we can store the local's
717         // intended type alongside its real value. Prevents us from misinterpreting
718         // the degenerate case of "----CustomType : Exception, IAsyncResult" so that
719         // we know whether it was returned as an IAsyncResult or thrown as an Exception.
720         private sealed class Holder<T> {
721             public readonly T Value;
722
723             public Holder(T value) {
724                 Value = value;
725             }
726         }
727
728         // holds diagnostic information about a particular invocation
729         private sealed class InvocationInfo {
730             public readonly int ThreadId;
731             public readonly DateTimeOffset Timestamp;
732             public readonly string StackTrace;
733
734             private InvocationInfo(bool captureStack) {
735                 ThreadId = Thread.CurrentThread.ManagedThreadId;
736                 Timestamp = DateTimeOffset.UtcNow; // UTC is faster, will convert to local on error
737                 StackTrace = captureStack? CaptureStackTrace(): "n/a";
738             }
739
740             public static InvocationInfo Capture(bool captureStack) {
741                 return new InvocationInfo(captureStack);
742             }
743
744             // captures a stack trace, removing AppVerifier.* frames from the top of the stack to minimize noise
745             private static string CaptureStackTrace() {
746                 StackTrace fullStackTrace = new StackTrace(fNeedFileInfo: true);
747                 string[] traceLines = fullStackTrace.ToString().Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
748                 for (int i = 0; i < fullStackTrace.FrameCount && i < traceLines.Length; i++) {
749                     StackFrame thisFrame = fullStackTrace.GetFrame(i);
750                     if (thisFrame.GetMethod().Module == typeof(AppVerifier).Module
751                         && thisFrame.GetMethod().DeclaringType.FullName.StartsWith("System.Web.Util.AppVerifier", StringComparison.Ordinal)) {
752                         // we want to skip this frame since it's an AppVerifier.* frame
753                         continue;
754                     }
755                     else {
756                         // this is the first frame that is not an AppVerifier.* frame, so start the stack trace from here
757                         return String.Join(Environment.NewLine, traceLines.Skip(i));
758                     }
759                 }
760
761                 // if we reached this point, not sure what happened, so just return the original stack trace
762                 return fullStackTrace.ToString();
763             }
764         }
765
766         [SuppressUnmanagedCodeSecurityAttribute]
767         private static class NativeMethods {
768             [DllImport("kernel32.dll")]
769             internal extern static void DebugBreak();
770         }
771
772     }
773 }