diff options
Diffstat (limited to 'src/ToolBox/SOS/Strike/ExpressionNode.cpp')
-rw-r--r-- | src/ToolBox/SOS/Strike/ExpressionNode.cpp | 2178 |
1 files changed, 2178 insertions, 0 deletions
diff --git a/src/ToolBox/SOS/Strike/ExpressionNode.cpp b/src/ToolBox/SOS/Strike/ExpressionNode.cpp new file mode 100644 index 0000000000..6516823aaa --- /dev/null +++ b/src/ToolBox/SOS/Strike/ExpressionNode.cpp @@ -0,0 +1,2178 @@ +// 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 "ExpressionNode.h" + + +#ifndef IfFailRet +#define IfFailRet(EXPR) do { Status = (EXPR); if(FAILED(Status)) { return (Status); } } while (0) +#endif + +// 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* ExpressionNode::GetAbsoluteExpression() { return pAbsoluteExpression; } + +// 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* ExpressionNode::GetRelativeExpression() { return pRelativeExpression; } + +// 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* ExpressionNode::GetTypeName() { PopulateType(); return pTypeName; } + +// 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* ExpressionNode::GetTextValue() { PopulateTextValue(); return pTextValue; } + +// 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* ExpressionNode::GetErrorMessage() { return pErrorMessage; } + +// Factory function for creating the expression node at the root of a tree +HRESULT ExpressionNode::CreateExpressionNode(__in_z WCHAR* pExpression, ExpressionNode** ppExpressionNode) +{ + *ppExpressionNode = NULL; + HRESULT Status = CreateExpressionNodeHelper(pExpression, + pExpression, + 0, + NULL, + NULL, + NULL, + 0, + NULL, + ppExpressionNode); + if(FAILED(Status) && *ppExpressionNode == NULL) + { + + WCHAR pErrorMessage[MAX_ERROR]; + _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Error 0x%x while parsing expression", Status); + *ppExpressionNode = new ExpressionNode(pExpression, pErrorMessage); + Status = S_OK; + if(*ppExpressionNode == NULL) + Status = E_OUTOFMEMORY; + } + return Status; +} + +// 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 ExpressionNode::Expand(__in_z WCHAR* varToExpand) +{ + if(!ShouldExpandVariable(varToExpand)) + return S_FALSE; + if(pValue == NULL && pTypeCast == NULL) + return S_OK; + + // if the node evaluates to a type, then the children are static fields of the type + if(pValue == NULL) + return ExpandFields(NULL, varToExpand); + + // If the value is a null reference there is nothing to expand + HRESULT Status = S_OK; + BOOL isNull = TRUE; + ToRelease<ICorDebugValue> pInnerValue; + IfFailRet(DereferenceAndUnboxValue(pValue, &pInnerValue, &isNull)); + if(isNull) + { + return S_OK; + } + + + CorElementType corElemType; + IfFailRet(pValue->GetType(&corElemType)); + if (corElemType == ELEMENT_TYPE_SZARRAY) + { + //If its an array, add children representing the indexed elements + return ExpandSzArray(pInnerValue, varToExpand); + } + else if(corElemType == ELEMENT_TYPE_CLASS || corElemType == ELEMENT_TYPE_VALUETYPE) + { + // If its a class or struct (not counting string, array, or object) then add children representing + // the fields. + return ExpandFields(pInnerValue, varToExpand); + } + else + { + // nothing else expands + return S_OK; + } +} + +// Standard depth first search tree traversal pattern with a callback +VOID ExpressionNode::DFSVisit(ExpressionNodeVisitorCallback pFunc, VOID* pUserData, int depth) +{ + pFunc(this, depth, pUserData); + ExpressionNode* pCurChild = pChild; + while(pCurChild != NULL) + { + pCurChild->DFSVisit(pFunc, pUserData, depth+1); + pCurChild = pCurChild->pNextSibling; + } +} + + +// Creates a new expression with a given debuggee value and frame +ExpressionNode::ExpressionNode(__in_z WCHAR* pExpression, ICorDebugValue* pValue, ICorDebugILFrame* pFrame) +{ + Init(pValue, NULL, pFrame); + _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression); + _snwprintf_s(pRelativeExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression); +} + +// Creates a new expression that has an error and no value +ExpressionNode::ExpressionNode(__in_z WCHAR* pExpression, __in_z WCHAR* pErrorMessage) +{ + Init(NULL, NULL, NULL); + _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression); + _snwprintf_s(pRelativeExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pExpression); + _snwprintf_s(this->pErrorMessage, MAX_ERROR, _TRUNCATE, L"%s", pErrorMessage); +} + +// Creates a new child expression +ExpressionNode::ExpressionNode(__in_z WCHAR* pParentExpression, ChildKind ck, __in_z WCHAR* pRelativeExpression, ICorDebugValue* pValue, ICorDebugType* pType, ICorDebugILFrame* pFrame, UVCP_CONSTANT pDefaultValue, ULONG cchDefaultValue) +{ + Init(pValue, pType, pFrame); + if(ck == ChildKind_BaseClass) + { + _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, L"%s", pParentExpression); + } + else + { + _snwprintf_s(pAbsoluteExpression, MAX_EXPRESSION, _TRUNCATE, ck == ChildKind_Field ? L"%s.%s" : L"%s[%s]", pParentExpression, pRelativeExpression); + } + _snwprintf_s(this->pRelativeExpression, MAX_EXPRESSION, _TRUNCATE, ck == ChildKind_Index ? L"[%s]" : L"%s", pRelativeExpression); + this->pDefaultValue = pDefaultValue; + this->cchDefaultValue = cchDefaultValue; +} + +// Common member initialization for the constructors +VOID ExpressionNode::Init(ICorDebugValue* pValue, ICorDebugType* pTypeCast, ICorDebugILFrame* pFrame) +{ + this->pValue = pValue; + this->pTypeCast = pTypeCast; + this->pILFrame = pFrame; + pChild = NULL; + pNextSibling = NULL; + pTextValue[0] = 0; + pErrorMessage[0] = 0; + pAbsoluteExpression[0] = 0; + pRelativeExpression[0] = 0; + pTypeName[0] = 0; + + pDefaultValue = NULL; + cchDefaultValue = 0; + + // The ToRelease holders don't automatically AddRef + if(pILFrame != NULL) + pILFrame->AddRef(); + if(pTypeCast != NULL) + pTypeCast->AddRef(); + if(pValue != NULL) + { + pValue->AddRef(); + PopulateMetaDataImport(); + } +} + +// Retreves the correct IMetaDataImport for the type represented in this node and stores it +// in pMD. +HRESULT ExpressionNode::PopulateMetaDataImport() +{ + if(pMD != NULL) + return S_OK; + + HRESULT Status = S_OK; + ToRelease<ICorDebugType> pType; + if(pTypeCast != NULL) + { + pType = pTypeCast; + pType->AddRef(); + } + else + { + BOOL isNull; + ToRelease<ICorDebugValue> pInnerValue; + IfFailRet(DereferenceAndUnboxValue(pValue, &pInnerValue, &isNull)); + ToRelease<ICorDebugValue2> pValue2; + IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)); + + IfFailRet(pValue2->GetExactType(&pType)); + + // for array, pointer, and byref types we can't directly get a class, we must unwrap first + CorElementType et; + IfFailRet(pType->GetType(&et)); + while(et == ELEMENT_TYPE_ARRAY || et == ELEMENT_TYPE_SZARRAY || et == ELEMENT_TYPE_BYREF || et == ELEMENT_TYPE_PTR) + { + pType->GetFirstTypeParameter(&pType); + IfFailRet(pType->GetType(&et)); + } + } + ToRelease<ICorDebugClass> pClass; + IfFailRet(pType->GetClass(&pClass)); + ToRelease<ICorDebugModule> pModule; + IfFailRet(pClass->GetModule(&pModule)); + ToRelease<IUnknown> pMDUnknown; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + return Status; +} + +// Determines the string representation of pType and stores it in typeName +HRESULT ExpressionNode::CalculateTypeName(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen) +{ + HRESULT Status = S_OK; + + CorElementType corElemType; + IfFailRet(pType->GetType(&corElemType)); + + switch (corElemType) + { + //List of unsupported CorElementTypes: + //ELEMENT_TYPE_END = 0x0, + //ELEMENT_TYPE_VAR = 0x13, // a class type variable VAR <U1> + //ELEMENT_TYPE_GENERICINST = 0x15, // GENERICINST <generic type> <argCnt> <arg1> ... <argn> + //ELEMENT_TYPE_TYPEDBYREF = 0x16, // TYPEDREF (it takes no args) a typed referece to some other type + //ELEMENT_TYPE_MVAR = 0x1e, // a method type variable MVAR <U1> + //ELEMENT_TYPE_CMOD_REQD = 0x1F, // required C modifier : E_T_CMOD_REQD <mdTypeRef/mdTypeDef> + //ELEMENT_TYPE_CMOD_OPT = 0x20, // optional C modifier : E_T_CMOD_OPT <mdTypeRef/mdTypeDef> + //ELEMENT_TYPE_INTERNAL = 0x21, // INTERNAL <typehandle> + //ELEMENT_TYPE_MAX = 0x22, // first invalid element type + //ELEMENT_TYPE_MODIFIER = 0x40, + //ELEMENT_TYPE_SENTINEL = 0x01 | ELEMENT_TYPE_MODIFIER, // sentinel for varargs + //ELEMENT_TYPE_PINNED = 0x05 | ELEMENT_TYPE_MODIFIER, + default: + swprintf_s(typeName, typeNameLen, L"(Unhandled CorElementType: 0x%x)\0", corElemType); + break; + + case ELEMENT_TYPE_VALUETYPE: + case ELEMENT_TYPE_CLASS: + { + //Defaults in case we fail... + if(corElemType == ELEMENT_TYPE_VALUETYPE) swprintf_s(typeName, typeNameLen, L"struct\0"); + else swprintf_s(typeName, typeNameLen, L"class\0"); + + mdTypeDef typeDef; + ToRelease<ICorDebugClass> pClass; + if(SUCCEEDED(pType->GetClass(&pClass)) && SUCCEEDED(pClass->GetToken(&typeDef))) + { + ToRelease<ICorDebugModule> pModule; + IfFailRet(pClass->GetModule(&pModule)); + + ToRelease<IUnknown> pMDUnknown; + ToRelease<IMetaDataImport> pMD; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + + if(SUCCEEDED(NameForToken_s(TokenFromRid(typeDef, mdtTypeDef), pMD, g_mdName, mdNameLen, false))) + swprintf_s(typeName, typeNameLen, L"%s\0", g_mdName); + } + AddGenericArgs(pType, typeName, typeNameLen); + } + break; + case ELEMENT_TYPE_VOID: + swprintf_s(typeName, typeNameLen, L"void\0"); + break; + case ELEMENT_TYPE_BOOLEAN: + swprintf_s(typeName, typeNameLen, L"bool\0"); + break; + case ELEMENT_TYPE_CHAR: + swprintf_s(typeName, typeNameLen, L"char\0"); + break; + case ELEMENT_TYPE_I1: + swprintf_s(typeName, typeNameLen, L"signed byte\0"); + break; + case ELEMENT_TYPE_U1: + swprintf_s(typeName, typeNameLen, L"byte\0"); + break; + case ELEMENT_TYPE_I2: + swprintf_s(typeName, typeNameLen, L"short\0"); + break; + case ELEMENT_TYPE_U2: + swprintf_s(typeName, typeNameLen, L"unsigned short\0"); + break; + case ELEMENT_TYPE_I4: + swprintf_s(typeName, typeNameLen, L"int\0"); + break; + case ELEMENT_TYPE_U4: + swprintf_s(typeName, typeNameLen, L"unsigned int\0"); + break; + case ELEMENT_TYPE_I8: + swprintf_s(typeName, typeNameLen, L"long\0"); + break; + case ELEMENT_TYPE_U8: + swprintf_s(typeName, typeNameLen, L"unsigned long\0"); + break; + case ELEMENT_TYPE_R4: + swprintf_s(typeName, typeNameLen, L"float\0"); + break; + case ELEMENT_TYPE_R8: + swprintf_s(typeName, typeNameLen, L"double\0"); + break; + case ELEMENT_TYPE_OBJECT: + swprintf_s(typeName, typeNameLen, L"object\0"); + break; + case ELEMENT_TYPE_STRING: + swprintf_s(typeName, typeNameLen, L"string\0"); + break; + case ELEMENT_TYPE_I: + swprintf_s(typeName, typeNameLen, L"IntPtr\0"); + break; + case ELEMENT_TYPE_U: + swprintf_s(typeName, typeNameLen, L"UIntPtr\0"); + break; + case ELEMENT_TYPE_SZARRAY: + case ELEMENT_TYPE_ARRAY: + case ELEMENT_TYPE_BYREF: + case ELEMENT_TYPE_PTR: + { + // get a name for the type we are building from + ToRelease<ICorDebugType> pFirstParameter; + if(SUCCEEDED(pType->GetFirstTypeParameter(&pFirstParameter))) + CalculateTypeName(pFirstParameter, typeName, typeNameLen); + else + swprintf_s(typeName, typeNameLen, L"<unknown>\0"); + + // append the appropriate [], *, & + switch(corElemType) + { + case ELEMENT_TYPE_SZARRAY: + wcsncat_s(typeName, typeNameLen, L"[]", typeNameLen); + return S_OK; + case ELEMENT_TYPE_ARRAY: + { + ULONG32 rank = 0; + pType->GetRank(&rank); + wcsncat_s(typeName, typeNameLen, L"[", typeNameLen); + for(ULONG32 i = 0; i < rank - 1; i++) + { + // todo- could we print out exact boundaries? + wcsncat_s(typeName, typeNameLen, L",", typeNameLen); + } + wcsncat_s(typeName, typeNameLen, L"]", typeNameLen); + } + return S_OK; + case ELEMENT_TYPE_BYREF: + wcsncat_s(typeName, typeNameLen, L"&", typeNameLen); + return S_OK; + case ELEMENT_TYPE_PTR: + wcsncat_s(typeName, typeNameLen, L"*", typeNameLen); + return S_OK; + } + } + break; + case ELEMENT_TYPE_FNPTR: + swprintf_s(typeName, typeNameLen, L"*(...)"); + break; + case ELEMENT_TYPE_TYPEDBYREF: + swprintf_s(typeName, typeNameLen, L"typedbyref"); + break; + } + return S_OK; +} + + +// Appends angle brackets and the generic argument list to a type name +HRESULT ExpressionNode::AddGenericArgs(ICorDebugType * pType, __inout_ecount(typeNameLen) WCHAR* typeName, DWORD typeNameLen) +{ + bool isFirst = true; + ToRelease<ICorDebugTypeEnum> pTypeEnum; + if(SUCCEEDED(pType->EnumerateTypeParameters(&pTypeEnum))) + { + ULONG numTypes = 0; + ToRelease<ICorDebugType> pCurrentTypeParam; + + while(SUCCEEDED(pTypeEnum->Next(1, &pCurrentTypeParam, &numTypes))) + { + if(numTypes == 0) break; + + if(isFirst) + { + isFirst = false; + wcsncat_s(typeName, typeNameLen, L"<", typeNameLen); + } + else wcsncat_s(typeName, typeNameLen, L",", typeNameLen); + + WCHAR typeParamName[mdNameLen]; + typeParamName[0] = L'\0'; + CalculateTypeName(pCurrentTypeParam, typeParamName, mdNameLen); + wcsncat_s(typeName, typeNameLen, typeParamName, typeNameLen); + } + if(!isFirst) + wcsncat_s(typeName, typeNameLen, L">", typeNameLen); + } + + return S_OK; +} + +// Determines the text name for the type of this node and caches it +HRESULT ExpressionNode::PopulateType() +{ + HRESULT Status = S_OK; + if(pTypeName[0] != 0) + return S_OK; + + //default value + swprintf_s(pTypeName, MAX_EXPRESSION, L"<unknown>"); + + // if we are displaying this type as a specific sub-type, use that + if(pTypeCast != NULL) + return CalculateTypeName(pTypeCast, pTypeName, MAX_EXPRESSION); + + // if there is no value then either we succesfully already determined the type + // name, or this node has no value or type and thus no type name. + if(pValue == NULL) + return S_OK; + + // get the type from the value and then calculate a name based on that + ToRelease<ICorDebugType> pType; + ToRelease<ICorDebugValue2> pValue2; + if(SUCCEEDED(pValue->QueryInterface(IID_ICorDebugValue2, (void**) &pValue2)) && SUCCEEDED(pValue2->GetExactType(&pType))) + return CalculateTypeName(pType, pTypeName, MAX_EXPRESSION); + + return S_OK; +} + +// 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 ExpressionNode::AddChild(ExpressionNode* pNewChild) +{ + if(pChild == NULL) + pChild = pNewChild; + else + { + ExpressionNode* pCursor = pChild; + while(pCursor->pNextSibling != NULL) + pCursor = pCursor->pNextSibling; + pCursor->pNextSibling = pNewChild; + } +} + +// Helper that determines if the current node is on the path of nodes represented by +// expression varToExpand +BOOL ExpressionNode::ShouldExpandVariable(__in_z WCHAR* varToExpand) +{ + if(pAbsoluteExpression == NULL || varToExpand == NULL) return FALSE; + + // if there is a cast operation, move past it + WCHAR* pEndCast = _wcschr(varToExpand, L')'); + varToExpand = (pEndCast == NULL) ? varToExpand : pEndCast+1; + + size_t varToExpandLen = _wcslen(varToExpand); + size_t currentExpansionLen = _wcslen(pAbsoluteExpression); + if(currentExpansionLen > varToExpandLen) return FALSE; + if(currentExpansionLen < varToExpandLen && + varToExpand[currentExpansionLen] != L'.' && + varToExpand[currentExpansionLen] != L'[') + return FALSE; + if(_wcsncmp(pAbsoluteExpression, varToExpand, currentExpansionLen) != 0) return FALSE; + + return TRUE; +} + +// Expands this array node by creating child nodes with expressions refering to individual array elements +HRESULT ExpressionNode::ExpandSzArray(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand) +{ + HRESULT Status = S_OK; + ToRelease<ICorDebugArrayValue> pArrayValue; + IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID*) &pArrayValue)); + + ULONG32 nRank; + IfFailRet(pArrayValue->GetRank(&nRank)); + if (nRank != 1) + { + _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Multi-dimensional arrays NYI"); + return E_UNEXPECTED; + } + + ULONG32 cElements; + IfFailRet(pArrayValue->GetCount(&cElements)); + + //TODO: do we really want all the elements? This could be huge! + for (ULONG32 i=0; i < cElements; i++) + { + WCHAR index[20]; + swprintf_s(index, 20, L"%d", i); + + ToRelease<ICorDebugValue> pElementValue; + IfFailRet(pArrayValue->GetElementAtPosition(i, &pElementValue)); + ExpressionNode* pExpr = new ExpressionNode(pAbsoluteExpression, ChildKind_Index, index, pElementValue, NULL, pILFrame); + AddChild(pExpr); + pExpr->Expand(varToExpand); + } + return S_OK; +} + +// Expands this struct/class node by creating child nodes with expressions refering to individual field values +// and one node for the basetype value +HRESULT ExpressionNode::ExpandFields(ICorDebugValue* pInnerValue, __in_z WCHAR* varToExpand) +{ + HRESULT Status = S_OK; + + mdTypeDef currentTypeDef; + ToRelease<ICorDebugClass> pClass; + ToRelease<ICorDebugType> pType; + ToRelease<ICorDebugModule> pModule; + if(pTypeCast == NULL) + { + ToRelease<ICorDebugValue2> pValue2; + IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)); + IfFailRet(pValue2->GetExactType(&pType)); + } + else + { + pType = pTypeCast; + pType->AddRef(); + } + IfFailRet(pType->GetClass(&pClass)); + IfFailRet(pClass->GetModule(&pModule)); + IfFailRet(pClass->GetToken(¤tTypeDef)); + + ToRelease<IUnknown> pMDUnknown; + ToRelease<IMetaDataImport> pMD; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + + // If the current type has a base type that isn't object, enum, or ValueType then add a node for the base type + WCHAR baseTypeName[mdNameLen] = L"\0"; + ToRelease<ICorDebugType> pBaseType; + ExpressionNode* pBaseTypeNode = NULL; + if(SUCCEEDED(pType->GetBase(&pBaseType)) && pBaseType != NULL && SUCCEEDED(CalculateTypeName(pBaseType, baseTypeName, mdNameLen))) + { + if(_wcsncmp(baseTypeName, L"System.Enum", 11) == 0) + return S_OK; + else if(_wcsncmp(baseTypeName, L"System.Object", 13) != 0 && _wcsncmp(baseTypeName, L"System.ValueType", 16) != 0) + { + pBaseTypeNode = new ExpressionNode(pAbsoluteExpression, ChildKind_BaseClass, L"<baseclass>", pInnerValue, pBaseType, pILFrame); + AddChild(pBaseTypeNode); + } + } + + // add nodes for all the fields in this object + ULONG numFields = 0; + HCORENUM fEnum = NULL; + mdFieldDef fieldDef; + BOOL fieldExpanded = FALSE; + while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) + { + mdTypeDef classDef = 0; + ULONG nameLen = 0; + DWORD fieldAttr = 0; + WCHAR mdName[mdNameLen]; + WCHAR typeName[mdNameLen]; + CorElementType fieldDefaultValueEt; + UVCP_CONSTANT pDefaultValue; + ULONG cchDefaultValue; + if(SUCCEEDED(pMD->GetFieldProps(fieldDef, &classDef, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, (DWORD*)&fieldDefaultValueEt, &pDefaultValue, &cchDefaultValue))) + { + ToRelease<ICorDebugType> pFieldType; + ToRelease<ICorDebugValue> pFieldVal; + + // static fields (of any kind - AppDomain, thread, context, RVA) + if (fieldAttr & fdStatic) + { + pType->GetStaticFieldValue(fieldDef, pILFrame, &pFieldVal); + } + // non-static fields on an object instance + else if(pInnerValue != NULL) + { + ToRelease<ICorDebugObjectValue> pObjValue; + if (SUCCEEDED(pInnerValue->QueryInterface(IID_ICorDebugObjectValue, (LPVOID*) &pObjValue))) + pObjValue->GetFieldValue(pClass, fieldDef, &pFieldVal); + } + // skip over non-static fields on static types + else + { + continue; + } + + // we didn't get a value yet and there is default value available + // need to calculate the type because there won't be a ICorDebugValue to derive it from + if(pFieldVal == NULL && pDefaultValue != NULL) + { + FindTypeFromElementType(fieldDefaultValueEt, &pFieldType); + } + + ExpressionNode* pNewChildNode = new ExpressionNode(pAbsoluteExpression, ChildKind_Field, mdName, pFieldVal, pFieldType, pILFrame, pDefaultValue, cchDefaultValue); + AddChild(pNewChildNode); + if(pNewChildNode->Expand(varToExpand) != S_FALSE) + fieldExpanded = TRUE; + } + } + pMD->CloseEnum(fEnum); + + // Only recurse to expand the base type if all of these hold: + // 1) base type exists + // 2) no field was expanded + // 3) the non-casting portion of the varToExpand doesn't match the current expression + // OR the cast exists and doesn't match + + if(pBaseTypeNode == NULL) return Status; + if(fieldExpanded) return Status; + + WCHAR* pEndCast = _wcschr(varToExpand, L')'); + WCHAR* pNonCast = (pEndCast == NULL) ? varToExpand : pEndCast+1; + if(_wcscmp(pNonCast, pAbsoluteExpression) != 0) + { + pBaseTypeNode->Expand(varToExpand); + return Status; + } + + if(varToExpand[0] == L'(' && pEndCast != NULL) + { + int cchCastTypeName = ((int)(pEndCast-1)-(int)varToExpand)/2; + PopulateType(); + if(_wcslen(pTypeName) != (cchCastTypeName) || + _wcsncmp(varToExpand+1, pTypeName, cchCastTypeName) != 0) + { + pBaseTypeNode->Expand(varToExpand); + return Status; + } + } + + return Status; +} + + +// Value Population functions + +//Helper for unwrapping values +HRESULT ExpressionNode::DereferenceAndUnboxValue(ICorDebugValue * pInputValue, ICorDebugValue** ppOutputValue, BOOL * pIsNull) +{ + HRESULT Status = S_OK; + *ppOutputValue = NULL; + if(pIsNull != NULL) *pIsNull = FALSE; + + ToRelease<ICorDebugReferenceValue> pReferenceValue; + Status = pInputValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue); + if (SUCCEEDED(Status)) + { + BOOL isNull = FALSE; + IfFailRet(pReferenceValue->IsNull(&isNull)); + if(!isNull) + { + ToRelease<ICorDebugValue> pDereferencedValue; + IfFailRet(pReferenceValue->Dereference(&pDereferencedValue)); + return DereferenceAndUnboxValue(pDereferencedValue, ppOutputValue); + } + else + { + if(pIsNull != NULL) *pIsNull = TRUE; + *ppOutputValue = pInputValue; + (*ppOutputValue)->AddRef(); + return S_OK; + } + } + + ToRelease<ICorDebugBoxValue> pBoxedValue; + Status = pInputValue->QueryInterface(IID_ICorDebugBoxValue, (LPVOID*) &pBoxedValue); + if (SUCCEEDED(Status)) + { + ToRelease<ICorDebugObjectValue> pUnboxedValue; + IfFailRet(pBoxedValue->GetObject(&pUnboxedValue)); + return DereferenceAndUnboxValue(pUnboxedValue, ppOutputValue); + } + *ppOutputValue = pInputValue; + (*ppOutputValue)->AddRef(); + return S_OK; +} + +// Returns TRUE if the value derives from System.Enum +BOOL ExpressionNode::IsEnum(ICorDebugValue * pInputValue) +{ + ToRelease<ICorDebugValue> pValue; + if(FAILED(DereferenceAndUnboxValue(pInputValue, &pValue, NULL))) return FALSE; + + WCHAR baseTypeName[mdNameLen]; + ToRelease<ICorDebugValue2> pValue2; + ToRelease<ICorDebugType> pType; + ToRelease<ICorDebugType> pBaseType; + + if(FAILED(pValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2))) return FALSE; + if(FAILED(pValue2->GetExactType(&pType))) return FALSE; + if(FAILED(pType->GetBase(&pBaseType)) || pBaseType == NULL) return FALSE; + if(FAILED(CalculateTypeName(pBaseType, baseTypeName, mdNameLen))) return FALSE; + + return (_wcsncmp(baseTypeName, L"System.Enum", 11) == 0); +} + +// Calculates the value text for nodes that have enum values +HRESULT ExpressionNode::PopulateEnumValue(ICorDebugValue* pEnumValue, BYTE* enumValue) +{ + HRESULT Status = S_OK; + + mdTypeDef currentTypeDef; + ToRelease<ICorDebugClass> pClass; + ToRelease<ICorDebugValue2> pValue2; + ToRelease<ICorDebugType> pType; + ToRelease<ICorDebugModule> pModule; + IfFailRet(pEnumValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)); + IfFailRet(pValue2->GetExactType(&pType)); + IfFailRet(pType->GetClass(&pClass)); + IfFailRet(pClass->GetModule(&pModule)); + IfFailRet(pClass->GetToken(¤tTypeDef)); + + ToRelease<IUnknown> pMDUnknown; + ToRelease<IMetaDataImport> pMD; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + + + //First, we need to figure out the underlying enum type so that we can correctly type cast the raw values of each enum constant + //We get that from the non-static field of the enum variable (I think the field is called __value or something similar) + ULONG numFields = 0; + HCORENUM fEnum = NULL; + mdFieldDef fieldDef; + CorElementType enumUnderlyingType = ELEMENT_TYPE_END; + while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) + { + DWORD fieldAttr = 0; + PCCOR_SIGNATURE pSignatureBlob = NULL; + ULONG sigBlobLength = 0; + if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, NULL, 0, NULL, &fieldAttr, &pSignatureBlob, &sigBlobLength, NULL, NULL, NULL))) + { + if((fieldAttr & fdStatic) == 0) + { + CorSigUncompressCallingConv(pSignatureBlob); + enumUnderlyingType = CorSigUncompressElementType(pSignatureBlob); + break; + } + } + } + pMD->CloseEnum(fEnum); + + + //Now that we know the underlying enum type, let's decode the enum variable into OR-ed, human readable enum contants + fEnum = NULL; + bool isFirst = true; + ULONG64 remainingValue = *((ULONG64*)enumValue); + WCHAR* pTextValueCursor = pTextValue; + DWORD cchTextValueCursor = MAX_EXPRESSION; + while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) + { + ULONG nameLen = 0; + DWORD fieldAttr = 0; + WCHAR mdName[mdNameLen]; + WCHAR typeName[mdNameLen]; + UVCP_CONSTANT pRawValue = NULL; + ULONG rawValueLength = 0; + if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, NULL, &pRawValue, &rawValueLength))) + { + DWORD enumValueRequiredAttributes = fdPublic | fdStatic | fdLiteral | fdHasDefault; + if((fieldAttr & enumValueRequiredAttributes) != enumValueRequiredAttributes) + continue; + + ULONG64 currentConstValue = 0; + switch (enumUnderlyingType) + { + case ELEMENT_TYPE_CHAR: + case ELEMENT_TYPE_I1: + currentConstValue = (ULONG64)(*((CHAR*)pRawValue)); + break; + case ELEMENT_TYPE_U1: + currentConstValue = (ULONG64)(*((BYTE*)pRawValue)); + break; + case ELEMENT_TYPE_I2: + currentConstValue = (ULONG64)(*((SHORT*)pRawValue)); + break; + case ELEMENT_TYPE_U2: + currentConstValue = (ULONG64)(*((USHORT*)pRawValue)); + break; + case ELEMENT_TYPE_I4: + currentConstValue = (ULONG64)(*((INT32*)pRawValue)); + break; + case ELEMENT_TYPE_U4: + currentConstValue = (ULONG64)(*((UINT32*)pRawValue)); + break; + case ELEMENT_TYPE_I8: + currentConstValue = (ULONG64)(*((LONG*)pRawValue)); + break; + case ELEMENT_TYPE_U8: + currentConstValue = (ULONG64)(*((ULONG*)pRawValue)); + break; + case ELEMENT_TYPE_I: + currentConstValue = (ULONG64)(*((int*)pRawValue)); + break; + case ELEMENT_TYPE_U: + case ELEMENT_TYPE_R4: + case ELEMENT_TYPE_R8: + // Technically U and the floating-point ones are options in the CLI, but not in the CLS or C#, so these are NYI + default: + currentConstValue = 0; + } + + if((currentConstValue == remainingValue) || ((currentConstValue != 0) && ((currentConstValue & remainingValue) == currentConstValue))) + { + remainingValue &= ~currentConstValue; + DWORD charsCopied = 0; + if(isFirst) + { + charsCopied = _snwprintf_s(pTextValueCursor, cchTextValueCursor, _TRUNCATE, L"= %s", mdName); + isFirst = false; + } + else + charsCopied = _snwprintf_s(pTextValueCursor, cchTextValueCursor, _TRUNCATE, L" | %s", mdName); + + // if an error or truncation occurred, stop copying + if(charsCopied == -1) + { + cchTextValueCursor = 0; + pTextValueCursor = NULL; + } + else + { + // charsCopied is the number of characters copied, not counting the terminating null + // this advances the cursor to point right at the terminating null so that future copies + // will concatenate the string + pTextValueCursor += charsCopied; + cchTextValueCursor -= charsCopied; + } + } + } + } + pMD->CloseEnum(fEnum); + + return Status; +} + +// Helper that caches the textual value for nodes that evaluate to a string object +HRESULT ExpressionNode::GetDebuggeeStringValue(ICorDebugValue* pInputValue, __inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer) +{ + HRESULT Status; + + ToRelease<ICorDebugStringValue> pStringValue; + IfFailRet(pInputValue->QueryInterface(IID_ICorDebugStringValue, (LPVOID*) &pStringValue)); + + ULONG32 cchValueReturned; + IfFailRet(pStringValue->GetString(cchBuffer, &cchValueReturned, wszBuffer)); + + return S_OK; +} + +// Retrieves the string value for a constant +HRESULT ExpressionNode::GetConstantStringValue(__inout_ecount(cchBuffer) WCHAR* wszBuffer, DWORD cchBuffer) +{ + // The string encoded in metadata isn't null-terminated + // so we need to copy it to a null terminated buffer + DWORD copyLen = cchDefaultValue; + if(copyLen > cchBuffer-1) + copyLen = cchDefaultValue; + + wcsncpy_s(wszBuffer, cchBuffer, (WCHAR*)pDefaultValue, copyLen); + return S_OK; +} + +// Helper that caches the textual value for nodes that evaluate to array objects +HRESULT ExpressionNode::PopulateSzArrayValue(ICorDebugValue* pInputValue) +{ + HRESULT Status = S_OK; + + ToRelease<ICorDebugArrayValue> pArrayValue; + IfFailRet(pInputValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID*) &pArrayValue)); + + ULONG32 nRank; + IfFailRet(pArrayValue->GetRank(&nRank)); + if (nRank != 1) + { + _snwprintf_s(pErrorMessage, MAX_EXPRESSION, _TRUNCATE, L"Multi-dimensional arrays NYI"); + return E_UNEXPECTED; + } + + ULONG32 cElements; + IfFailRet(pArrayValue->GetCount(&cElements)); + + if (cElements == 0) + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"(empty)"); + else if (cElements == 1) + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"(1 element)"); + else + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"(%d elements)", cElements); + + return S_OK; +} + +// Helper that caches the textual value for nodes of any type +HRESULT ExpressionNode::PopulateTextValueHelper() +{ + HRESULT Status = S_OK; + + BOOL isNull = TRUE; + ToRelease<ICorDebugValue> pInnerValue; + CorElementType corElemType; + ULONG32 cbSize = 0; + if(pValue != NULL) + { + IfFailRet(DereferenceAndUnboxValue(pValue, &pInnerValue, &isNull)); + + if(isNull) + { + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= null"); + return S_OK; + } + IfFailRet(pInnerValue->GetSize(&cbSize)); + IfFailRet(pInnerValue->GetType(&corElemType)); + } + else if(pDefaultValue != NULL) + { + if(pTypeCast == NULL) + { + // this shouldn't happen, but just print nothing if it does + return S_OK; + } + // This works around an irritating issue in ICorDebug. For default values + // we have to construct the ICorDebugType ourselves, however ICorDebug + // doesn't allow type construction using the correct element types. The + // caller must past CLASS or VALUETYPE even when a more specific short + // form element type is applicable. That means that later, here, we get + // back the wrong answer. To work around this we format the type as a + // string, and check it against all the known types. That allows us determine + // everything except VALUETYPE/CLASS. Thankfully that distinction is the + // one piece of data ICorDebugType will tell us if needed. + if(FAILED(GetCanonicalElementTypeForTypeName(GetTypeName(), &corElemType))) + { + pTypeCast->GetType(&corElemType); + } + + switch(corElemType) + { + case ELEMENT_TYPE_BOOLEAN: + case ELEMENT_TYPE_I1: + case ELEMENT_TYPE_U1: + cbSize = 1; + break; + + case ELEMENT_TYPE_CHAR: + case ELEMENT_TYPE_I2: + case ELEMENT_TYPE_U2: + cbSize = 2; + break; + + case ELEMENT_TYPE_I: + case ELEMENT_TYPE_U: + case ELEMENT_TYPE_I4: + case ELEMENT_TYPE_U4: + case ELEMENT_TYPE_R4: + cbSize = 4; + break; + + case ELEMENT_TYPE_I8: + case ELEMENT_TYPE_U8: + case ELEMENT_TYPE_R8: + cbSize = 8; + break; + } + } + + if (corElemType == ELEMENT_TYPE_STRING) + { + WCHAR buffer[MAX_EXPRESSION]; + buffer[0] = L'\0'; + if(pInnerValue != NULL) + GetDebuggeeStringValue(pInnerValue, buffer, MAX_EXPRESSION); + else + GetConstantStringValue(buffer, MAX_EXPRESSION); + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= \"%s\"", buffer); + } + else if (corElemType == ELEMENT_TYPE_SZARRAY) + { + return PopulateSzArrayValue(pInnerValue); + } + + + ArrayHolder<BYTE> rgbValue = new BYTE[cbSize]; + memset(rgbValue.GetPtr(), 0, cbSize * sizeof(BYTE)); + if(pInnerValue != NULL) + { + ToRelease<ICorDebugGenericValue> pGenericValue; + IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugGenericValue, (LPVOID*) &pGenericValue)); + IfFailRet(pGenericValue->GetValue((LPVOID) &(rgbValue[0]))); + } + else + { + memcpy((LPVOID) &(rgbValue[0]), pDefaultValue, cbSize); + } + + //TODO: this should really be calculated from the type + if(pInnerValue != NULL && IsEnum(pInnerValue)) + { + Status = PopulateEnumValue(pInnerValue, rgbValue); + return Status; + } + + switch (corElemType) + { + default: + _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Unhandled CorElementType: 0x%x", corElemType); + Status = E_FAIL; + break; + + case ELEMENT_TYPE_PTR: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"<pointer>"); + break; + + case ELEMENT_TYPE_FNPTR: + { + CORDB_ADDRESS addr = 0; + ToRelease<ICorDebugReferenceValue> pReferenceValue = NULL; + if(SUCCEEDED(pInnerValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue))) + pReferenceValue->GetValue(&addr); + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"<function pointer 0x%x>", addr); + } + break; + + case ELEMENT_TYPE_VALUETYPE: + case ELEMENT_TYPE_CLASS: + case ELEMENT_TYPE_OBJECT: + ULONG64 pointer; + if(pInnerValue != NULL && SUCCEEDED(pInnerValue->GetAddress(&pointer))) + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"@ 0x%p", (void *) pointer); + break; + + case ELEMENT_TYPE_BOOLEAN: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %s", rgbValue[0] == 0 ? L"false" : L"true"); + break; + + case ELEMENT_TYPE_CHAR: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= '%C'", *(WCHAR *) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I1: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(char*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U1: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(unsigned char*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I2: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %hd", *(short*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U2: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %hu", *(unsigned short*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(int*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %u", *(unsigned int*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I4: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %d", *(int*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U4: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %u", *(unsigned int*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_I8: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %I64d", *(__int64*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_U8: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %I64u", *(unsigned __int64*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_R4: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"= %f", (double) *(float*) &(rgbValue[0])); + break; + + case ELEMENT_TYPE_R8: + _snwprintf_s(pTextValue, MAX_EXPRESSION, _TRUNCATE, L"%f", *(double*) &(rgbValue[0])); + break; + + // TODO: The following corElementTypes are not yet implemented here. Array + // might be interesting to add, though the others may be of rather limited use: + // ELEMENT_TYPE_ARRAY = 0x14, // MDARRAY <type> <rank> <bcount> <bound1> ... <lbcount> <lb1> ... + // + // ELEMENT_TYPE_GENERICINST = 0x15, // GENERICINST <generic type> <argCnt> <arg1> ... <argn> + } + + return Status; +} + +// Caches the textual value of this node +HRESULT ExpressionNode::PopulateTextValue() +{ + if(pErrorMessage[0] != 0) + return E_UNEXPECTED; + if(pValue == NULL && pDefaultValue == NULL) + return S_OK; + HRESULT Status = PopulateTextValueHelper(); + if(FAILED(Status) && pErrorMessage[0] == 0) + { + _snwprintf_s(pErrorMessage, MAX_ERROR, _TRUNCATE, L"Error in PopulateTextValueHelper: 0x%x", Status); + } + return Status; +} + + +// Expression parsing and search + +//Callback that searches a frame to determine if it contains a local variable or parameter of a given name +VOID ExpressionNode::EvaluateExpressionFrameScanCallback(ICorDebugFrame* pFrame, VOID* pUserData) +{ + EvaluateExpressionFrameScanData* pData = (EvaluateExpressionFrameScanData*)pUserData; + + // we already found what we were looking for, just continue + if(pData->pFoundValue != NULL) + return; + + // if any of these fail we just continue on + // querying for ILFrame will frequently fail because many frames aren't IL + ToRelease<ICorDebugILFrame> pILFrame; + HRESULT Status = pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame); + if (FAILED(Status)) + { + return; + } + // we need to save off the first frame we find regardless of whether we find the + // local or not. We might need this frame later for static field lookup. + if(pData->pFirstFrame == NULL) + { + pData->pFirstFrame = pILFrame; + pData->pFirstFrame->AddRef(); + } + // not all IL frames map to an assembly (ex. LCG) + ToRelease<ICorDebugFunction> pFunction; + Status = pFrame->GetFunction(&pFunction); + if (FAILED(Status)) + { + return; + } + // from here down shouldn't generally fail, but just in case + mdMethodDef methodDef; + Status = pFunction->GetToken(&methodDef); + if (FAILED(Status)) + { + return; + } + ToRelease<ICorDebugModule> pModule; + Status = pFunction->GetModule(&pModule); + if (FAILED(Status)) + { + return; + } + ToRelease<IUnknown> pMDUnknown; + Status = pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown); + if (FAILED(Status)) + { + return; + } + ToRelease<IMetaDataImport> pMD; + Status = pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD); + if (FAILED(Status)) + { + return; + } + + pData->pFoundFrame = pILFrame; + pData->pFoundFrame->AddRef(); + // Enumerate all the parameters + EnumerateParameters(pMD, methodDef, pILFrame, EvaluateExpressionVariableScanCallback, pUserData); + // Enumerate all the locals + EnumerateLocals(pMD, methodDef, pILFrame, EvaluateExpressionVariableScanCallback, pUserData); + + // if we didn't find it in this frame then clear the frame back out + if(pData->pFoundValue == NULL) + { + pData->pFoundFrame = NULL; + } + + return; +} + +//Callback checks to see if a given local/parameter has name pName +VOID ExpressionNode::EvaluateExpressionVariableScanCallback(ICorDebugValue* pValue, __in_z WCHAR* pName, __out_z WCHAR* pErrorMessage, VOID* pUserData) +{ + EvaluateExpressionFrameScanData* pData = (EvaluateExpressionFrameScanData*)pUserData; + if(_wcscmp(pName, pData->pIdentifier) == 0) + { + // found it + pData->pFoundValue = pValue; + pValue->AddRef(); + } + return; +} + +//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 +// +// +// Valid combinations of state comming into this method: +// The expression up to charactersParsed isn't recognized yet: +// pParsedValue = pParsedType = pParsedDefaultValue = NULL +// cchParsedDefaultValue = 0 +// The expression up to charactersParsed is a recognized type: +// pParsedType = <parsed type> +// pParsedValue = pParsedDefaultValue = NULL +// cchParsedDefaultValue = 0 +// The expression up to charactersParsed is a recognized value in the debuggee: +// pParsedValue = <parsed value> +// pParsedType = pParsedDefaultValue = NULL +// cchParsedDefaultValue = 0 +// The expression up to charactersParsed is a recognized default value stored in metadata: +// pParsedValue = NULL +// pParsedType = <type calculated from metadata> +// pParsedDefaultValue = <value from metadata> +// cchParsedDefaultValue = <size of metadata value> +// +// +// REFACTORING NOTE: This method is very similar (but not identical) to the expansion logic +// in ExpressionNode. The primary difference is that the nodes expand all +// fields/indices whereas this function only expands along a precise route. +// If the ExpressionNode code where enhanced to support expanding precisely +// large portions of this function could be disposed of. As soon as the function +// matched the initial name it could create an ExpressionNode and then use the +// ExpressionNode expansion functions to drill down to the actual node required. +// Also need to make sure the nodes manage lifetime ok when a parent is destroyed +// but a child node is still referenced. +HRESULT ExpressionNode::CreateExpressionNodeHelper(__in_z WCHAR* pExpression, + __in_z WCHAR* pExpressionParseRemainder, + DWORD charactersParsed, + ICorDebugValue* pParsedValue, + ICorDebugType* pParsedType, + UVCP_CONSTANT pParsedDefaultValue, + ULONG cchParsedDefaultValue, + ICorDebugILFrame* pFrame, + ExpressionNode** ppExpressionNode) +{ + HRESULT Status = S_OK; + WCHAR* pExpressionCursor = pExpressionParseRemainder; + DWORD currentCharsParsed = charactersParsed; + WCHAR pIdentifier[mdNameLen]; + pIdentifier[0] = 0; + BOOL isArray = FALSE; + WCHAR pResultBuffer[MAX_EXPRESSION]; + + // Get the next name from the expression string + if(FAILED(Status = ParseNextIdentifier(&pExpressionCursor, pIdentifier, mdNameLen, pResultBuffer, MAX_EXPRESSION, ¤tCharsParsed, &isArray))) + { + *ppExpressionNode = new ExpressionNode(pExpression, pResultBuffer); + if(*ppExpressionNode == NULL) + return E_OUTOFMEMORY; + else + return S_OK; + } + + // we've gone as far as we need, nothing left to parse + if(Status == S_FALSE) + { + ToRelease<ICorDebugValue> pValue; + *ppExpressionNode = new ExpressionNode(pExpression, ChildKind_BaseClass, pExpression, pParsedValue, pParsedType, pFrame, pParsedDefaultValue, cchParsedDefaultValue); + if(*ppExpressionNode == NULL) + return E_OUTOFMEMORY; + else + return S_OK; + } + // if we are just starting and have no context then we need to search locals/parameters/type names + else if(pParsedValue == NULL && pParsedType == NULL) + { + // the first identifier must be a name, not an indexing expression + if(isArray) + { + *ppExpressionNode = new ExpressionNode(pExpression, L"Expression must begin with a local variable, parameter, or fully qualified type name"); + return S_OK; + } + + // scan for root on stack + EvaluateExpressionFrameScanData data; + data.pIdentifier = pIdentifier; + data.pFoundValue = NULL; + data.pFoundFrame = NULL; + data.pFirstFrame = NULL; + data.pErrorMessage = pResultBuffer; + data.cchErrorMessage = MAX_EXPRESSION; + EnumerateFrames(EvaluateExpressionFrameScanCallback, (VOID*) &data); + + if(data.pFoundValue != NULL) + { + // found the root, now recurse along the expression + return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, data.pFoundValue, NULL, NULL, 0, data.pFoundFrame, ppExpressionNode); + } + + // didn't find it - search the type table for a matching name + WCHAR pName[MAX_EXPRESSION]; + while(true) + { + wcsncpy_s(pName, MAX_EXPRESSION, pExpression, currentCharsParsed); + ToRelease<ICorDebugType> pType; + if(SUCCEEDED(FindTypeByName(pName, &pType))) + return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, NULL, pType, NULL, 0, data.pFirstFrame, ppExpressionNode); + + if(FAILED(Status = ParseNextIdentifier(&pExpressionCursor, pIdentifier, mdNameLen, pResultBuffer, MAX_EXPRESSION, ¤tCharsParsed, &isArray))) + { + *ppExpressionNode = new ExpressionNode(pExpression, pResultBuffer); + return S_OK; + } + else if(Status == S_FALSE) + { + break; + } + } + + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"No expression prefix could not be matched to an existing type, parameter, or local"); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + + // we've got some context from an earlier portion of the search, now just need to continue + // by dereferencing and indexing until we reach the end of the expression + + // Figure out the type, module, and metadata from our context information + ToRelease<ICorDebugType> pType; + BOOL isNull = TRUE; + ToRelease<ICorDebugValue> pInnerValue = NULL; + if(pParsedValue != NULL) + { + IfFailRet(DereferenceAndUnboxValue(pParsedValue, &pInnerValue, &isNull)); + + if(isNull) + { + WCHAR parsedExpression[MAX_EXPRESSION]; + wcsncpy_s(parsedExpression, MAX_EXPRESSION, pExpression, charactersParsed); + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"Dereferencing \'%s\' throws NullReferenceException", parsedExpression); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + + ToRelease<ICorDebugValue2> pValue2; + IfFailRet(pInnerValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2)); + IfFailRet(pValue2->GetExactType(&pType)); + CorElementType et; + IfFailRet(pType->GetType(&et)); + while(et == ELEMENT_TYPE_ARRAY || et == ELEMENT_TYPE_SZARRAY || et == ELEMENT_TYPE_BYREF || et == ELEMENT_TYPE_PTR) + { + pType->GetFirstTypeParameter(&pType); + IfFailRet(pType->GetType(&et)); + } + } + else + { + pType = pParsedType; + pType->AddRef(); + } + ToRelease<ICorDebugClass> pClass; + IfFailRet(pType->GetClass(&pClass)); + ToRelease<ICorDebugModule> pModule; + IfFailRet(pClass->GetModule(&pModule)); + mdTypeDef currentTypeDef; + IfFailRet(pClass->GetToken(¤tTypeDef)); + + ToRelease<IUnknown> pMDUnknown; + ToRelease<IMetaDataImport> pMD; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + + + // if we are searching along and this is an array index dereference + if(isArray) + { + ToRelease<ICorDebugArrayValue> pArrayValue; + if(pInnerValue == NULL || FAILED(Status = pInnerValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID*) &pArrayValue))) + { + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"Index notation only supported for instances of an array type"); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + + ULONG32 nRank; + IfFailRet(pArrayValue->GetRank(&nRank)); + if (nRank != 1) + { + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"Multi-dimensional arrays NYI"); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + + int index = -1; + if(swscanf_s(pIdentifier, L"%d", &index) != 1) + { + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"Failed to parse expression, missing or invalid index expression at character %d", charactersParsed+1); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + + ULONG32 cElements; + IfFailRet(pArrayValue->GetCount(&cElements)); + if(index < 0 || (ULONG32)index >= cElements) + { + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"Index is out of range for this array"); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + + ToRelease<ICorDebugValue> pElementValue; + IfFailRet(pArrayValue->GetElementAtPosition(index, &pElementValue)); + return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, pElementValue, NULL, NULL, 0, pFrame, ppExpressionNode); + } + // if we are searching along and this is field dereference + else + { + ToRelease<ICorDebugType> pBaseType = pType; + pBaseType->AddRef(); + + while(pBaseType != NULL) + { + // get the current base type class/token/MD + ToRelease<ICorDebugClass> pBaseClass; + IfFailRet(pBaseType->GetClass(&pBaseClass)); + ToRelease<ICorDebugModule> pBaseTypeModule; + IfFailRet(pBaseClass->GetModule(&pBaseTypeModule)); + mdTypeDef baseTypeDef; + IfFailRet(pBaseClass->GetToken(&baseTypeDef)); + ToRelease<IUnknown> pBaseTypeMDUnknown; + ToRelease<IMetaDataImport> pBaseTypeMD; + IfFailRet(pBaseTypeModule->GetMetaDataInterface(IID_IMetaDataImport, &pBaseTypeMDUnknown)); + IfFailRet(pBaseTypeMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pBaseTypeMD)); + + + // iterate through all fields at this level of the class hierarchy + ULONG numFields = 0; + HCORENUM fEnum = NULL; + mdFieldDef fieldDef; + while(SUCCEEDED(pMD->EnumFields(&fEnum, baseTypeDef, &fieldDef, 1, &numFields)) && numFields != 0) + { + ULONG nameLen = 0; + DWORD fieldAttr = 0; + WCHAR mdName[mdNameLen]; + WCHAR typeName[mdNameLen]; + CorElementType fieldDefaultValueEt; + UVCP_CONSTANT pDefaultValue; + ULONG cchDefaultValue; + if(SUCCEEDED(pBaseTypeMD->GetFieldProps(fieldDef, NULL, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, (DWORD*)&fieldDefaultValueEt, &pDefaultValue, &cchDefaultValue)) && + _wcscmp(mdName, pIdentifier) == 0) + { + ToRelease<ICorDebugType> pFieldValType = NULL; + ToRelease<ICorDebugValue> pFieldVal; + if (fieldAttr & fdStatic) + pBaseType->GetStaticFieldValue(fieldDef, pFrame, &pFieldVal); + else if(pInnerValue != NULL) + { + ToRelease<ICorDebugObjectValue> pObjValue; + if (SUCCEEDED(pInnerValue->QueryInterface(IID_ICorDebugObjectValue, (LPVOID*) &pObjValue))) + pObjValue->GetFieldValue(pBaseClass, fieldDef, &pFieldVal); + } + + // we didn't get a value yet and there is default value available + // need to calculate the type because there won't be a ICorDebugValue to derive it from + if(pFieldVal == NULL && pDefaultValue != NULL) + { + FindTypeFromElementType(fieldDefaultValueEt, &pFieldValType); + } + else + { + // if we aren't using default value, make sure it is cleared out + pDefaultValue = NULL; + cchDefaultValue = 0; + } + + // if we still don't have a value, check if we are trying to get an instance field from a static type + if(pInnerValue == NULL && pFieldVal == NULL && pDefaultValue == NULL) + { + WCHAR pObjectTypeName[MAX_EXPRESSION]; + CalculateTypeName(pBaseType, pObjectTypeName, MAX_EXPRESSION); + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"Can not evaluate instance field \'%s\' from static type \'%s\'", pIdentifier, pObjectTypeName); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + return CreateExpressionNodeHelper(pExpression, pExpressionCursor, currentCharsParsed, pFieldVal, pFieldValType, pDefaultValue, cchDefaultValue, pFrame, ppExpressionNode); + } + } + + //advance to next base type + ICorDebugType* pTemp = NULL; + pBaseType->GetBase(&pTemp); + pBaseType = pTemp; + } + + WCHAR pObjectTypeName[MAX_EXPRESSION]; + CalculateTypeName(pType, pObjectTypeName, MAX_EXPRESSION); + WCHAR errorMessage[MAX_ERROR]; + swprintf_s(errorMessage, MAX_ERROR, L"Field \'%s\' does not exist in type \'%s\'", pIdentifier, pObjectTypeName); + *ppExpressionNode = new ExpressionNode(pExpression, errorMessage); + return S_OK; + } + + return Status; +} + +// Splits apart a C#-like expression and determines the first identifier in the string and updates expression to point +// at the remaining unparsed portion +HRESULT ExpressionNode::ParseNextIdentifier(__in_z WCHAR** expression, __inout_ecount(cchIdentifierName) WCHAR* identifierName, DWORD cchIdentifierName, __inout_ecount(cchErrorMessage) WCHAR* errorMessage, DWORD cchErrorMessage, DWORD* charactersParsed, BOOL* isArrayIndex) +{ + + // This algorithm is best understood as a two stage process. The first stage splits + // the expression into two chunks an identifier and a remaining expression. The second stage + // normalizes the identifier. The splitting algorithm doesn't care if identifiers are well-formed + // at all, we do some error checking in the 2nd stage though. For the splitting stage, an identifier is + // any first character, followed by as many characters as possible that aren't a '.' or a '['. + // In the 2nd stage any '.' character is removed from the front (the only place it could be) + // and enclosing braces are removed. An error is recorded if the identifier ends 0 length or the + // opening bracket isn't matched. Here is an example showing how we would parse an expression + // which is deliberately not very well formed. Each line is the result of calling this function once... it + // takes many calls to break the entire expression down. + // + // expression 1st stage identifier 2nd stage identifier + // foo.bar[18..f[1.][2][[ + // .bar[18..f[1.][2][[ foo foo + // [18..f[1.][2][[ .bar bar + // ..f[1.][2][[ [18 error no ] + // .f[1.][2][[ . error 0-length + // [1.][2][[ .f f + // .][2][[ [1 error no ] + // [2][[ .] ] (we don't error check legal CLI identifier name characters) + // [[ [2] 2 + // [ [ error no ] + // [ error no ] + + // not an error, just the end of the expression + if(*expression == NULL || **expression == 0) + return S_FALSE; + + WCHAR* expressionStart = *expression; + DWORD currentCharsParsed = *charactersParsed; + DWORD identifierLen = (DWORD) _wcscspn(expressionStart, L".["); + // if the first character was a . or [ skip over it. Note that we don't + // do this always in case the first WCHAR was part of a surrogate pair + if(identifierLen == 0) + { + identifierLen = (DWORD) _wcscspn(expressionStart+1, L".[") + 1; + } + + *expression += identifierLen; + *charactersParsed += identifierLen; + + // done with the first stage splitting, on to 2nd stage + + // a . should be followed by field name + if(*expressionStart == L'.') + { + if(identifierLen == 1) // 0-length after . + { + swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing name after character %d", currentCharsParsed+1); + return E_FAIL; + } + if(identifierLen-1 >= cchIdentifierName) + { + swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, name at character %d is too long", currentCharsParsed+2); + return E_FAIL; + } + *isArrayIndex = FALSE; + wcsncpy_s(identifierName, cchIdentifierName, expressionStart+1, identifierLen-1); + return S_OK; + } + // an open bracket should be followed by a decimal value and then a closing bracket + else if(*expressionStart == L'[') + { + if(*(expressionStart+identifierLen-1) != L']') + { + swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing or invalid index expression at character %d", currentCharsParsed+1); + return E_FAIL; + } + if(identifierLen <= 2) // 0-length between [] + { + swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing index after character %d", currentCharsParsed+1); + return E_FAIL; + } + if(identifierLen-2 >= cchIdentifierName) + { + swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, index at character %d is too large", currentCharsParsed+2); + return E_FAIL; + } + *isArrayIndex = TRUE; + wcsncpy_s(identifierName, cchIdentifierName, expressionStart+1, identifierLen-2); + return S_OK; + } + else // no '.' or '[', this is an initial name + { + if(identifierLen == 0) // 0-length + { + swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, missing name after character %d", currentCharsParsed+1); + return E_FAIL; + } + if(identifierLen >= cchIdentifierName) + { + swprintf_s(errorMessage, cchErrorMessage, L"Failed to parse expression, name at character %d is too long", currentCharsParsed+1); + return E_FAIL; + } + *isArrayIndex = FALSE; + wcsncpy_s(identifierName, cchIdentifierName, expressionStart, identifierLen); + return S_OK; + } +} + + +// Iterate through all parameters in the ILFrame calling the callback function for each of them +HRESULT ExpressionNode::EnumerateParameters(IMetaDataImport * pMD, + mdMethodDef methodDef, + ICorDebugILFrame * pILFrame, + VariableEnumCallback pCallback, + VOID* pUserData) +{ + HRESULT Status = S_OK; + + ULONG cParams = 0; + ToRelease<ICorDebugValueEnum> pParamEnum; + IfFailRet(pILFrame->EnumerateArguments(&pParamEnum)); + IfFailRet(pParamEnum->GetCount(&cParams)); + DWORD methAttr = 0; + IfFailRet(pMD->GetMethodProps(methodDef, NULL, NULL, 0, NULL, &methAttr, NULL, NULL, NULL, NULL)); + for (ULONG i=0; i < cParams; i++) + { + ULONG paramNameLen = 0; + mdParamDef paramDef; + WCHAR paramName[mdNameLen] = L"\0"; + + if(i == 0 && (methAttr & mdStatic) == 0) + swprintf_s(paramName, mdNameLen, L"this\0"); + else + { + int idx = ((methAttr & mdStatic) == 0)? i : (i + 1); + if(SUCCEEDED(pMD->GetParamForMethodIndex(methodDef, idx, ¶mDef))) + pMD->GetParamProps(paramDef, NULL, NULL, paramName, mdNameLen, ¶mNameLen, NULL, NULL, NULL, NULL); + } + if(_wcslen(paramName) == 0) + swprintf_s(paramName, mdNameLen, L"param_%d\0", i); + + ToRelease<ICorDebugValue> pValue; + ULONG cArgsFetched; + WCHAR pErrorMessage[MAX_ERROR] = L"\0"; + HRESULT hr = pParamEnum->Next(1, &pValue, &cArgsFetched); + if (FAILED(hr)) + { + swprintf_s(pErrorMessage, MAX_ERROR, L" + (Error 0x%x retrieving parameter '%S')\n", hr, paramName); + } + if (hr == S_FALSE) + { + break; + } + pCallback(pValue, paramName, pErrorMessage, pUserData); + } + + return Status; +} + +// Enumerate all locals in the given ILFrame, calling the callback method for each of them +HRESULT ExpressionNode::EnumerateLocals(IMetaDataImport * pMD, + mdMethodDef methodDef, + ICorDebugILFrame * pILFrame, + VariableEnumCallback pCallback, + VOID* pUserData) +{ + HRESULT Status = S_OK; + ULONG cLocals = 0; + ToRelease<ICorDebugFunction> pFunction; + ToRelease<ICorDebugModule> pModule; + if(SUCCEEDED(pILFrame->GetFunction(&pFunction))) + { + IfFailRet(pFunction->GetModule(&pModule)); + } + ToRelease<ICorDebugValueEnum> pLocalsEnum; + IfFailRet(pILFrame->EnumerateLocalVariables(&pLocalsEnum)); + IfFailRet(pLocalsEnum->GetCount(&cLocals)); + if (cLocals > 0) + { + SymbolReader symReader; + bool symbolsAvailable = false; + if(pModule != NULL && SUCCEEDED(symReader.LoadSymbols(pMD, pModule))) + symbolsAvailable = true; + + for (ULONG i=0; i < cLocals; i++) + { + ULONG paramNameLen = 0; + WCHAR paramName[mdNameLen] = L"\0"; + WCHAR pErrorMessage[MAX_ERROR] = L"\0"; + ToRelease<ICorDebugValue> pValue; + HRESULT hr = S_OK; + if(symbolsAvailable) + hr = symReader.GetNamedLocalVariable(pILFrame, i, paramName, mdNameLen, &pValue); + else + { + ULONG cArgsFetched; + hr = pLocalsEnum->Next(1, &pValue, &cArgsFetched); + } + if(_wcslen(paramName) == 0) + swprintf_s(paramName, mdNameLen, L"local_%d\0", i); + + if (FAILED(hr)) + { + swprintf_s(pErrorMessage, MAX_ERROR, L" + (Error 0x%x retrieving local variable '%S')\n", hr, paramName); + } + else if (hr == S_FALSE) + { + break; + } + pCallback(pValue, paramName, pErrorMessage, pUserData); + } + } + + return Status; +} + +// Iterates over all frames on the current thread's stack, calling the callback function for each of them +HRESULT ExpressionNode::EnumerateFrames(FrameEnumCallback pCallback, VOID* pUserData) +{ + HRESULT Status = S_OK; + ToRelease<ICorDebugThread> pThread; + ToRelease<ICorDebugThread3> pThread3; + ToRelease<ICorDebugStackWalk> pStackWalk; + ULONG ulThreadID = 0; + g_ExtSystem->GetCurrentThreadSystemId(&ulThreadID); + + IfFailRet(g_pCorDebugProcess->GetThread(ulThreadID, &pThread)); + IfFailRet(pThread->QueryInterface(IID_ICorDebugThread3, (LPVOID *) &pThread3)); + IfFailRet(pThread3->CreateStackWalk(&pStackWalk)); + + InternalFrameManager internalFrameManager; + IfFailRet(internalFrameManager.Init(pThread3)); + + int currentFrame = -1; + + for (Status = S_OK; ; Status = pStackWalk->Next()) + { + currentFrame++; + + if (Status == CORDBG_S_AT_END_OF_STACK) + { + break; + } + IfFailRet(Status); + + if (IsInterrupt()) + { + ExtOut("<interrupted>\n"); + break; + } + + CROSS_PLATFORM_CONTEXT context; + ULONG32 cbContextActual; + if ((Status=pStackWalk->GetContext( + DT_CONTEXT_FULL, + sizeof(context), + &cbContextActual, + (BYTE *)&context))!=S_OK) + { + ExtOut("GetFrameContext failed: %lx\n",Status); + break; + } + + ToRelease<ICorDebugFrame> pFrame; + IfFailRet(pStackWalk->GetFrame(&pFrame)); + if (Status == S_FALSE) + { + Status = S_OK; + continue; + } + + pCallback(pFrame, pUserData); + } + + return Status; +} + +// Determines the corresponding ICorDebugType for a given primitive type +HRESULT ExpressionNode::FindTypeFromElementType(CorElementType et, ICorDebugType** ppType) +{ + HRESULT Status; + switch (et) + { + default: + Status = E_FAIL; + break; + + case ELEMENT_TYPE_BOOLEAN: + Status = FindTypeByName(L"System.Boolean", ppType); + break; + + case ELEMENT_TYPE_CHAR: + Status = FindTypeByName(L"System.Char", ppType); + break; + + case ELEMENT_TYPE_I1: + Status = FindTypeByName(L"System.SByte", ppType); + break; + + case ELEMENT_TYPE_U1: + Status = FindTypeByName(L"System.Byte", ppType); + break; + + case ELEMENT_TYPE_I2: + Status = FindTypeByName(L"System.Short", ppType); + break; + + case ELEMENT_TYPE_U2: + Status = FindTypeByName(L"System.UShort", ppType); + break; + + case ELEMENT_TYPE_I: + Status = FindTypeByName(L"System.Int32", ppType); + break; + + case ELEMENT_TYPE_U: + Status = FindTypeByName(L"System.UInt32", ppType); + break; + + case ELEMENT_TYPE_I4: + Status = FindTypeByName(L"System.Int32", ppType); + break; + + case ELEMENT_TYPE_U4: + Status = FindTypeByName(L"System.UInt32", ppType); + break; + + case ELEMENT_TYPE_I8: + Status = FindTypeByName(L"System.Int64", ppType); + break; + + case ELEMENT_TYPE_U8: + Status = FindTypeByName(L"System.UInt64", ppType); + break; + + case ELEMENT_TYPE_R4: + Status = FindTypeByName(L"System.Single", ppType); + break; + + case ELEMENT_TYPE_R8: + Status = FindTypeByName(L"System.Double", ppType); + break; + + case ELEMENT_TYPE_OBJECT: + Status = FindTypeByName(L"System.Object", ppType); + break; + + case ELEMENT_TYPE_STRING: + Status = FindTypeByName(L"System.String", ppType); + break; + } + return Status; +} + +// 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. +HRESULT ExpressionNode::GetCanonicalElementTypeForTypeName(__in_z WCHAR* pTypeName, CorElementType *et) +{ + //Sadly ICorDebug deliberately prevents creating ICorDebugType instances + //that use canonical short form element types... seems like an issue to me. + + if(_wcscmp(pTypeName, L"System.String")==0) + { + *et = ELEMENT_TYPE_STRING; + } + else if(_wcscmp(pTypeName, L"System.Object")==0) + { + *et = ELEMENT_TYPE_OBJECT; + } + else if(_wcscmp(pTypeName, L"System.Void")==0) + { + *et = ELEMENT_TYPE_VOID; + } + else if(_wcscmp(pTypeName, L"System.Boolean")==0) + { + *et = ELEMENT_TYPE_BOOLEAN; + } + else if(_wcscmp(pTypeName, L"System.Char")==0) + { + *et = ELEMENT_TYPE_CHAR; + } + else if(_wcscmp(pTypeName, L"System.Byte")==0) + { + *et = ELEMENT_TYPE_U1; + } + else if(_wcscmp(pTypeName, L"System.Sbyte")==0) + { + *et = ELEMENT_TYPE_I1; + } + else if(_wcscmp(pTypeName, L"System.Int16")==0) + { + *et = ELEMENT_TYPE_I2; + } + else if(_wcscmp(pTypeName, L"System.UInt16")==0) + { + *et = ELEMENT_TYPE_U2; + } + else if(_wcscmp(pTypeName, L"System.UInt32")==0) + { + *et = ELEMENT_TYPE_U4; + } + else if(_wcscmp(pTypeName, L"System.Int32")==0) + { + *et = ELEMENT_TYPE_I4; + } + else if(_wcscmp(pTypeName, L"System.UInt64")==0) + { + *et = ELEMENT_TYPE_U8; + } + else if(_wcscmp(pTypeName, L"System.Int64")==0) + { + *et = ELEMENT_TYPE_I8; + } + else if(_wcscmp(pTypeName, L"System.Single")==0) + { + *et = ELEMENT_TYPE_R4; + } + else if(_wcscmp(pTypeName, L"System.Double")==0) + { + *et = ELEMENT_TYPE_R8; + } + else if(_wcscmp(pTypeName, L"System.IntPtr")==0) + { + *et = ELEMENT_TYPE_U; + } + else if(_wcscmp(pTypeName, L"System.UIntPtr")==0) + { + *et = ELEMENT_TYPE_I; + } + else if(_wcscmp(pTypeName, L"System.TypedReference")==0) + { + *et = ELEMENT_TYPE_TYPEDBYREF; + } + else + { + return E_FAIL; // can't tell from a name whether it should be valuetype or class + } + return S_OK; +} + +// Searches the debuggee for any ICorDebugType that matches the given fully qualified name +// This will search across all AppDomains and Assemblies +HRESULT ExpressionNode::FindTypeByName(__in_z WCHAR* pTypeName, ICorDebugType** ppType) +{ + HRESULT Status = S_OK; + ToRelease<ICorDebugAppDomainEnum> pAppDomainEnum; + IfFailRet(g_pCorDebugProcess->EnumerateAppDomains(&pAppDomainEnum)); + DWORD count; + IfFailRet(pAppDomainEnum->GetCount(&count)); + for(DWORD i = 0; i < count; i++) + { + ToRelease<ICorDebugAppDomain> pAppDomain; + DWORD countFetched = 0; + IfFailRet(pAppDomainEnum->Next(1, &pAppDomain, &countFetched)); + Status = FindTypeByName(pAppDomain, pTypeName, ppType); + if(SUCCEEDED(Status)) + break; + } + + return Status; +} + +// Searches the debuggee for any ICorDebugType that matches the given fully qualified name +// This will search across all Assemblies in the given AppDomain +HRESULT ExpressionNode::FindTypeByName(ICorDebugAppDomain* pAppDomain, __in_z WCHAR* pTypeName, ICorDebugType** ppType) +{ + HRESULT Status = S_OK; + ToRelease<ICorDebugAssemblyEnum> pAssemblyEnum; + IfFailRet(pAppDomain->EnumerateAssemblies(&pAssemblyEnum)); + DWORD count; + IfFailRet(pAssemblyEnum->GetCount(&count)); + for(DWORD i = 0; i < count; i++) + { + ToRelease<ICorDebugAssembly> pAssembly; + DWORD countFetched = 0; + IfFailRet(pAssemblyEnum->Next(1, &pAssembly, &countFetched)); + Status = FindTypeByName(pAssembly, pTypeName, ppType); + if(SUCCEEDED(Status)) + break; + } + + return Status; +} + +// Searches the assembly for any ICorDebugType that matches the given fully qualified name +HRESULT ExpressionNode::FindTypeByName(ICorDebugAssembly* pAssembly, __in_z WCHAR* pTypeName, ICorDebugType** ppType) +{ + HRESULT Status = S_OK; + ToRelease<ICorDebugModuleEnum> pModuleEnum; + IfFailRet(pAssembly->EnumerateModules(&pModuleEnum)); + DWORD count; + IfFailRet(pModuleEnum->GetCount(&count)); + for(DWORD i = 0; i < count; i++) + { + ToRelease<ICorDebugModule> pModule; + DWORD countFetched = 0; + IfFailRet(pModuleEnum->Next(1, &pModule, &countFetched)); + Status = FindTypeByName(pModule, pTypeName, ppType); + if(SUCCEEDED(Status)) + break; + } + + return Status; +} + +// Searches a given module for any ICorDebugType that matches the given fully qualified type name +HRESULT ExpressionNode::FindTypeByName(ICorDebugModule* pModule, __in_z WCHAR* pTypeName, ICorDebugType** ppType) +{ + HRESULT Status = S_OK; + ToRelease<IUnknown> pMDUnknown; + ToRelease<IMetaDataImport> pMD; + IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown)); + IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD)); + + // If the name contains a generic argument list, extract the type name from + // before the list + WCHAR rootName[mdNameLen]; + WCHAR* pRootName = NULL; + int typeNameLen = (int) _wcslen(pTypeName); + int genericParamListStart = (int) _wcscspn(pTypeName, L"<"); + if(genericParamListStart != typeNameLen) + { + if(pTypeName[typeNameLen-1] != L'>' || genericParamListStart > mdNameLen) + { + return E_FAIL; // mal-formed type name + } + else + { + wcsncpy_s(rootName, mdNameLen, pTypeName, genericParamListStart); + pRootName = rootName; + } + } + else + { + pRootName = pTypeName; + } + + // Convert from name to token to ICorDebugClass + mdTypeDef typeDef; + IfFailRet(pMD->FindTypeDefByName(pRootName, NULL, &typeDef)); + DWORD flags; + ULONG nameLen; + mdToken tkExtends; + IfFailRet(pMD->GetTypeDefProps(typeDef, NULL, 0, &nameLen, &flags, &tkExtends)); + BOOL isValueType; + IfFailRet(IsTokenValueTypeOrEnum(tkExtends, pMD, &isValueType)); + CorElementType et = isValueType ? ELEMENT_TYPE_VALUETYPE : ELEMENT_TYPE_CLASS; + ToRelease<ICorDebugClass> pClass; + IfFailRet(pModule->GetClassFromToken(typeDef, &pClass)); + ToRelease<ICorDebugClass2> pClass2; + IfFailRet(pClass->QueryInterface(__uuidof(ICorDebugClass2), (void**)&pClass2)); + + // Convert from class to type - if generic then recursively resolve the generic + // parameter list + ArrayHolder<ToRelease<ICorDebugType>> typeParams = NULL; + int countTypeParams = 0; + if(genericParamListStart != typeNameLen) + { + ToRelease<ICorDebugAssembly> pAssembly; + IfFailRet(pModule->GetAssembly(&pAssembly)); + ToRelease<ICorDebugAppDomain> pDomain; + IfFailRet(pAssembly->GetAppDomain(&pDomain)); + + countTypeParams = 1; + for(int i = genericParamListStart+1; i < typeNameLen; i++) + { + if(pTypeName[i] == L',') countTypeParams++; + } + typeParams = new ToRelease<ICorDebugType>[countTypeParams]; + + WCHAR* pCurName = pTypeName + genericParamListStart+1; + for(int i = 0; i < countTypeParams; i++) + { + WCHAR typeParamName[mdNameLen]; + WCHAR* pNextComma = _wcschr(pCurName, L','); + int len = (pNextComma != NULL) ? (int)(pNextComma - pCurName) : (int)_wcslen(pCurName)-1; + if(len > mdNameLen) + return E_FAIL; + wcsncpy_s(typeParamName, mdNameLen, pCurName, len); + FindTypeByName(pDomain, typeParamName, &(typeParams[i])); + pCurName = pNextComma+1; + } + } + IfFailRet(pClass2->GetParameterizedType(et, countTypeParams, &(typeParams[0]), ppType)); + + return Status; +} + +// Checks whether the given token is or refers to type System.ValueType or System.Enum +HRESULT ExpressionNode::IsTokenValueTypeOrEnum(mdToken token, IMetaDataImport* pMetadata, BOOL* pResult) +{ + // This isn't a 100% correct check because we aren't verifying the module portion of the + // type identity. Arbitrary assemblies could define a type named System.ValueType or System.Enum. + // If that happens this code will get the answer wrong... we just assume that happens so rarely + // that it isn't worth doing all the overhead of assembly resolution to deal with + + HRESULT Status = S_OK; + CorTokenType type = (CorTokenType)(token & 0xFF000000); + + // only need enough space to hold either System.ValueType or System.Enum + //System.ValueType -> 16 characters + //System.Enum -> 11 characters + WCHAR nameBuffer[17]; + nameBuffer[0] = L'\0'; + + if(type == mdtTypeRef) + { + ULONG chTypeDef; + pMetadata->GetTypeRefProps(token, NULL, NULL, 0, &chTypeDef); + if(chTypeDef > _countof(nameBuffer)) + { + *pResult = FALSE; + return Status; + } + IfFailRet(pMetadata->GetTypeRefProps(token, NULL, nameBuffer, _countof(nameBuffer), &chTypeDef)); + } + else if(type == mdtTypeDef) + { + ULONG chTypeDef; + pMetadata->GetTypeDefProps(token, NULL, 0, &chTypeDef, NULL, NULL); + if(chTypeDef > _countof(nameBuffer)) + { + *pResult = FALSE; + return Status; + } + IfFailRet(pMetadata->GetTypeDefProps(token, nameBuffer, _countof(nameBuffer), &chTypeDef, NULL, NULL)); + } + + if(_wcscmp(nameBuffer, L"System.ValueType") == 0 || + _wcscmp(nameBuffer, L"System.Enum") == 0) + { + *pResult = TRUE; + } + else + { + *pResult = FALSE; + } + return Status; +} |