diff options
Diffstat (limited to 'src/inc/contract.inl')
-rw-r--r-- | src/inc/contract.inl | 749 |
1 files changed, 749 insertions, 0 deletions
diff --git a/src/inc/contract.inl b/src/inc/contract.inl new file mode 100644 index 0000000000..07d25fbe74 --- /dev/null +++ b/src/inc/contract.inl @@ -0,0 +1,749 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +// --------------------------------------------------------------------------- +// Contract.inl +// + +// ! I am the owner for issues in the contract *infrastructure*, not for every +// ! CONTRACT_VIOLATION dialog that comes up. If you interrupt my work for a routine +// ! CONTRACT_VIOLATION, you will become the new owner of this file. +// --------------------------------------------------------------------------- + +#ifndef CONTRACT_INL_ +#define CONTRACT_INL_ + +#include "contract.h" +#include <string.h> + +#ifndef _countof +#define _countof(x) (sizeof(x)/sizeof(x[0])) +#endif + +#ifdef ENABLE_CONTRACTS_IMPL + +#ifdef FEATURE_STACK_PROBE +/* FLAG to turn on/off dynamic SO Contract checking */ +extern BOOL g_EnableDefaultRWValidation; + +/* Used to report any code with SO_NOT_MAINLINE being run in a test environment + * with COMPLUS_NO_SO_NOT_MAINLINE enabled + */ +void SONotMainlineViolation(const char *szFunction, const char *szFile, int lineNum); + +/* Wrapper over SOTolerantViolation(). Used to report SO_Intolerant functions being called + * from SO_tolerant funcs without stack probing. + */ +void SoTolerantViolationHelper(const char *szFunction, + const char *szFile, + int lineNum); +#endif + + +inline void BaseContract::DoChecks(UINT testmask, __in_z const char *szFunction, __in_z char *szFile, int lineNum) +{ + STATIC_CONTRACT_DEBUG_ONLY; + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + // Cache the pointer to our ClrDebugState if it's not already cached. + // Derived types could set up this ptr before calling BaseContract::DoChecks if they have access to the Thread ptr + if (m_pClrDebugState == NULL) + { + m_pClrDebugState = GetClrDebugState(); + } + + // Save the incoming contents for restoration in the destructor + m_IncomingClrDebugState = *m_pClrDebugState; + + m_testmask = testmask; // Save the testmask for destructor + + // Setup the new stack record. + m_contractStackRecord.m_szFunction = szFunction; + m_contractStackRecord.m_szFile = szFile; + m_contractStackRecord.m_lineNum = lineNum; + m_contractStackRecord.m_testmask = testmask; + m_contractStackRecord.m_construct = "CONTRACT"; + + // Link the new ContractStackRecord into the chain for this thread. + m_pClrDebugState->LinkContractStackTrace( &m_contractStackRecord ); + + if (testmask & DEBUG_ONLY_Yes) + { + m_pClrDebugState->SetDebugOnly(); + } + +#ifdef FEATURE_STACK_PROBE //Dynamic SO contract checks only required when SO infrastructure is present. + + if (testmask & SO_MAINLINE_No) + { + static DWORD dwCheckNotMainline = -1; + + // Some tests should never hit an SO_NOT_MAINLINE contract + if (dwCheckNotMainline == -1) + dwCheckNotMainline = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_NO_SO_NOT_MAINLINE); + + + if (dwCheckNotMainline) + { + SONotMainlineViolation(m_contractStackRecord.m_szFunction, + m_contractStackRecord.m_szFile, + m_contractStackRecord.m_lineNum); + } + + m_pClrDebugState->SetSONotMainline(); + } + +#endif // FEATURE_STACK_PROBE + + switch (testmask & FAULT_Mask) + { + case FAULT_Forbid: + m_pClrDebugState->ViolationMaskReset( FaultViolation|FaultNotFatal ); + m_pClrDebugState->SetFaultForbid(); + break; + + case FAULT_Inject: + if (m_pClrDebugState->IsFaultForbid() && + !(m_pClrDebugState->ViolationMask() & (FaultViolation|FaultNotFatal|BadDebugState))) + { + CONTRACT_ASSERT("INJECT_FAULT called in a FAULTFORBID region.", + BaseContract::FAULT_Forbid, + BaseContract::FAULT_Mask, + m_contractStackRecord.m_szFunction, + m_contractStackRecord.m_szFile, + m_contractStackRecord.m_lineNum); + } + break; + + case FAULT_Disabled: + // Nothing + break; + + default: + UNREACHABLE(); + } + + switch (testmask & THROWS_Mask) + { + case THROWS_Yes: + m_pClrDebugState->CheckOkayToThrow(m_contractStackRecord.m_szFunction, + m_contractStackRecord.m_szFile, + m_contractStackRecord.m_lineNum); + break; + + case THROWS_No: + m_pClrDebugState->ViolationMaskReset( ThrowsViolation ); + m_pClrDebugState->ResetOkToThrow(); + break; + + case THROWS_Disabled: + // Nothing + break; + + default: + UNREACHABLE(); + } + + // LOADS_TYPE check + switch (testmask & LOADS_TYPE_Mask) + { + case LOADS_TYPE_Disabled: + // Nothing + break; + + default: + { + UINT newTypeLoadLevel = ((testmask & LOADS_TYPE_Mask) >> LOADS_TYPE_Shift) - 1; + if (newTypeLoadLevel > m_pClrDebugState->GetMaxLoadTypeLevel()) + { + if (!((LoadsTypeViolation|BadDebugState) & m_pClrDebugState->ViolationMask())) + { + CONTRACT_ASSERT("A function tried to load a type past the current level limit.", + (m_pClrDebugState->GetMaxLoadTypeLevel() + 1) << LOADS_TYPE_Shift, + Contract::LOADS_TYPE_Mask, + m_contractStackRecord.m_szFunction, + m_contractStackRecord.m_szFile, + m_contractStackRecord.m_lineNum + ); + } + } + m_pClrDebugState->SetMaxLoadTypeLevel(newTypeLoadLevel); + m_pClrDebugState->ViolationMaskReset(LoadsTypeViolation); + + } + break; + } + +#ifdef FEATURE_STACK_PROBE + + switch (testmask & SO_TOLERANCE_Mask) + { + case SO_TOLERANT_No: + if (g_EnableDefaultRWValidation) + { + m_pClrDebugState->CheckIfSOIntolerantOK(m_contractStackRecord.m_szFunction, + m_contractStackRecord.m_szFile, + m_contractStackRecord.m_lineNum); + } + break; + + case SO_TOLERANT_Yes: + case SO_TOLERANCE_Disabled: + // Nothing + break; + + default: + UNREACHABLE(); + } + +#endif // FEATURE_STACK_PROBE + + if (testmask & CAN_RETAKE_LOCK_No) + { + m_pClrDebugState->OnEnterCannotRetakeLockFunction(); + m_pClrDebugState->ResetOkToRetakeLock(); + } + + switch (testmask & CAN_TAKE_LOCK_Mask) + { + case CAN_TAKE_LOCK_Yes: + m_pClrDebugState->CheckOkayToLock(m_contractStackRecord.m_szFunction, + m_contractStackRecord.m_szFile, + m_contractStackRecord.m_lineNum); + break; + + case CAN_TAKE_LOCK_No: + m_pClrDebugState->ViolationMaskReset(TakesLockViolation); + m_pClrDebugState->ResetOkToLock(); + break; + + case CAN_TAKE_LOCK_Disabled: + // Nothing + break; + + default: + UNREACHABLE(); + } + +} + +FORCEINLINE BOOL BaseContract::CheckFaultInjection() +{ + // ??? use m_tag to see if we should trigger an injection + return FALSE; +} + +inline BOOL ClrDebugState::CheckOkayToThrowNoAssert() +{ + if (!IsOkToThrow() && !(m_violationmask & (ThrowsViolation|BadDebugState))) + { + return FALSE; + } + return TRUE; +} + +inline void ClrDebugState::CheckOkayToThrow(__in_z const char *szFunction, __in_z const char *szFile, int lineNum) +{ + STATIC_CONTRACT_DEBUG_ONLY; + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + if (!CheckOkayToThrowNoAssert()) + { + CONTRACT_ASSERT("THROWS called in a NOTHROW region.", + BaseContract::THROWS_No, + BaseContract::THROWS_Mask, + szFunction, + szFile, + lineNum); + } +} + +inline BOOL ClrDebugState::CheckOkayToLockNoAssert() +{ + STATIC_CONTRACT_DEBUG_ONLY; + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + if (!IsOkToLock() && !(m_violationmask & (TakesLockViolation|BadDebugState))) + { + return FALSE; + } + return TRUE; +} + +inline void ClrDebugState::CheckOkayToLock(__in_z const char *szFunction, __in_z const char *szFile, int lineNum) +{ + STATIC_CONTRACT_DEBUG_ONLY; + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + if (!CheckOkayToLockNoAssert()) + { + + CONTRACT_ASSERT("CAN_TAKE_LOCK called in a CANNOT_TAKE_LOCK region.", + BaseContract::CAN_TAKE_LOCK_No, + BaseContract::CAN_TAKE_LOCK_Mask, + szFunction, + szFile, + lineNum); + + } +} + + +inline void ClrDebugState::LockTaken(DbgStateLockType dbgStateLockType, + UINT cTakes, + void * pvLock, + __in_z const char * szFunction, + __in_z const char * szFile, + int lineNum) +{ + STATIC_CONTRACT_DEBUG_ONLY; + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + if ((m_violationmask & BadDebugState) != 0) + { + return; + } + + // Assert if we're taking a lock in a CANNOT_TAKE_LOCK scope. Even if this asserts, we'll + // continue to the following lines to track the lock + CheckOkayToLock(szFunction, szFile, lineNum); + + _ASSERTE(GetDbgStateLockData() != NULL); + + if (!IsOkToRetakeLock()) + { + if (m_LockState.IsLockRetaken(pvLock)) + { + CONTRACT_ASSERT("You cannot take a lock which is already being held in a CANNOT_RETAKE_LOCK scope.", + BaseContract::CAN_RETAKE_LOCK_No, + BaseContract::CAN_RETAKE_LOCK_No, + szFunction, + szFile, + lineNum); + } + } + + GetDbgStateLockData()->LockTaken(dbgStateLockType, cTakes, pvLock, szFunction, szFile, lineNum); +} + +inline void ClrDebugState::LockReleased(DbgStateLockType dbgStateLockType, UINT cReleases, void * pvLock) +{ + STATIC_CONTRACT_DEBUG_ONLY; + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + if ((m_violationmask & BadDebugState) != 0) + { + return; + } + + _ASSERTE(GetDbgStateLockData() != NULL); + + if (!IsOkToRetakeLock()) + { + // It is very suspicious to release any locks being hold at the time this function was + // called in a CANNOT_RETAKE_LOCK scope + _ASSERTE(m_LockState.IsSafeToRelease(cReleases)); + } + + GetDbgStateLockData()->LockReleased(dbgStateLockType, cReleases, pvLock); +} + +inline UINT ClrDebugState::GetLockCount(DbgStateLockType dbgStateLockType) +{ + STATIC_CONTRACT_DEBUG_ONLY; + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + if ((m_violationmask & BadDebugState) != 0) + { + return 0; + } + + _ASSERTE(GetDbgStateLockData() != NULL); + return GetDbgStateLockData()->GetLockCount(dbgStateLockType); +} + +inline UINT ClrDebugState::GetCombinedLockCount() +{ + STATIC_CONTRACT_DEBUG_ONLY; + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + if ((m_violationmask & BadDebugState) != 0) + { + return 0; + } + + _ASSERTE(GetDbgStateLockData() != NULL); + return GetDbgStateLockData()->GetCombinedLockCount(); +} + +inline void DbgStateLockData::LockTaken(DbgStateLockType dbgStateLockType, + UINT cTakes, // # times we're taking this lock (usually 1) + void * pvLock, + __in_z const char * szFunction, + __in_z const char * szFile, + int lineNum) +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_GC_NOTRIGGER; + + // Technically the lock's already been taken before we're called, but it's + // handy to have this contract here at the leaf end of the call chain, as it + // ensures SCAN will enforce that no use of the LOCK_TAKEN macros occurs + // in a CANNOT_TAKE_LOCK scope (as LOCK_TAKEN macros just call this function). + STATIC_CONTRACT_CAN_TAKE_LOCK; + + // Valid enum? + _ASSERTE(UINT(dbgStateLockType) < kDbgStateLockType_Count); + + UINT cCombinedLocks = GetCombinedLockCount(); + + // Are we exceeding the threshold for what we can store in m_rgTakenLockInfos? + // If so, assert a warning, but we'll deal with it. + if ((cCombinedLocks <= _countof(m_rgTakenLockInfos)) && + (cCombinedLocks + cTakes > _countof(m_rgTakenLockInfos))) + { + // Actually, for now we are NOT asserting until I can dedicate more time + // to this. Some class loader code paths legally hold many simultaneous + // locks (>10). Need to do further analysis on reasonable value to set + // for kMaxAllowedSimultaneousLocks. Since lock order checking is turned + // off for the moment anyway, exceeding kMaxAllowedSimultaneousLocks + // has no consequences for now anyway. + } + + m_rgcLocksTaken[dbgStateLockType] += cTakes; + + // Remember as many of these new entrances in m_rgTakenLockInfos as we can + for (UINT i = cCombinedLocks; + i < min (_countof(m_rgTakenLockInfos), cCombinedLocks + cTakes); + i++) + { + m_rgTakenLockInfos[i].m_pvLock = pvLock; + m_rgTakenLockInfos[i].m_szFile = szFile; + m_rgTakenLockInfos[i].m_lineNum = lineNum; + } +} + +inline void DbgStateLockData::LockReleased(DbgStateLockType dbgStateLockType, UINT cReleases, void * pvLock) +{ + // Valid enum? + _ASSERTE(UINT(dbgStateLockType) < kDbgStateLockType_Count); + + if (cReleases > m_rgcLocksTaken[dbgStateLockType]) + { + _ASSERTE(!"Releasing lock(s) that were never taken"); + cReleases = m_rgcLocksTaken[dbgStateLockType]; + } + + UINT cCombinedLocks = GetCombinedLockCount(); + + // If lock count is within range of our m_rgTakenLockInfos buffer size, then + // make sure we're releasing locks in reverse order of how we took them + for (UINT i = cCombinedLocks - cReleases; + i < min (_countof(m_rgTakenLockInfos), cCombinedLocks); + i++) + { + if (m_rgTakenLockInfos[i].m_pvLock != pvLock) + { + // Ok, I lied. We're not really checking that we're releasing locks in reverse + // order, because sometimes we legally release them out of order. (The loader + // does this intentionally in a few places.) We should consider whether those + // places can be changed, or whether we can add some kind of macro to declare + // that we're releasing out of order, and that it's ok & intentional. At that + // point, we can place a nice ASSERTE right here. Until then, do nothing. + } + + // We may be clearing out the wrong entry in m_rgTakenLockInfos here, if the locks + // were released out of order. However, it will eventually correct itself once all + // the out-of-order locks have been released. And our count + // (i.e., m_rgcLocksTaken[dbgStateLockType]) will always be accurate + memset(&(m_rgTakenLockInfos[i]), + 0, + sizeof(m_rgTakenLockInfos[i])); + } + + m_rgcLocksTaken[dbgStateLockType] -= cReleases; +} + +inline void DbgStateLockData::SetStartingValues() +{ + memset(this, 0, sizeof(*this)); +} + +inline UINT DbgStateLockData::GetLockCount(DbgStateLockType dbgStateLockType) +{ + _ASSERTE(UINT(dbgStateLockType) < kDbgStateLockType_Count); + return m_rgcLocksTaken[dbgStateLockType]; +} + +inline UINT DbgStateLockData::GetCombinedLockCount() +{ + // If this fires, the set of lock types must have changed. You'll need to + // fix the sum below to include all lock types + _ASSERTE(kDbgStateLockType_Count == 3); + + return m_rgcLocksTaken[0] + m_rgcLocksTaken[1] + m_rgcLocksTaken[2]; +} + +inline void DbgStateLockState::SetStartingValues() +{ + m_cLocksEnteringCannotRetakeLock = 0; + m_pLockData = NULL; // Will get filled in by CLRInitDebugState() +} + +// We set a marker to record the number of locks that have been taken when +// CANNOT_RETAKE_LOCK contract is constructed. +inline void DbgStateLockState::OnEnterCannotRetakeLockFunction() +{ + m_cLocksEnteringCannotRetakeLock = m_pLockData->GetCombinedLockCount(); +} + +inline BOOL DbgStateLockState::IsLockRetaken(void * pvLock) +{ + // m_cLocksEnteringCannotRetakeLock must be in valid range + _ASSERTE(m_cLocksEnteringCannotRetakeLock <= m_pLockData->GetCombinedLockCount()); + + // m_cLocksEnteringCannotRetakeLock records the number of locks that were taken + // when CANNOT_RETAKE_LOCK contract was constructed. + for (UINT i = 0; + i < min(_countof(m_pLockData->m_rgTakenLockInfos), m_cLocksEnteringCannotRetakeLock); + ++i) + { + if (m_pLockData->m_rgTakenLockInfos[i].m_pvLock == pvLock) + { + return TRUE; + } + } + return FALSE; +} + +inline BOOL DbgStateLockState::IsSafeToRelease(UINT cReleases) +{ + return m_cLocksEnteringCannotRetakeLock <= (m_pLockData->GetCombinedLockCount() - cReleases); +} + +inline void DbgStateLockState::SetDbgStateLockData(DbgStateLockData * pDbgStateLockData) +{ + m_pLockData = pDbgStateLockData; +} + +inline DbgStateLockData * DbgStateLockState::GetDbgStateLockData() +{ + return m_pLockData; +} + +#ifdef FEATURE_STACK_PROBE +// We don't want to allow functions that use holders to EX_TRY to be intolerant +// code... if an exception were to occur, the holders and EX_CATCH/FINALLY would +// have less than 1/4 clean up. +inline void EnsureSOIntolerantOK(const char *szFunction, + const char *szFile, + int lineNum) +{ + // We don't want to use a holder here, because a holder will + // call EnsureSOIntolerantOK + + DWORD error = GetLastError(); + if (! g_EnableDefaultRWValidation) + { + SetLastError(error); + return; + } + ClrDebugState *pClrDebugState = CheckClrDebugState(); + if (! pClrDebugState) + { + SetLastError(error); + return; + } + pClrDebugState->CheckIfSOIntolerantOK(szFunction, szFile, lineNum); + SetLastError(error); +} + +inline void ClrDebugState::CheckIfSOIntolerantOK(const char *szFunction, + const char *szFile, + int lineNum) + +{ + // If we are an RW function on a managed thread, we must be in SO-intolerant mode. Ie. we must be behind a probe. + if (IsSOIntolerant() || IsDebugOnly() || IsSONotMainline() || (m_violationmask & SOToleranceViolation) || + !g_fpShouldValidateSOToleranceOnThisThread || !g_fpShouldValidateSOToleranceOnThisThread()) + { + return; + } + SoTolerantViolationHelper(szFunction, szFile, lineNum); +} + +#endif + +inline +void CONTRACT_ASSERT(const char *szElaboration, + UINT whichTest, + UINT whichTestMask, + const char *szFunction, + const char *szFile, + int lineNum) +{ + if (CheckClrDebugState() && ( CheckClrDebugState()->ViolationMask() & BadDebugState)) + { + _ASSERTE(!"Someone tried to assert a contract violation although the contracts were disabled in this thread due to" + " an OOM or a shim/mscorwks mismatch. You can probably safely ignore this assert - however, whoever" + " called CONTRACT_ASSERT was supposed to checked if the current violationmask had the BadDebugState set." + " Look up the stack, see who called CONTRACT_ASSERT and file a bug against the owner."); + return; + } + + // prevent recursion - we use the same mechanism as CHECK, so this will + // also prevent mutual recursion involving ASSERT_CHECKs + CHECK _check; + if (_check.EnterAssert()) + { + char Buf[512*20 + 2048 + 1024]; + + sprintf_s(Buf,_countof(Buf), "CONTRACT VIOLATION by %s at \"%s\" @ %d\n\n%s\n", szFunction, szFile, lineNum, szElaboration); + + int count = 20; + ContractStackRecord *pRec = CheckClrDebugState() ? CheckClrDebugState()->GetContractStackTrace() : NULL; + BOOL foundconflict = FALSE; + BOOL exceptionBuildingStack = FALSE; + + PAL_TRY_NAKED + { + while (pRec != NULL) + { + char tmpbuf[512]; + BOOL fshowconflict = FALSE; + + if (!foundconflict) + { + if (whichTest == (pRec->m_testmask & whichTestMask)) + { + foundconflict = TRUE; + fshowconflict = TRUE; + } + } + + if (count != 0 || fshowconflict) + { + if (count != 0) + { + count--; + } + else + { + // Show that some lines have been skipped + strcat_s(Buf, _countof(Buf), "\n ..."); + + } + + sprintf_s(tmpbuf,_countof(tmpbuf), + "\n%s %s in %s at \"%s\" @ %d", + fshowconflict ? "VIOLATED-->" : " ", + pRec->m_construct, + pRec->m_szFunction, + pRec->m_szFile, + pRec->m_lineNum + ); + + strcat_s(Buf, _countof(Buf), tmpbuf); + } + + pRec = pRec->m_pNext; + } + } + PAL_EXCEPT_NAKED(EXCEPTION_EXECUTE_HANDLER) + { + // We're done trying to walk the stack of contracts. We faulted trying to form the contract stack trace, + // and that usually means that its corrupted. A common cause of this is having CONTRACTs in functions that + // never return, but instead do a non-local goto. + count = 0; + exceptionBuildingStack = TRUE; + } + PAL_ENDTRY_NAKED; + + if (count == 0) + { + strcat_s(Buf,_countof(Buf), "\n ..."); + } + + if (exceptionBuildingStack) + { + strcat_s(Buf,_countof(Buf), + "\n" + "\nError forming contract stack. Any contract stack displayed above is correct," + "\nbut it's most probably truncated. This is probably due to a CONTRACT in a" + "\nfunction that does a non-local goto. There are two bugs here:" + "\n" + "\n 1) the CONTRACT violation, and" + "\n 2) the CONTRACT in the function with the non-local goto." + "\n" + "\nPlease fix both bugs!" + "\n" + ); + } + + strcat_s(Buf,_countof(Buf), "\n\n"); + + if (!foundconflict && count != 0) + { + if (whichTest == BaseContract::THROWS_No) + { + strcat_s(Buf,_countof(Buf), "You can't throw here because there is no handler on the stack.\n"); + } + else + { + strcat_s(Buf,_countof(Buf), "We can't find the violated contract. Look for an old-style non-holder-based contract.\n"); + } + } + + DbgAssertDialog((char *)szFile, lineNum, Buf); + _check.LeaveAssert(); + } +} + + +FORCEINLINE BOOL BaseContract::EnforceContract() +{ + if (s_alwaysEnforceContracts) + return TRUE; + else + return CHECK::EnforceAssert(); +} + +inline void BaseContract::SetUnconditionalContractEnforcement(BOOL value) +{ + s_alwaysEnforceContracts = value; +} + +inline UINT GetDbgStateCombinedLockCount() +{ + return GetClrDebugState()->GetCombinedLockCount(); +} +inline UINT GetDbgStateLockCount(DbgStateLockType dbgStateLockType) +{ + return GetClrDebugState()->GetLockCount(dbgStateLockType); +} + +#define ASSERT_NO_USER_LOCKS_HELD() \ + _ASSERTE(GetDbgStateLockCount(kDbgStateLockType_User) == 0) +#define ASSERT_NO_HOST_BREAKABLE_CRSTS_HELD() \ + _ASSERTE(GetDbgStateLockCount(kDbgStateLockType_HostBreakableCrst) == 0) +#define ASSERT_NO_EE_LOCKS_HELD() \ + _ASSERTE(GetDbgStateLockCount(kDbgStateLockType_EE) == 0) + +#else // ENABLE_CONTRACTS_IMPL + +#define ASSERT_NO_USER_LOCKS_HELD() +#define ASSERT_NO_HOST_BREAKABLE_CRSTS_HELD() +#define ASSERT_NO_EE_LOCKS_HELD() + +#endif // ENABLE_CONTRACTS_IMPL + +#endif // CONTRACT_INL_ |