summaryrefslogtreecommitdiff
path: root/src/vm/eecontract.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/vm/eecontract.cpp')
-rw-r--r--src/vm/eecontract.cpp272
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;
+}
+