summaryrefslogtreecommitdiff
path: root/src/vm/eecontract.cpp
blob: 4cb31debd742f92b13ae734a27055b5203a7585f (plain)
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
// 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;
}