1 namespace System.Web.Util {
3 using System.Collections.Generic;
4 using System.Diagnostics;
5 using System.Diagnostics.CodeAnalysis;
6 using System.Globalization;
8 using System.Reflection;
9 using System.Runtime.CompilerServices;
10 using System.Runtime.InteropServices;
11 using System.Security;
12 using System.Security.Permissions;
14 using System.Threading;
17 internal static class AppVerifier {
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 },
43 // Provides an option for different wrappers to specify whether to collect the call stacks traces
45 internal enum CallStackCollectionBitMasks : int {
48 // used for a 3-parameter Begin* method [(T, AsyncCallback, object) -> IAsyncResult] wrapper
49 BeginCallHandlerMask = 1,
50 CallHandlerCallbackMask = 2,
52 // used for a BeginEventHandler method [(object, sender, EventArgs, object) -> IAsyncResult] wrapper
53 BeginExecutionStepMask = 4,
54 ExecutionStepCallbackMask = 8,
56 // when adding new bits above also update the following:
57 AllHandlerMask = BeginCallHandlerMask | CallHandlerCallbackMask,
58 AllStepMask = BeginExecutionStepMask | ExecutionStepCallbackMask,
60 AllBeginMask = BeginCallHandlerMask | BeginExecutionStepMask,
61 AllCallbackMask = CallHandlerCallbackMask | ExecutionStepCallbackMask
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;
71 private delegate void AssertDelegate(bool condition, AppVerifierErrorCode errorCode);
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;
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);
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);
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));
90 switch (valueFromRegistry) {
92 // Just write to the event log
93 return WriteToEventLog;
96 // Write to the event log and Debugger.Launch / Debugger.Break
97 return WriteToEventLogAndSoftBreak;
100 // Write to the event log and kernel32!DebugBreak
101 return WriteToEventLogAndHardBreak;
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
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.
118 if (Debugger.Launch()) {
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.)
128 if (Debugger.IsAttached) {
132 NativeMethods.DebugBreak();
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;
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)
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;
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)
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.
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;
185 InvocationInfo beginHandlerInvocationInfo = InvocationInfo.Capture(captureBeginStack);
186 Uri requestUrl = null;
187 RequestNotification? currentNotification = null;
188 bool isPostNotification = false;
189 Type httpHandlerType = null;
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;
199 if (context.NotificationContext != null) {
200 currentNotification = context.NotificationContext.CurrentNotification;
201 isPostNotification = context.NotificationContext.IsPostNotification;
204 if (context.Handler != null) {
205 httpHandlerType = context.Handler.GetType();
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;
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;
220 InvocationInfo assertInvocationInfo = InvocationInfo.Capture(captureStack);
223 StringBuilder errorString = new StringBuilder();
224 errorString.AppendLine(FormatErrorString(SR.AppVerifier_Title));
225 errorString.AppendLine(FormatErrorString(SR.AppVerifier_Subtitle));
226 errorString.AppendLine();
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());
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));
241 errorString.AppendLine(FormatErrorString(SR.AppVerifier_BeginMethodInfo_RequestNotification_NotIntegrated));
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());
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());
256 AppVerifierException ex = new AppVerifierException(errorCode, errorString.ToString());
262 assert(httpApplication != null, AppVerifierErrorCode.HttpApplicationInstanceWasNull);
263 assert(originalDelegate != null, AppVerifierErrorCode.BeginHandlerDelegateWasNull);
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
272 HttpContext assignedContextUponCallingBeginHandler = httpApplication.Context; // used to determine whether the underlying request disappeared
275 asyncResultReturnedByBeginHandler = beginMethod(
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);
284 // The 'asyncResult' parameter must never be null.
285 assert(asyncResult != null, AppVerifierErrorCode.AsyncCallbackInvokedWithNullParameter);
287 object tempBeginHandlerReturnValueHolder;
288 Thread tempThreadWhichCalledBeginHandler;
290 asyncResultPassedToCallback = asyncResult;
291 tempBeginHandlerReturnValueHolder = beginHandlerReturnValueHolder;
292 tempThreadWhichCalledBeginHandler = threadWhichCalledBeginHandler;
295 // At this point, 'IsCompleted = true' is mandatory.
296 assert(asyncResult.IsCompleted, AppVerifierErrorCode.AsyncCallbackGivenAsyncResultWhichWasNotCompleted);
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);
311 // BeginHandler already returned, so this invocation is definitely asynchronous.
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);
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);
327 // AsyncState must match the 'state' parameter passed to BeginHandler
328 assert(asyncResult.AsyncState == state, AppVerifierErrorCode.AsyncCallbackInvokedWithUnexpectedAsyncResultAsyncState);
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);
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.
341 // all checks complete - delegate control to the actual callback
342 if (callback != null) {
343 callback(asyncResult);
345 callbackRanToCompletion = true;
349 // The return value must never be null.
350 assert(asyncResultReturnedByBeginHandler != null, AppVerifierErrorCode.BeginHandlerReturnedNull);
353 beginHandlerReturnValueHolder = new Holder<IAsyncResult>(asyncResultReturnedByBeginHandler);
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);
363 IAsyncResult tempAsyncResultPassedToCallback;
365 tempAsyncResultPassedToCallback = asyncResultPassedToCallback;
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);
376 // AsyncState must match the 'state' parameter passed to BeginHandler
377 assert(asyncResultReturnedByBeginHandler.AsyncState == state, AppVerifierErrorCode.BeginHandlerReturnedUnexpectedAsyncResultAsyncState);
379 // all checks complete
380 return asyncResultReturnedByBeginHandler;
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;
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.
393 IAsyncResult tempAsyncResultPassedToCallback;
395 beginHandlerReturnValueHolder = new Holder<Exception>(ex);
396 tempAsyncResultPassedToCallback = asyncResultPassedToCallback;
400 // The AsyncCallback should only be invoked if BeginHandler ran to completion.
401 if (tempAsyncResultPassedToCallback != null) {
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);
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);
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
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;
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.
440 threadWhichCalledBeginHandler = null;
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) {
453 return GetSyncContextCheckDelegateImpl(syncContext, HandleAppVerifierException);
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.
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;
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;
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
477 originalThreadContextId = originalHttpContext.ThreadContextId;
478 originalHttpContext = null;
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;
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);
493 StringBuilder errorString = new StringBuilder();
494 errorString.AppendLine(FormatErrorString(SR.AppVerifier_Title));
495 errorString.AppendLine(FormatErrorString(SR.AppVerifier_Subtitle));
496 errorString.AppendLine();
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());
505 AppVerifierException ex = new AppVerifierException(errorCode, errorString.ToString());
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);
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
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.
536 AppVerifierErrorCode errorCode = ex.ErrorCode;
537 string fullMessage = ex.Message;
539 DefaultAppVerifierBehavior(ex);
541 GC.KeepAlive(errorCode);
542 GC.KeepAlive(fullMessage);
546 internal static string PrettyPrintDelegate(Delegate del) {
547 return PrettyPrintMemberInfo((del != null) ? del.Method : null);
550 // prints "TResult MethodName(TArg1, TArg2, ...) [Module.dll!Namespace.TypeName]"
551 internal static string PrettyPrintMemberInfo(MethodInfo method) {
552 if (method == null) {
556 string retVal = method.ToString();
558 Type type = method.ReflectedType;
560 retVal = retVal + " [";
561 if (type.Module != null) {
562 retVal += type.Module.Name + "!";
565 retVal += type.FullName + "]";
571 internal static string GetLocalizedDescriptionStringForError(AppVerifierErrorCode errorCode) {
572 return FormatErrorString(_errorStringMappings[errorCode]);
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);
583 // contains a counter and invocation information for an AsyncCallback delegate
584 private sealed class AsyncCallbackInvocationHelper {
585 private InvocationInfo _firstInvocationInfo;
586 private int _totalInvocationCount;
588 public int TotalInvocations {
589 [MethodImpl(MethodImplOptions.Synchronized)]
590 get { return _totalInvocationCount; }
593 [MethodImpl(MethodImplOptions.Synchronized)]
594 public InvocationInfo GetFirstInvocationInfo(out int totalInvocationCount) {
595 totalInvocationCount = _totalInvocationCount;
596 return _firstInvocationInfo;
599 [MethodImpl(MethodImplOptions.Synchronized)]
600 public int RecordInvocation(bool captureCallStack) {
601 _totalInvocationCount++;
602 if (_firstInvocationInfo == null) {
603 _firstInvocationInfo = InvocationInfo.Capture(captureCallStack);
605 return _totalInvocationCount;
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;
616 public Holder(T value) {
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;
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";
633 public static InvocationInfo Capture(bool captureStack) {
634 return new InvocationInfo(captureStack);
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
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));
654 // if we reached this point, not sure what happened, so just return the original stack trace
655 return fullStackTrace.ToString();
659 [SuppressUnmanagedCodeSecurityAttribute]
660 private static class NativeMethods {
661 [DllImport("kernel32.dll")]
662 internal extern static void DebugBreak();