summaryrefslogtreecommitdiff
path: root/src/ToolBox/SOS/Strike/WatchCmd.cpp
blob: 443f1dd6ef66e4c3056b13adfb074348800d7967 (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
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
// 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.

#include "WatchCmd.h"

#ifndef IfFailRet
#define IfFailRet(EXPR) do { Status = (EXPR); if(FAILED(Status)) { return (Status); } } while (0)
#endif

_PersistList::~_PersistList()
{
    PersistWatchExpression* pCur = pHeadExpr;
    while(pCur != NULL)
    {
        PersistWatchExpression* toDelete = pCur;
        pCur = pCur->pNext;
        delete toDelete;
    }
}

WatchCmd::WatchCmd() :
pExpressionListHead(NULL)
{ }
WatchCmd::~WatchCmd()
{
    Clear();
    PersistList* pCur = pPersistListHead;
    while(pCur != NULL)
    {
        PersistList* toDelete = pCur;
        pCur = pCur->pNext;
        delete toDelete;
    }
}

// Deletes all current watch expressions from the watch list
// (does not delete persisted watch lists though)
HRESULT WatchCmd::Clear()
{
    WatchExpression* pCurrent = pExpressionListHead;
    while(pCurrent != NULL)
    {
        WatchExpression* toDelete = pCurrent;
        pCurrent = pCurrent->pNext;
        delete toDelete;
    }
    pExpressionListHead = NULL;
    return S_OK;
}

// Adds a new expression to the active watch list
HRESULT WatchCmd::Add(__in_z WCHAR* pExpression)
{
    WatchExpression* pExpr = new WatchExpression;
    if(pExpr == NULL)
        return E_OUTOFMEMORY;
    wcsncpy_s(pExpr->pExpression, MAX_EXPRESSION, pExpression, _TRUNCATE);
    pExpr->pNext = NULL;

    WatchExpression** ppCurrent = &pExpressionListHead;
    while(*ppCurrent != NULL)
        ppCurrent = &((*ppCurrent)->pNext);
    *ppCurrent = pExpr;
    return S_OK;
}

// removes an expression at the given index in the active watch list
HRESULT WatchCmd::Remove(int index)
{
    HRESULT Status = S_FALSE;
    WatchExpression** ppCurrent = &pExpressionListHead;
    for(int i=1; *ppCurrent != NULL; i++)
    {
        if(i == index)
        {
            WatchExpression* toDelete = *ppCurrent;
            *ppCurrent = (*ppCurrent)->pNext;
            delete toDelete;
            Status = S_OK;
            break;
        }
        ppCurrent = &((*ppCurrent)->pNext);

    }
    return Status;
}

// Evaluates and prints a tree version of the active watch list
// The tree will be expanded along the nodes in expansionPath
// Optionally the list is filtered to only show differences from pFilterName (the name of a persisted watch list)
HRESULT WatchCmd::Print(int expansionIndex, __in_z WCHAR* expansionPath, __in_z WCHAR* pFilterName)
{
    HRESULT Status = S_OK;
    INIT_API_EE();
    INIT_API_DAC();
    EnableDMLHolder dmlHolder(TRUE);
    IfFailRet(InitCorDebugInterface());

    PersistList* pFilterList = NULL;
    if(pFilterName != NULL)
    {
        pFilterList = pPersistListHead;
        while(pFilterList != NULL)
        {
            if(_wcscmp(pFilterList->pName, pFilterName)==0)
                break;
            pFilterList = pFilterList->pNext;
        }
    }

    PersistWatchExpression* pHeadFilterExpr = (pFilterList != NULL) ? pFilterList->pHeadExpr : NULL;

    WatchExpression* pExpression = pExpressionListHead;
    int index = 1;
    while(pExpression != NULL)
    {
        ExpressionNode* pResult = NULL;
        if(FAILED(Status = ExpressionNode::CreateExpressionNode(pExpression->pExpression, &pResult)))
        {
            ExtOut("  %d) Error: HRESULT 0x%x while evaluating expression \'%S\'", index, Status, pExpression->pExpression);
        }
        else
        {
            //check for matching absolute expression
            PersistWatchExpression* pCurFilterExpr = pHeadFilterExpr;
            while(pCurFilterExpr != NULL)
            {
                if(_wcscmp(pCurFilterExpr->pExpression, pResult->GetAbsoluteExpression())==0)
                    break;
                pCurFilterExpr = pCurFilterExpr->pNext;
            }

            // check for matching persist evaluation on the matching expression
            BOOL print = TRUE;
            if(pCurFilterExpr != NULL)
            {
                WCHAR pCurPersistResult[MAX_EXPRESSION];
                FormatPersistResult(pCurPersistResult, MAX_EXPRESSION, pResult);
                if(_wcscmp(pCurPersistResult, pCurFilterExpr->pPersistResult)==0)
                {
                    print = FALSE;
                }
            }

            //expand and print
            if(print)
            {
                if(index == expansionIndex)
                    pResult->Expand(expansionPath);
                PrintCallbackData data;
                data.index = index;
                WCHAR pCommand[MAX_EXPRESSION];
                swprintf_s(pCommand, MAX_EXPRESSION, L"!watch -expand %d", index);
                data.pCommand = pCommand;
                pResult->DFSVisit(EvalPrintCallback, (VOID*)&data);
            }
            delete pResult;
        }
        pExpression = pExpression->pNext;
        index++;
    }
    return Status;
}

// Deletes an persisted watch list by name
HRESULT WatchCmd::RemoveList(__in_z WCHAR* pListName)
{
    PersistList** ppList = &pPersistListHead;
    while(*ppList != NULL)
    {
        if(_wcscmp((*ppList)->pName, pListName) == 0)
        {
            PersistList* toDelete = *ppList;
            *ppList = (*ppList)->pNext;
            delete toDelete;
            return S_OK;
        }
        ppList = &((*ppList)->pNext);
    }
    return S_FALSE;
}

// Renames a previously saved persisted watch list
HRESULT WatchCmd::RenameList(__in_z WCHAR* pOldName, __in_z WCHAR* pNewName)
{
    if(_wcscmp(pOldName, pNewName)==0)
        return S_OK;
    PersistList** ppList = &pPersistListHead;
    while(*ppList != NULL)
    {
        if(_wcscmp((*ppList)->pName, pOldName) == 0)
        {
            PersistList* pListToChangeName = *ppList;
            RemoveList(pNewName);
            wcsncpy_s(pListToChangeName->pName, MAX_EXPRESSION, pNewName, _TRUNCATE);
            return S_OK;
        }
        ppList = &((*ppList)->pNext);
    }
    return S_FALSE;
}

// Saves the active watch list together with the current evaluations as
// a new persisted watch list
HRESULT WatchCmd::SaveList(__in_z WCHAR* pSaveName)
{
    HRESULT Status = S_OK;
    INIT_API_EE();
    INIT_API_DAC();
    IfFailRet(InitCorDebugInterface());

    RemoveList(pSaveName);
    PersistList* pList = new PersistList();
    wcsncpy_s(pList->pName, MAX_EXPRESSION, pSaveName, _TRUNCATE);
    pList->pHeadExpr = NULL;
    PersistCallbackData data;
    data.ppNext = &(pList->pHeadExpr);
    WatchExpression* pExpression = pExpressionListHead;
    while(pExpression != NULL)
    {
        ExpressionNode* pResult = NULL;
        if(SUCCEEDED(Status = ExpressionNode::CreateExpressionNode(pExpression->pExpression, &pResult)))
        {
            pResult->DFSVisit(PersistCallback, (VOID*)&data);
            delete pResult;
        }
        pExpression = pExpression->pNext;
    }

    pList->pNext = pPersistListHead;
    pPersistListHead = pList;
    return Status;
}

// Saves the current watch list to file as a sequence of commands that will
// recreate the list
HRESULT WatchCmd::SaveListToFile(FILE* pFile)
{
    WatchExpression* pExpression = pExpressionListHead;
    while(pExpression != NULL)
    {
        fprintf_s(pFile, "!watch -a %S\n", pExpression->pExpression);
        pExpression = pExpression->pNext;
    }
    return S_OK;
}

// Escapes characters that would be interpretted as DML markup, namely angle brackets
// that often appear in generic type names
VOID WatchCmd::DmlEscape(__in_ecount(cchInput) WCHAR* pInput, int cchInput, __in_ecount(cchOutput) WCHAR* pEscapedOutput, int cchOutput)
{
    pEscapedOutput[0] = L'\0';
    for(int i = 0; i < cchInput; i++)
    {
        if(pInput[i] == L'<') 
        {
            if(0 != wcscat_s(pEscapedOutput, cchOutput, L"&lt;")) return;
            pEscapedOutput += 4;
            cchOutput -= 4;
        }
        else if(pInput[i] == L'>')
        {
            if(0 != wcscat_s(pEscapedOutput, cchOutput, L"&gt;")) return;
            pEscapedOutput += 4;
            cchOutput -= 4;
        }
        else if(cchOutput > 1)
        {
            pEscapedOutput[0] = pInput[i];
            pEscapedOutput[1] = '\0';
            pEscapedOutput++;
            cchOutput--;
        }
        if(pInput[i] == L'\0' || cchOutput == 1) break;
    }
}

// A DFS traversal callback for the expression node tree that prints it
VOID WatchCmd::EvalPrintCallback(ExpressionNode* pExpressionNode, int depth, VOID* pUserData)
{
    PrintCallbackData* pData = (PrintCallbackData*)pUserData;
    for(int i = 0; i < depth; i++) ExtOut("    ");
    if(depth == 0)
        ExtOut("  %d) ", pData->index);
    else
        ExtOut(" |- ");
    if(pExpressionNode->GetErrorMessage()[0] != 0)
    {
        ExtOut("%S (%S)\n", pExpressionNode->GetRelativeExpression(), pExpressionNode->GetErrorMessage());
    }
    else
    {
        // names can have '<' and '>' in them, need to escape
        WCHAR pEscapedTypeName[MAX_EXPRESSION];
        DmlEscape(pExpressionNode->GetTypeName(), (int)_wcslen(pExpressionNode->GetTypeName()), pEscapedTypeName, MAX_EXPRESSION);
        WCHAR pRelativeExpression[MAX_EXPRESSION];
        DmlEscape(pExpressionNode->GetRelativeExpression(), (int)_wcslen(pExpressionNode->GetRelativeExpression()), pRelativeExpression, MAX_EXPRESSION);
        DMLOut("%S <exec cmd=\"%S (%S)%S\">%S</exec> %S\n", pEscapedTypeName, pData->pCommand, pEscapedTypeName, pExpressionNode->GetAbsoluteExpression(), pRelativeExpression, pExpressionNode->GetTextValue());
    }
}

// A DFS traversal callback for the expression node tree that saves all the values into a new
// persisted watch list
VOID WatchCmd::PersistCallback(ExpressionNode* pExpressionNode, int depth, VOID* pUserData)
{
    PersistCallbackData* pData = (PersistCallbackData*)pUserData;
    if(depth != 0)
        return;

    PersistWatchExpression* pPersistExpr = new PersistWatchExpression();
    wcsncpy_s(pPersistExpr->pExpression, MAX_EXPRESSION, pExpressionNode->GetAbsoluteExpression(), _TRUNCATE);
    FormatPersistResult(pPersistExpr->pPersistResult, MAX_EXPRESSION, pExpressionNode);
    pPersistExpr->pNext = NULL;
    *(pData->ppNext) = pPersistExpr;
    pData->ppNext = &(pPersistExpr->pNext);
}

// Determines how the value of an expression node is saved as a persisted result. This effectively determines
// the definition of equality when determining if an expression has changed value
VOID WatchCmd::FormatPersistResult(__inout_ecount(cchPersistResult)  WCHAR* pPersistResult, DWORD cchPersistResult, ExpressionNode* pExpressionNode)
{
    if(pExpressionNode->GetErrorMessage()[0] != 0)
    {
        _snwprintf_s(pPersistResult, MAX_EXPRESSION, _TRUNCATE, L"%s (%s)\n", pExpressionNode->GetRelativeExpression(), pExpressionNode->GetErrorMessage());
    }
    else
    {
        _snwprintf_s(pPersistResult, MAX_EXPRESSION, _TRUNCATE, L"%s %s %s\n", pExpressionNode->GetTypeName(), pExpressionNode->GetRelativeExpression(), pExpressionNode->GetTextValue());
    }
}