diff options
Diffstat (limited to 'src/vm/eecontract.cpp')
-rw-r--r-- | src/vm/eecontract.cpp | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/src/vm/eecontract.cpp b/src/vm/eecontract.cpp new file mode 100644 index 0000000000..4cb31debd7 --- /dev/null +++ b/src/vm/eecontract.cpp @@ -0,0 +1,272 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// --------------------------------------------------------------------------- +// EEContract.cpp +// + +// ! 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. +// --------------------------------------------------------------------------- + + +#include "common.h" +#include "dbginterface.h" + + +#ifdef ENABLE_CONTRACTS + +void EEContract::Disable() +{ + BaseContract::Disable(); +} + +void EEContract::DoChecks(UINT testmask, __in_z const char *szFunction, __in_z char *szFile, int lineNum) +{ + SCAN_IGNORE_THROW; // Tell the static contract analyzer to ignore contract violations + SCAN_IGNORE_FAULT; // due to the contract checking logic itself. + SCAN_IGNORE_TRIGGER; + SCAN_IGNORE_LOCK; + SCAN_IGNORE_SO; + + // Many of the checks below result in calls to GetThread() + // that work just fine if GetThread() returns NULL, so temporarily + // allow such calls. + BEGIN_GETTHREAD_ALLOWED_IN_NO_THROW_REGION; + m_pThread = GetThread(); + if (m_pThread != NULL) + { + m_pClrDebugState = m_pThread->GetClrDebugState(); + } + + // Call our base DoChecks. + BaseContract::DoChecks(testmask, szFunction, szFile, lineNum); + + m_testmask = testmask; + m_contractStackRecord.m_testmask = testmask; + + // GC mode check + switch (testmask & MODE_Mask) + { + case MODE_Coop: + if (m_pThread == NULL || !m_pThread->PreemptiveGCDisabled()) + { + // + // Check if this is the debugger helper thread and has the runtime + // stoppped. If both of these things are true, then we do not care + // whether we are in COOP mode or not. + // + if ((g_pDebugInterface != NULL) && + g_pDebugInterface->ThisIsHelperThread() && + g_pDebugInterface->IsStopped()) + { + break; + } + + // Pretend that the threads doing GC are in cooperative mode so that code with + // MODE_COOPERATIVE contract works fine on them. + if (IsGCThread()) + { + break; + } + + if (!( (ModeViolation|BadDebugState) & m_pClrDebugState->ViolationMask())) + { + if (m_pThread == NULL) + { + CONTRACT_ASSERT("You must have called SetupThread in order to be in GC Cooperative mode.", + Contract::MODE_Preempt, + Contract::MODE_Mask, + m_contractStackRecord.m_szFunction, + m_contractStackRecord.m_szFile, + m_contractStackRecord.m_lineNum + ); + } + else + { + CONTRACT_ASSERT("MODE_COOPERATIVE encountered while thread is in preemptive state.", + Contract::MODE_Preempt, + Contract::MODE_Mask, + m_contractStackRecord.m_szFunction, + m_contractStackRecord.m_szFile, + m_contractStackRecord.m_lineNum + ); + } + } + } + break; + + case MODE_Preempt: + // Unmanaged threads are considered permanently preemptive so a NULL thread amounts to a passing case here. + if (m_pThread != NULL && m_pThread->PreemptiveGCDisabled()) + { + if (!( (ModeViolation|BadDebugState) & m_pClrDebugState->ViolationMask())) + { + CONTRACT_ASSERT("MODE_PREEMPTIVE encountered while thread is in cooperative state.", + Contract::MODE_Coop, + Contract::MODE_Mask, + m_contractStackRecord.m_szFunction, + m_contractStackRecord.m_szFile, + m_contractStackRecord.m_lineNum + ); + } + } + break; + + case MODE_Disabled: + // Nothing + break; + + default: + UNREACHABLE(); + } + + // GC Trigger check + switch (testmask & GC_Mask) + { + case GC_Triggers: + // We don't want to do a full TRIGGERSGC here as this could corrupt + // OBJECTREF-typed arguments to the function. + { + if (m_pClrDebugState->GetGCNoTriggerCount()) + { + if (!( (GCViolation|BadDebugState) & m_pClrDebugState->ViolationMask())) + { + CONTRACT_ASSERT("GC_TRIGGERS encountered in a GC_NOTRIGGER scope", + Contract::GC_NoTrigger, + Contract::GC_Mask, + m_contractStackRecord.m_szFunction, + m_contractStackRecord.m_szFile, + m_contractStackRecord.m_lineNum + ); + } + } + } + break; + + case GC_NoTrigger: + m_pClrDebugState->ViolationMaskReset( GCViolation ); + + // Inlined BeginNoTriggerGC + m_pClrDebugState->IncrementGCNoTriggerCount(); + if (m_pThread && m_pThread->m_fPreemptiveGCDisabled) + { + m_pClrDebugState->IncrementGCForbidCount(); + } + + break; + + case GC_Disabled: + // Nothing + break; + + default: + UNREACHABLE(); + } + + // Host Triggers check + switch (testmask & HOST_Mask) + { + case HOST_Calls: + { + if (!m_pClrDebugState->IsHostCaller()) + { + if (!( (HostViolation|BadDebugState) & m_pClrDebugState->ViolationMask())) + { + // Avoid infinite recursion by temporarily allowing HOST_CALLS + // violations so that we don't get contract asserts in anything + // called downstream of CONTRACT_ASSERT. If we unwind out of + // here, our dtor will reset our state to what it was on entry. + CONTRACT_VIOLATION(HostViolation); + CONTRACT_ASSERT("HOST_CALLS encountered in a HOST_NOCALLS scope", + Contract::HOST_NoCalls, + Contract::HOST_Mask, + m_contractStackRecord.m_szFunction, + m_contractStackRecord.m_szFile, + m_contractStackRecord.m_lineNum + ); + } + } + } + break; + + case HOST_NoCalls: + // m_pClrDebugState->ViolationMaskReset( HostViolation ); + m_pClrDebugState->ResetHostCaller(); + break; + + case HOST_Disabled: + // Nothing + break; + + default: + UNREACHABLE(); + } + END_GETTHREAD_ALLOWED_IN_NO_THROW_REGION; + + // EE Thread-required check + // NOTE: The following must NOT be inside BEGIN/END_GETTHREAD_ALLOWED, + // as the change to m_pClrDebugState->m_allowGetThread below would be + // overwritten by END_GETTHREAD_ALLOWED. + switch (testmask & EE_THREAD_Mask) + { + case EE_THREAD_Required: + if (!((EEThreadViolation|BadDebugState) & m_pClrDebugState->ViolationMask())) + { + if (m_pThread == NULL) + { + CONTRACT_ASSERT("EE_THREAD_REQUIRED encountered with no current EE Thread object in TLS.", + Contract::EE_THREAD_Required, + Contract::EE_THREAD_Mask, + m_contractStackRecord.m_szFunction, + m_contractStackRecord.m_szFile, + m_contractStackRecord.m_lineNum + ); + } + else if (!m_pClrDebugState->IsGetThreadAllowed()) + { + // In general, it's unsafe for an EE_THREAD_NOT_REQUIRED function to + // call an EE_THREAD_REQUIRED function. In cases where it is safe, + // you may wrap the call to the EE_THREAD_REQUIRED function inside a + // BEGIN/END_GETTHREAD_ALLOWED block, but you may only do so if the + // case where GetThread() == NULL is clearly handled in a way that + // prevents entry into the BEGIN/END_GETTHREAD_ALLOWED block. + CONTRACT_ASSERT("EE_THREAD_REQUIRED encountered in an EE_THREAD_NOT_REQUIRED scope, without an intervening BEGIN/END_GETTHREAD_ALLOWED block.", + Contract::EE_THREAD_Required, + Contract::EE_THREAD_Mask, + m_contractStackRecord.m_szFunction, + m_contractStackRecord.m_szFile, + m_contractStackRecord.m_lineNum + ); + } + } + m_pClrDebugState->SetGetThreadAllowed(); + break; + + case EE_THREAD_Not_Required: + m_pClrDebugState->ResetGetThreadAllowed(); + break; + + case EE_THREAD_Disabled: + break; + + default: + UNREACHABLE(); + } +} +#endif // ENABLE_CONTRACTS + + +BYTE* __stdcall GetAddrOfContractShutoffFlag() +{ + LIMITED_METHOD_CONTRACT; + + // Exposed entrypoint where we cannot probe or do anything TLS + // related + static BYTE gContractShutoffFlag = 0; + + return &gContractShutoffFlag; +} + |