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