summaryrefslogtreecommitdiff
path: root/src/ToolBox/SOS/Strike/ExpressionNode.h
blob: 507a8a53d3ced5c06de55451ae08d95648965004 (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
// 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.


#ifndef _EXPRESSION_NODE_
#define _EXPRESSION_NODE_

#ifdef FEATURE_PAL
#error This file isn't designed to build in PAL
#endif

#include "strike.h"
#include "sos.h"
#include "util.h"

#define MAX_EXPRESSION 500
#define MAX_ERROR 500


// Represents one node in a tree of expressions and sub-expressions
// These nodes are used in the !watch expandable expression tree
// Each node consists of a string based C#-like expression and its
// evaluation within the current context of the debuggee.
//
// These nodes are also intended for eventual use in ClrStack -i expression tree
// but ClrStack -i hasn't yet been refactored to use them
//
// Each node can evaluate to:
// nothing - if an error occurs during expression parsing or the expression
//           names don't match to anything in the debuggee
// a debuggee value - these are values that are backed in memory of the debuggee
//                    (ICorDebugValue objects) or build time constants which are
//                    stored in the assembly metadata.
// a debuggee type - instead of refering to a particular instance of a type (the
//                   value case above), nodes can directly refer to a type definition
//                   represented by an ICorDebugType object
class ExpressionNode
{
public:

    typedef VOID (*ExpressionNodeVisitorCallback)(ExpressionNode* pExpressionNode, int depth, VOID* pUserData);
    
    // Returns the complete expression being evaluated to get the value for this node
    // The returned pointer is a string interior to this object - once you release
    // all references to this object the string is invalid.
    WCHAR* GetAbsoluteExpression();

    // Returns the sub expression that logically indicates how the parent expression
    // was built upon to reach this node. This relative value has no purpose other
    // than an identifier and to convey UI meaning to the user. At present typical values
    // are the name of type, a local, a parameter, a field, an array index, or '<basetype>'
    // for a baseclass casting operation
    // The returned pointer is a string interior to this object - once you release
    // all references to this object the string is invalid.
    WCHAR* GetRelativeExpression();

    // Returns a text representation of the type of value that this node refers to
    // It is possible this node doesn't evaluate to anything and therefore has no
    // type
    // The returned pointer is a string interior to this object - once you release
    // all references to this object the string is invalid.
    WCHAR* GetTypeName();

    // Returns a text representation of the value for this node. It is possible that
    // this node doesn't evaluate to anything and therefore has no value text.
    // The returned pointer is a string interior to this object - once you release
    // all references to this object the string is invalid.
    WCHAR* GetTextValue();

    // If there is any error during the evaluation of this node's expression, it is
    // returned here.
    // The returned pointer is a string interior to this object - once you release
    // all references to this object the string is invalid.
    WCHAR* GetErrorMessage();
    
    // Factory function for creating the expression node at the root of a tree
    static HRESULT CreateExpressionNode(__in_z WCHAR* pExpression, ExpressionNode** ppExpressionNode);

    // Performs recursive expansion within the tree for nodes that are along the path to varToExpand.
    // Expansion involves calulating a set of child expressions from the current expression via
    // field dereferencing, array index dereferencing, or casting to a base type.
    // For example if a tree was rooted with expression 'foo.bar' and varToExpand is '(Baz)foo.bar[9]'
    // then 'foo.bar', 'foo.bar[9]', and '(Baz)foo.bar[9]' nodes would all be expanded.
    HRESULT Expand(__in_z WCHAR* varToExpand);

    // Standard depth first search tree traversal pattern with a callback
    VOID DFSVisit(ExpressionNodeVisitorCallback pFunc, VOID* pUserData, int depth=0);

private:
    // for nodes that evaluate to a type, this is that type
    // for nodes that evaluate to a debuggee value, this is the type of that
    // value or one of its base types. It represents the type the value should
    // displayed and expanded as.
    ToRelease<ICorDebugType> pTypeCast;

    // for nodes that evaluate to a memory backed debuggee value, this is that value
    ToRelease<ICorDebugValue> pValue;

    // if this node gets expanded and it has thread-static or context-static sub-fields,
    // this frame disambiguates which thread and context to use.
    ToRelease<ICorDebugILFrame> pILFrame;

    // TODO: exactly which metadata is this supposed to be? try to get rid of this
    ToRelease<IMetaDataImport> pMD;

    // PERF: this could be a lot more memory efficient
    WCHAR pTextValue[MAX_EXPRESSION];
    WCHAR pErrorMessage[MAX_ERROR];
    WCHAR pAbsoluteExpression[MAX_EXPRESSION];
    WCHAR pRelativeExpression[MAX_EXPRESSION];
    WCHAR pTypeName[MAX_EXPRESSION];

    // if this value represents a build time constant debuggee value, this is a pointer
    // to the value data stored in metadata and its size
    UVCP_CONSTANT pDefaultValue;
    ULONG cchDefaultValue;

    // Pointer in a linked list of sibling nodes that all share the same parent
    ExpressionNode* pNextSibling;
    // Pointer to the first child node of this node, other children can be found
    // by following the child's sibling list.
    ExpressionNode* pChild;

    typedef VOID (*VariableEnumCallback)(ICorDebugValue* pValue, WCHAR* pName, WCHAR* pErrorMessage, VOID* pUserData);
    typedef VOID (*FrameEnumCallback)(ICorDebugFrame* pFrame, VOID* pUserData);
    
    // Indicates how a child node was derived from its parent
    enum ChildKind
    {
        ChildKind_Field,
        ChildKind_Index,
        ChildKind_BaseClass
    };

    // Creates a new expression with a given debuggee value and frame
    ExpressionNode(__in_z WCHAR* pExpression, ICorDebugValue* pValue, ICorDebugILFrame* pFrame);

    // Creates a new expression that has an error and no value
    ExpressionNode(__in_z WCHAR* pExpression, __in_z WCHAR* pErrorMessage);

    // Creates a new child expression
    ExpressionNode(__in_z WCHAR* pParentExpression, ChildKind ck, __in_z WCHAR* pRelativeExpression, ICorDebugValue* pValue, ICorDebugType* pType, ICorDebugILFrame* pFrame, UVCP_CONSTANT pDefaultValue = NULL, ULONG cchDefaultValue = 0);

    // Common member initialization for the constructors
    VOID Init(ICorDebugValue* pValue, ICorDebugType* pTypeCast, ICorDebugILFrame* pFrame);

    // Retreves the correct IMetaDataImport for the type represented in this node and stores it
    // in pMD.
    HRESULT PopulateMetaDataImport();

    // Determines the string representation of pType and stores it in typeName
    static HRESULT CalculateTypeName(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen);
    

    // Appends angle brackets and the generic argument list to a type name
    static HRESULT AddGenericArgs(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen);

    // Determines the text name for the type of this node and caches it
    HRESULT PopulateType();

    // Node expansion helpers

    // Inserts a new child at the end of the linked list of children
    // PERF: This has O(N) insert time but these lists should never be large
    VOID AddChild(ExpressionNode* pNewChild);

    // Helper that determines if the current node is on the path of nodes represented by
    // expression varToExpand
    BOOL ShouldExpandVariable(__in_z WCHAR* varToExpand);

    // Expands this array node by creating child nodes with expressions refering to individual array elements
    HRESULT ExpandSzArray(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand);

    // Expands this struct/class node by creating child nodes with expressions refering to individual field values
    // and one node for the basetype value
    HRESULT ExpandFields(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand);
    
    // Value Population functions

    //Helper for unwrapping values
    static HRESULT DereferenceAndUnboxValue(ICorDebugValue * pInputValue, ICorDebugValue** ppOutputValue, BOOL * pIsNull = NULL);

    // Returns TRUE if the value derives from System.Enum
    static BOOL IsEnum(ICorDebugValue * pInputValue);

    // Calculates the value text for nodes that have enum values
    HRESULT PopulateEnumValue(ICorDebugValue* pEnumValue, BYTE* enumValue);

    // Helper that fetches the text value of a string ICorDebugValue
    HRESULT GetDebuggeeStringValue(ICorDebugValue* pInputValue, __inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer);

    // Helper that fetches the text value of a string build-time literal
    HRESULT GetConstantStringValue(__inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer);

    // Helper that caches the textual value for nodes that evaluate to array objects
    HRESULT PopulateSzArrayValue(ICorDebugValue* pInputValue);

    // Helper that caches the textual value for nodes of any type
    HRESULT PopulateTextValueHelper();

    // Caches the textual value of this node
    HRESULT PopulateTextValue();


    // Expression parsing and search

    // In/Out parameters for the EvaluateExpressionFrameScanCallback
    typedef struct _EvaluateExpressionFrameScanData
    {
        WCHAR* pIdentifier;
        ToRelease<ICorDebugValue> pFoundValue;
        ToRelease<ICorDebugILFrame> pFoundFrame;
        ToRelease<ICorDebugILFrame> pFirstFrame;
        WCHAR* pErrorMessage;
        DWORD cchErrorMessage;
    } EvaluateExpressionFrameScanData;

    //Callback that searches a frame to determine if it contains a local variable or parameter of a given name
    static VOID EvaluateExpressionFrameScanCallback(ICorDebugFrame* pFrame, VOID* pUserData);

    //Callback checks to see if a given local/parameter has name pName
    static VOID EvaluateExpressionVariableScanCallback(ICorDebugValue* pValue, __in_z WCHAR* pName, __out_z WCHAR* pErrorMessage, VOID* pUserData);

    //Factory method that recursively parses pExpression and create an ExpressionNode
    //  pExpression -          the entire expression being parsed
    //  pExpressionRemainder - the portion of the expression that remains to be parsed in this
    //                         recursive invocation
    //  charactersParsed     - the number of characters that have been parsed from pExpression
    //                         so far (todo: this is basically the difference between remainder and
    //                         full expression, do we need it?)
    //  pParsedValue         - A debuggee value that should be used as the context for interpreting
    //                         pExpressionRemainder
    //  pParsedType          - A debuggee type that should be used as the context for interpreting
    //                         pExpressionRemainder. 
    //  pParsedDefaultValue  - A fixed value from metadata that should be used as context for
    //                         interpretting pExpressionRemainder
    //  cchParsedDefaultValue- Size of pParsedDefaultValue
    //  pFrame               - A debuggee IL frame that disambiguates the thread and context needed
    //                         to evaluate a thread-static or context-static value
    //  ppExpressionNode     - OUT - the resulting expression node
    //
    //
    static HRESULT CreateExpressionNodeHelper(__in_z WCHAR* pExpression,
                                              __in_z WCHAR* pExpressionParseRemainder,
                                              DWORD charactersParsed,
                                              ICorDebugValue* pParsedValue,
                                              ICorDebugType* pParsedType,
                                              UVCP_CONSTANT pParsedDefaultValue,
                                              ULONG cchParsedDefaultValue,
                                              ICorDebugILFrame* pFrame,
                                              ExpressionNode** ppExpressionNode);

    // Splits apart a C#-like expression and determines the first identifier in the string and updates expression to point
    // at the remaining unparsed portion
    static HRESULT ParseNextIdentifier(__in_z WCHAR** expression,
                                       __inout_ecount(cchIdentifierName) WCHAR* identifierName,
                                       DWORD cchIdentifierName,
                                       __inout_ecount(cchErrorMessage) WCHAR* errorMessage,
                                       DWORD cchErrorMessage,
                                       DWORD* charactersParsed,
                                       BOOL* isArrayIndex);


    // Iterate through all parameters in the ILFrame calling the callback function for each of them
    static HRESULT EnumerateParameters(IMetaDataImport * pMD,
                                       mdMethodDef methodDef,
                                       ICorDebugILFrame * pILFrame,
                                       VariableEnumCallback pCallback,
                                       VOID* pUserData);

    // Enumerate all locals in the given ILFrame, calling the callback method for each of them
    static HRESULT EnumerateLocals(IMetaDataImport * pMD,
                                   mdMethodDef methodDef,
                                   ICorDebugILFrame * pILFrame,
                                   VariableEnumCallback pCallback,
                                   VOID* pUserData);

    // Iterates over all frames on the current thread's stack, calling the callback function for each of them
    static HRESULT EnumerateFrames(FrameEnumCallback pCallback, VOID* pUserData);

    // Determines the corresponding ICorDebugType for a given primitive type
    static HRESULT FindTypeFromElementType(CorElementType et, ICorDebugType** ppType);

    // Gets the appropriate element type encoding for well-known fully qualified type names
    // This doesn't work for arbitrary types, just types that have CorElementType short forms.
    static HRESULT GetCanonicalElementTypeForTypeName(__in_z WCHAR* pTypeName, CorElementType *et);

    // Searches the debuggee for any ICorDebugType that matches the given fully qualified name
    // This will search across all AppDomains and Assemblies
    static HRESULT FindTypeByName(__in_z WCHAR* pTypeName, ICorDebugType** ppType);

    // Searches the debuggee for any ICorDebugType that matches the given fully qualified name
    // This will search across all Assemblies in the given AppDomain
    static HRESULT FindTypeByName(ICorDebugAppDomain* pAppDomain, __in_z WCHAR* pTypeName, ICorDebugType** ppType);

    // Searches the assembly for any ICorDebugType that matches the given fully qualified name
    static HRESULT FindTypeByName(ICorDebugAssembly* pAssembly, __in_z WCHAR* pTypeName, ICorDebugType** ppType);

    // Searches a given module for any ICorDebugType that matches the given fully qualified type name
    static HRESULT FindTypeByName(ICorDebugModule* pModule, __in_z WCHAR* pTypeName, ICorDebugType** ppType);

    // Checks whether the given token is or refers to type System.ValueType or System.Enum
    static HRESULT IsTokenValueTypeOrEnum(mdToken token, IMetaDataImport* pMetadata, BOOL* pResult);
};

#endif