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