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 }
,
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 }
,
48 // Provides an option for different wrappers to specify whether to collect the call stacks traces
50 internal enum CallStackCollectionBitMasks
: int {
53 // used for a 3-parameter Begin* method [(T, AsyncCallback, object) -> IAsyncResult] wrapper
54 BeginCallHandlerMask
= 1,
55 CallHandlerCallbackMask
= 2,
57 // used for a BeginEventHandler method [(object, sender, EventArgs, object) -> IAsyncResult] wrapper
58 BeginExecutionStepMask
= 4,
59 ExecutionStepCallbackMask
= 8,
61 // when adding new bits above also update the following:
62 AllHandlerMask
= BeginCallHandlerMask
| CallHandlerCallbackMask
,
63 AllStepMask
= BeginExecutionStepMask
| ExecutionStepCallbackMask
,
65 AllBeginMask
= BeginCallHandlerMask
| BeginExecutionStepMask
,
66 AllCallbackMask
= CallHandlerCallbackMask
| ExecutionStepCallbackMask
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
;
76 private delegate void AssertDelegate(bool condition
, AppVerifierErrorCode errorCode
);
77 private delegate void AppendAdditionalInfoDelegate(StringBuilder errorString
);
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;
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);
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);
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));
96 switch (valueFromRegistry
) {
98 // Just write to the event log
99 return WriteToEventLog
;
102 // Write to the event log and Debugger.Launch / Debugger.Break
103 return WriteToEventLogAndSoftBreak
;
106 // Write to the event log and kernel32!DebugBreak
107 return WriteToEventLogAndHardBreak
;
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
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.
124 if (Debugger
.Launch()) {
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.)
134 if (Debugger
.IsAttached
) {
138 NativeMethods
.DebugBreak();
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
;
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
)
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
;
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
)
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.
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
;
191 InvocationInfo beginHandlerInvocationInfo
= InvocationInfo
.Capture(captureBeginStack
);
192 string requestUrl
= null;
193 RequestNotification
? currentNotification
= null;
194 bool isPostNotification
= false;
195 Type httpHandlerType
= null;
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
= TryGetRequestUrl(context
);
205 if (context
.NotificationContext
!= null) {
206 currentNotification
= context
.NotificationContext
.CurrentNotification
;
207 isPostNotification
= context
.NotificationContext
.IsPostNotification
;
210 if (context
.Handler
!= null) {
211 httpHandlerType
= context
.Handler
.GetType();
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
;
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
;
226 InvocationInfo assertInvocationInfo
= InvocationInfo
.Capture(captureStack
);
229 StringBuilder errorString
= new StringBuilder();
230 errorString
.AppendLine(FormatErrorString(SR
.AppVerifier_Title
));
231 errorString
.AppendLine(FormatErrorString(SR
.AppVerifier_Subtitle
));
232 errorString
.AppendLine();
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());
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
));
247 errorString
.AppendLine(FormatErrorString(SR
.AppVerifier_BeginMethodInfo_RequestNotification_NotIntegrated
));
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());
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());
262 AppVerifierException ex
= new AppVerifierException(errorCode
, errorString
.ToString());
268 assert(httpApplication
!= null, AppVerifierErrorCode
.HttpApplicationInstanceWasNull
);
269 assert(originalDelegate
!= null, AppVerifierErrorCode
.BeginHandlerDelegateWasNull
);
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
278 HttpContext assignedContextUponCallingBeginHandler
= httpApplication
.Context
; // used to determine whether the underlying request disappeared
281 asyncResultReturnedByBeginHandler
= beginMethod(
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
);
290 // The 'asyncResult' parameter must never be null.
291 assert(asyncResult
!= null, AppVerifierErrorCode
.AsyncCallbackInvokedWithNullParameter
);
293 object tempBeginHandlerReturnValueHolder
;
294 Thread tempThreadWhichCalledBeginHandler
;
296 asyncResultPassedToCallback
= asyncResult
;
297 tempBeginHandlerReturnValueHolder
= beginHandlerReturnValueHolder
;
298 tempThreadWhichCalledBeginHandler
= threadWhichCalledBeginHandler
;
301 // At this point, 'IsCompleted = true' is mandatory.
302 assert(asyncResult
.IsCompleted
, AppVerifierErrorCode
.AsyncCallbackGivenAsyncResultWhichWasNotCompleted
);
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
);
317 // BeginHandler already returned, so this invocation is definitely asynchronous.
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
);
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
);
333 // AsyncState must match the 'state' parameter passed to BeginHandler
334 assert(asyncResult
.AsyncState
== state
, AppVerifierErrorCode
.AsyncCallbackInvokedWithUnexpectedAsyncResultAsyncState
);
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
);
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.
347 // all checks complete - delegate control to the actual callback
348 if (callback
!= null) {
349 callback(asyncResult
);
351 callbackRanToCompletion
= true;
355 // The return value must never be null.
356 assert(asyncResultReturnedByBeginHandler
!= null, AppVerifierErrorCode
.BeginHandlerReturnedNull
);
359 beginHandlerReturnValueHolder
= new Holder
<IAsyncResult
>(asyncResultReturnedByBeginHandler
);
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
);
369 IAsyncResult tempAsyncResultPassedToCallback
;
371 tempAsyncResultPassedToCallback
= asyncResultPassedToCallback
;
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
);
382 // AsyncState must match the 'state' parameter passed to BeginHandler
383 assert(asyncResultReturnedByBeginHandler
.AsyncState
== state
, AppVerifierErrorCode
.BeginHandlerReturnedUnexpectedAsyncResultAsyncState
);
385 // all checks complete
386 return asyncResultReturnedByBeginHandler
;
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
;
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.
399 IAsyncResult tempAsyncResultPassedToCallback
;
401 beginHandlerReturnValueHolder
= new Holder
<Exception
>(ex
);
402 tempAsyncResultPassedToCallback
= asyncResultPassedToCallback
;
406 // The AsyncCallback should only be invoked if BeginHandler ran to completion.
407 if (tempAsyncResultPassedToCallback
!= null) {
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
);
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
);
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
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
;
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.
446 threadWhichCalledBeginHandler
= null;
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
) {
460 return GetSyncContextCheckDelegateImpl(syncContext
, HandleAppVerifierException
);
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.
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 string requestUrl
= null;
473 object originalThreadContextId
= null;
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
= TryGetRequestUrl(originalHttpContext
);
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
485 originalThreadContextId
= originalHttpContext
.ThreadContextId
;
486 originalHttpContext
= null;
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);
492 return (checkForReEntry
) => {
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
);
500 if (HttpRuntime
.UsingIntegratedPipeline
&& !currentHttpContext
.HasWebSocketRequestTransitionCompleted
) {
501 var notificationContext
= (currentHttpContext
!= null) ? currentHttpContext
.NotificationContext
: null;
502 assert(notificationContext
!= null, AppVerifierErrorCode
.SyncContextSendOrPostCalledBetweenNotifications
);
504 if (checkForReEntry
&& notificationContext
!= null) {
505 assert(!notificationContext
.IsReEntry
, AppVerifierErrorCode
.SyncContextPostCalledInNestedNotification
);
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
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
)
525 if (verifierCheckDelegate
!= null) {
527 verifierCheckDelegate(result
);
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.
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
) {
542 return GetRequestNotificationStatusCheckDelegateImpl(context
, currentNotification
, isPostNotification
, HandleAppVerifierException
);
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.
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 string requestUrl
= null;
555 if (!context
.HideRequestResponse
&& context
.Request
!= null) {
556 requestUrl
= TryGetRequestUrl(context
);
559 AppendAdditionalInfoDelegate appendCurrentNotificationInfo
= (errorString
) => {
560 errorString
.AppendLine(FormatErrorString(SR
.AppVerifier_BasicInfo_NotificationInfo
, currentNotification
, isPostNotification
, isReentry
));
563 AssertDelegate assert
= GetAssertDelegateImpl(requestUrl
, errorHandler
, appendAdditionalInfoDelegate
: appendCurrentNotificationInfo
);
565 return (RequestNotificationStatus status
) => {
566 if (status
== RequestNotificationStatus
.Pending
) {
567 // We don't expect nested notifications to complete asynchronously
568 assert(!isReentry
, AppVerifierErrorCode
.PendingProcessRequestNotificationStatusAfterCompletingNestedNotification
);
571 // Completing synchronously with pending NotificationContext means a bug in either user code or the pipeline.
572 // NotificationContext being null means we already completed asynchronously before completing synchronously.
573 // Both cases indicate that we have some async operations we failed to account for.
574 assert(context
.NotificationContext
!= null && !context
.NotificationContext
.PendingAsyncCompletion
,
575 AppVerifierErrorCode
.RequestNotificationCompletedSynchronouslyWithNotificationContextPending
);
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
);
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
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(string 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
;
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
);
607 StringBuilder errorString
= new StringBuilder();
608 errorString
.AppendLine(FormatErrorString(SR
.AppVerifier_Title
));
609 errorString
.AppendLine(FormatErrorString(SR
.AppVerifier_Subtitle
));
610 errorString
.AppendLine();
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()));
618 // append additional info if needed
619 if (appendAdditionalInfoDelegate
!= null) {
620 appendAdditionalInfoDelegate(errorString
);
623 // append the stack trace
624 errorString
.AppendLine(assertInvocationInfo
.StackTrace
.ToString());
626 AppVerifierException ex
= new AppVerifierException(errorCode
, errorString
.ToString());
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.
643 AppVerifierErrorCode errorCode
= ex
.ErrorCode
;
644 string fullMessage
= ex
.Message
;
646 DefaultAppVerifierBehavior(ex
);
648 GC
.KeepAlive(errorCode
);
649 GC
.KeepAlive(fullMessage
);
653 private static string TryGetRequestUrl(HttpContext context
) {
655 return context
.Request
.EnsureRawUrl();
657 catch (HttpException
) {
662 internal static string PrettyPrintDelegate(Delegate del
) {
663 return PrettyPrintMemberInfo((del
!= null) ? del
.Method
: null);
666 // prints "TResult MethodName(TArg1, TArg2, ...) [Module.dll!Namespace.TypeName]"
667 internal static string PrettyPrintMemberInfo(MethodInfo method
) {
668 if (method
== null) {
672 string retVal
= method
.ToString();
674 Type type
= method
.ReflectedType
;
676 retVal
= retVal
+ " [";
677 if (type
.Module
!= null) {
678 retVal
+= type
.Module
.Name
+ "!";
681 retVal
+= type
.FullName
+ "]";
687 internal static string GetLocalizedDescriptionStringForError(AppVerifierErrorCode errorCode
) {
688 return FormatErrorString(_errorStringMappings
[errorCode
]);
691 // We use InstalledUICulture rather than CurrentCulture / CurrentUICulture since these strings will
692 // be stored in the system event log.
693 [SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId
= "System.String.Format(System.IFormatProvider,System.String,System.Object[])",
694 Justification
= "Matches culture specified in Misc.WriteUnhandledExceptionToEventLog.")]
695 internal static string FormatErrorString(string name
, params object[] args
) {
696 return String
.Format(CultureInfo
.InstalledUICulture
, SR
.Resources
.GetString(name
, CultureInfo
.InstalledUICulture
), args
);
699 // contains a counter and invocation information for an AsyncCallback delegate
700 private sealed class AsyncCallbackInvocationHelper
{
701 private InvocationInfo _firstInvocationInfo
;
702 private int _totalInvocationCount
;
704 public int TotalInvocations
{
705 [MethodImpl(MethodImplOptions
.Synchronized
)]
706 get { return _totalInvocationCount; }
709 [MethodImpl(MethodImplOptions
.Synchronized
)]
710 public InvocationInfo
GetFirstInvocationInfo(out int totalInvocationCount
) {
711 totalInvocationCount
= _totalInvocationCount
;
712 return _firstInvocationInfo
;
715 [MethodImpl(MethodImplOptions
.Synchronized
)]
716 public int RecordInvocation(bool captureCallStack
) {
717 _totalInvocationCount
++;
718 if (_firstInvocationInfo
== null) {
719 _firstInvocationInfo
= InvocationInfo
.Capture(captureCallStack
);
721 return _totalInvocationCount
;
725 // We use a special class for holding data so that we can store the local's
726 // intended type alongside its real value. Prevents us from misinterpreting
727 // the degenerate case of "----CustomType : Exception, IAsyncResult" so that
728 // we know whether it was returned as an IAsyncResult or thrown as an Exception.
729 private sealed class Holder
<T
> {
730 public readonly T Value
;
732 public Holder(T
value) {
737 // holds diagnostic information about a particular invocation
738 private sealed class InvocationInfo
{
739 public readonly int ThreadId
;
740 public readonly DateTimeOffset Timestamp
;
741 public readonly string StackTrace
;
743 private InvocationInfo(bool captureStack
) {
744 ThreadId
= Thread
.CurrentThread
.ManagedThreadId
;
745 Timestamp
= DateTimeOffset
.UtcNow
; // UTC is faster, will convert to local on error
746 StackTrace
= captureStack
? CaptureStackTrace(): "n/a";
749 public static InvocationInfo
Capture(bool captureStack
) {
750 return new InvocationInfo(captureStack
);
753 // captures a stack trace, removing AppVerifier.* frames from the top of the stack to minimize noise
754 private static string CaptureStackTrace() {
755 StackTrace fullStackTrace
= new StackTrace(fNeedFileInfo
: true);
756 string[] traceLines
= fullStackTrace
.ToString().Split(new string[] { Environment.NewLine }
, StringSplitOptions
.None
);
757 for (int i
= 0; i
< fullStackTrace
.FrameCount
&& i
< traceLines
.Length
; i
++) {
758 StackFrame thisFrame
= fullStackTrace
.GetFrame(i
);
759 if (thisFrame
.GetMethod().Module
== typeof(AppVerifier
).Module
760 && thisFrame
.GetMethod().DeclaringType
.FullName
.StartsWith("System.Web.Util.AppVerifier", StringComparison
.Ordinal
)) {
761 // we want to skip this frame since it's an AppVerifier.* frame
765 // this is the first frame that is not an AppVerifier.* frame, so start the stack trace from here
766 return String
.Join(Environment
.NewLine
, traceLines
.Skip(i
));
770 // if we reached this point, not sure what happened, so just return the original stack trace
771 return fullStackTrace
.ToString();
775 [SuppressUnmanagedCodeSecurityAttribute
]
776 private static class NativeMethods
{
777 [DllImport("kernel32.dll")]
778 internal extern static void DebugBreak();