Add varobj walking code
authorIgor Kulaychuk <i.kulaychuk@samsung.com>
Mon, 10 Jul 2017 10:45:56 +0000 (13:45 +0300)
committerIgor Kulaychuk <i.kulaychuk@samsung.com>
Mon, 13 Nov 2017 19:22:40 +0000 (22:22 +0300)
src/debug/debugger/main.cpp
src/debug/debugger/torelease.h
src/debug/debugger/varobj.cpp

index 7aeb845..b9d710a 100644 (file)
@@ -126,6 +126,7 @@ HRESULT GetFrameLocation(ICorDebugFrame *pFrame,
 
 // Varobj
 HRESULT ListVariables(ICorDebugFrame *pFrame, std::string &output);
+void NotifyEvalComplete();
 
 // TypePrinter
 #include "typeprinter.h"
@@ -566,12 +567,20 @@ public:
         virtual HRESULT STDMETHODCALLTYPE EvalComplete(
             /* [in] */ ICorDebugAppDomain *pAppDomain,
             /* [in] */ ICorDebugThread *pThread,
-            /* [in] */ ICorDebugEval *pEval) { return S_OK; }
+            /* [in] */ ICorDebugEval *pEval)
+        {
+            NotifyEvalComplete();
+            return S_OK;
+        }
 
         virtual HRESULT STDMETHODCALLTYPE EvalException(
             /* [in] */ ICorDebugAppDomain *pAppDomain,
             /* [in] */ ICorDebugThread *pThread,
-            /* [in] */ ICorDebugEval *pEval) { return S_OK; }
+            /* [in] */ ICorDebugEval *pEval)
+        {
+            NotifyEvalComplete();
+            return S_OK;
+        }
 
         virtual HRESULT STDMETHODCALLTYPE CreateProcess(
             /* [in] */ ICorDebugProcess *pProcess)
@@ -586,6 +595,7 @@ public:
         {
             out_printf("*stopped,reason=\"exited\",exit-code=\"%i\"\n", 0);
             g_processExited = true;
+            NotifyEvalComplete();
             return S_OK;
         }
 
index e180659..771c0bc 100644 (file)
@@ -67,6 +67,7 @@ public:
         }
     }
 
+    ToRelease(ToRelease&& that) noexcept : m_ptr(that.m_ptr) { that.m_ptr = nullptr; }
 private:
     ToRelease(const ToRelease& that) = delete;
     T* m_ptr;
index 8b29c7d..67d851a 100644 (file)
@@ -7,8 +7,10 @@
 
 #include <sstream>
 #include <mutex>
+#include <condition_variable>
 #include <memory>
 #include <unordered_map>
+#include <unordered_set>
 #include <vector>
 #include <iomanip>
 
@@ -101,7 +103,7 @@ static HRESULT PrintStringValue(ICorDebugValue * pValue, std::string &output)
     return S_OK;
 }
 
-HRESULT PrintValue(ICorDebugValue *pInputValue, ICorDebugILFrame * pILFrame, IMetaDataImport * pMD, std::string &output)
+HRESULT PrintValue(ICorDebugValue *pInputValue, ICorDebugILFrame * pILFrame, std::string &output)
 {
     HRESULT Status;
 
@@ -270,6 +272,571 @@ HRESULT PrintValue(ICorDebugValue *pInputValue, ICorDebugILFrame * pILFrame, IMe
     return S_OK;
 }
 
+static HRESULT GetNumChild(ICorDebugValue *pInputValue,
+                           ICorDebugType *pTypeCast,
+                           ULONG &numstatic,
+                           ULONG &numinstance)
+{
+    numstatic = 0;
+    numinstance = 0;
+
+    HRESULT Status = S_OK;
+
+    BOOL isNull = FALSE;
+    ToRelease<ICorDebugValue> pValue;
+
+    IfFailRet(DereferenceAndUnboxValue(pInputValue, &pValue, &isNull));
+
+    if (isNull) return S_OK;
+
+    ToRelease<ICorDebugArrayValue> pArrayValue;
+    if (SUCCEEDED(pValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID *) &pArrayValue)))
+    {
+        ULONG32 nRank;
+        IfFailRet(pArrayValue->GetRank(&nRank));
+
+        ULONG32 cElements;
+        IfFailRet(pArrayValue->GetCount(&cElements));
+
+        numinstance = cElements;
+
+        return S_OK;
+    }
+
+    mdTypeDef currentTypeDef;
+    ToRelease<ICorDebugClass> pClass;
+    ToRelease<ICorDebugValue2> pValue2;
+    ToRelease<ICorDebugType> pType;
+    ToRelease<ICorDebugModule> pModule;
+    IfFailRet(pValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2));
+    if(pTypeCast == NULL)
+        IfFailRet(pValue2->GetExactType(&pType));
+    else
+    {
+        pType = pTypeCast;
+        pType->AddRef();
+    }
+
+    CorElementType corElemType;
+    IfFailRet(pType->GetType(&corElemType));
+    if (corElemType == ELEMENT_TYPE_STRING)
+        return S_OK;
+
+    IfFailRet(pType->GetClass(&pClass));
+    IfFailRet(pClass->GetModule(&pModule));
+    IfFailRet(pClass->GetToken(&currentTypeDef));
+
+    ToRelease<IUnknown> pMDUnknown;
+    ToRelease<IMetaDataImport> pMD;
+    IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+    IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD));
+
+    std::string baseTypeName;
+    ToRelease<ICorDebugType> pBaseType;
+    if(SUCCEEDED(pType->GetBase(&pBaseType)) && pBaseType != NULL && SUCCEEDED(TypePrinter::GetTypeOfValue(pBaseType, baseTypeName)))
+    {
+        if(baseTypeName == "System.Enum")
+            return S_OK;
+        else if(baseTypeName != "System.Object"  && baseTypeName != "System.ValueType")
+        {
+            // Add fields of base class
+            ULONG numstaticBase = 0;
+            ULONG numinstanceBase = 0;
+            IfFailRet(GetNumChild(pInputValue, pBaseType, numstaticBase, numinstanceBase));
+            numstatic += numstaticBase;
+            numinstance += numinstanceBase;
+        }
+    }
+
+    ULONG numFields = 0;
+    HCORENUM fEnum = NULL;
+    mdFieldDef fieldDef;
+    ULONG numstaticBack = 0;
+    ULONG numinstanceBack = 0;
+    while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0)
+    {
+        ULONG nameLen = 0;
+        DWORD fieldAttr = 0;
+        WCHAR mdName[mdNameLen] = {0};
+        if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, NULL, NULL, NULL)))
+        {
+            bool is_back = (char)mdName[0] == '<';
+            if(fieldAttr & fdLiteral)
+                continue;
+
+            if (fieldAttr & fdStatic)
+            {
+                numstatic++;
+                numstaticBack += is_back ? 1 : 0;
+            }
+            else
+            {
+                numinstance++;
+                numinstanceBack += is_back ? 1 : 0;
+            }
+        }
+    }
+    pMD->CloseEnum(fEnum);
+
+    mdProperty propertyDef;
+    ULONG numProperties = 0;
+    HCORENUM propEnum = NULL;
+    while(SUCCEEDED(pMD->EnumProperties(&propEnum, currentTypeDef, &propertyDef, 1, &numProperties)) && numProperties != 0)
+    {
+        mdTypeDef  propertyClass;
+
+        DWORD propFlags;
+        UVCP_CONSTANT pDefaultValue;
+        ULONG cchDefaultValue;
+        mdMethodDef mdGetter;
+        mdMethodDef rmdOtherMethod;
+        ULONG cOtherMethod;
+        WCHAR propertyName[mdNameLen] = W("\0");
+        ULONG propertyNameLen = 0;
+        if (SUCCEEDED(pMD->GetPropertyProps(propertyDef,
+                                            &propertyClass,
+                                            propertyName,
+                                            mdNameLen,
+                                            &propertyNameLen,
+                                            &propFlags,
+                                            NULL,
+                                            NULL,
+                                            NULL,
+                                            &pDefaultValue,
+                                            &cchDefaultValue,
+                                            NULL,
+                                            &mdGetter,
+                                            NULL,
+                                            0,
+                                            NULL)))
+        {
+            if (propFlags & fdStatic)
+            {
+                if (numstaticBack == 0)
+                    numstatic++;
+                else
+                    numstaticBack--;
+            }
+            else
+            {
+                if (numinstanceBack == 0)
+                    numinstance++;
+                else
+                    numinstanceBack--;
+            }
+        }
+    }
+    pMD->CloseEnum(propEnum);
+
+    return S_OK;
+}
+
+HRESULT GetNumChild(ICorDebugValue *pValue,
+                    unsigned int &numchild,
+                    bool static_members = false)
+{
+    HRESULT Status = S_OK;
+    numchild = 0;
+
+    ULONG numstatic;
+    ULONG numinstance;
+    IfFailRet(GetNumChild(pValue, NULL, numstatic, numinstance));
+    if (static_members)
+    {
+        numchild = numstatic;
+    }
+    else
+    {
+        numchild = (numstatic > 0) ? numinstance + 1 : numinstance;
+    }
+    return S_OK;
+}
+
+extern std::mutex g_currentThreadMutex;
+extern ICorDebugThread *g_currentThread;
+
+std::mutex g_evalMutex;
+std::condition_variable g_evalCV;
+bool g_evalComplete = false;
+
+void NotifyEvalComplete()
+{
+    std::lock_guard<std::mutex> lock(g_evalMutex);
+    g_evalComplete = true;
+    g_evalMutex.unlock();
+    g_evalCV.notify_one();
+}
+
+HRESULT EvalProperty(
+    mdMethodDef methodDef,
+    ICorDebugModule *pModule,
+    ICorDebugType *pType,
+    ICorDebugValue *pInputValue,
+    bool is_static,
+    ICorDebugValue **ppEvalResult)
+{
+    HRESULT Status = S_OK;
+
+    ToRelease<ICorDebugEval> pEval;
+
+    ToRelease<ICorDebugProcess> pProcess;
+    {
+        // g_currentThreadMutex should be locked by caller function
+        //std::lock_guard<std::mutex> lock(g_currentThreadMutex);
+
+        IfFailRet(g_currentThread->GetProcess(&pProcess));
+        IfFailRet(g_currentThread->CreateEval(&pEval));
+    }
+
+    ToRelease<ICorDebugFunction> pFunc;
+    IfFailRet(pModule->GetFunctionFromToken(methodDef, &pFunc));
+
+    ToRelease<ICorDebugTypeEnum> pTypeEnum;
+
+    std::vector< ToRelease<ICorDebugType> > typeParams;
+    if(SUCCEEDED(pType->EnumerateTypeParameters(&pTypeEnum)))
+    {
+        ICorDebugType *curType;
+        ULONG fetched = 0;
+        while(SUCCEEDED(pTypeEnum->Next(1, &curType, &fetched)) && fetched == 1)
+        {
+            typeParams.emplace_back(curType);
+        }
+    }
+
+    ToRelease<ICorDebugEval2> pEval2;
+    IfFailRet(pEval->QueryInterface(IID_ICorDebugEval2, (LPVOID*) &pEval2));
+
+    IfFailRet(pEval2->CallParameterizedFunction(
+        pFunc,
+        typeParams.size(),
+        (ICorDebugType **)typeParams.data(),
+        is_static ? 0 : 1,
+        is_static ? NULL : &pInputValue
+    ));
+
+    std::unique_lock<std::mutex> lock(g_evalMutex);
+    g_evalComplete = false;
+    IfFailRet(pProcess->Continue(0));
+    g_evalCV.wait(lock, []{return g_evalComplete;});
+
+    return pEval->GetResult(ppEvalResult);
+}
+
+struct VarObjValue
+{
+    std::string name;
+    ICorDebugValue *value;
+    std::string owningType;
+    std::string typeName;
+
+    std::string varobjName;
+    bool statics_only;
+
+    unsigned int numchild;
+
+    VarObjValue(
+        const std::string &n,
+        ICorDebugValue *v,
+        const std::string t = "") : name(n), value(v), owningType(t),
+                                statics_only(false), numchild(0) {}
+    VarObjValue(
+        ICorDebugValue *v) : name("Static members"), value(v),
+                                statics_only(true), numchild(0) {}
+};
+
+static HRESULT PrintFieldsAndProperties(ICorDebugValue *pInputValue,
+                                        ICorDebugType *pTypeCast,
+                                        ICorDebugILFrame *pILFrame,
+                                        std::vector<VarObjValue> &members,
+                                        bool static_members,
+                                        bool &has_static_members)
+{
+    has_static_members = false;
+    HRESULT Status = S_OK;
+
+    BOOL isNull = FALSE;
+    ToRelease<ICorDebugValue> pValue;
+
+    IfFailRet(DereferenceAndUnboxValue(pInputValue, &pValue, &isNull));
+
+    if (isNull) return S_OK;
+
+    ToRelease<ICorDebugArrayValue> pArrayValue;
+    if (SUCCEEDED(pValue->QueryInterface(IID_ICorDebugArrayValue, (LPVOID *) &pArrayValue)))
+    {
+        ULONG32 nRank;
+        IfFailRet(pArrayValue->GetRank(&nRank));
+
+        // TODO: array elements
+
+        return S_OK;
+    }
+
+    mdTypeDef currentTypeDef;
+    ToRelease<ICorDebugClass> pClass;
+    ToRelease<ICorDebugValue2> pValue2;
+    ToRelease<ICorDebugType> pType;
+    ToRelease<ICorDebugModule> pModule;
+    IfFailRet(pValue->QueryInterface(IID_ICorDebugValue2, (LPVOID *) &pValue2));
+    if(pTypeCast == NULL)
+        IfFailRet(pValue2->GetExactType(&pType));
+    else
+    {
+        pType = pTypeCast;
+        pType->AddRef();
+    }
+
+    CorElementType corElemType;
+    IfFailRet(pType->GetType(&corElemType));
+    if (corElemType == ELEMENT_TYPE_STRING)
+        return S_OK;
+
+    IfFailRet(pType->GetClass(&pClass));
+    IfFailRet(pClass->GetModule(&pModule));
+    IfFailRet(pClass->GetToken(&currentTypeDef));
+
+    ToRelease<IUnknown> pMDUnknown;
+    ToRelease<IMetaDataImport> pMD;
+    IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+    IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMD));
+
+    std::string baseTypeName;
+    ToRelease<ICorDebugType> pBaseType;
+    if(SUCCEEDED(pType->GetBase(&pBaseType)) && pBaseType != NULL && SUCCEEDED(TypePrinter::GetTypeOfValue(pBaseType, baseTypeName)))
+    {
+        if(baseTypeName == "System.Enum")
+            return S_OK;
+        else if(baseTypeName != "System.Object"  && baseTypeName != "System.ValueType")
+        {
+            // Add fields of base class
+            PrintFieldsAndProperties(pInputValue, pBaseType, pILFrame, members, static_members, has_static_members);
+        }
+    }
+
+    std::string className;
+    TypePrinter::GetTypeOfValue(pType, className);
+
+    std::unordered_set<std::string> backedProperies;
+
+    ULONG numFields = 0;
+    HCORENUM fEnum = NULL;
+    mdFieldDef fieldDef;
+    while(SUCCEEDED(pMD->EnumFields(&fEnum, currentTypeDef, &fieldDef, 1, &numFields)) && numFields != 0)
+    {
+        ULONG nameLen = 0;
+        DWORD fieldAttr = 0;
+        WCHAR mdName[mdNameLen] = {0};
+        if(SUCCEEDED(pMD->GetFieldProps(fieldDef, NULL, mdName, mdNameLen, &nameLen, &fieldAttr, NULL, NULL, NULL, NULL, NULL)))
+        {
+            char cName[mdNameLen] = {0};
+            WideCharToMultiByte(CP_UTF8, 0, mdName, (int)(nameLen + 1), cName, _countof(cName), NULL, NULL);
+
+            if(fieldAttr & fdLiteral)
+                continue;
+
+            bool is_static = (fieldAttr & fdStatic);
+            if (is_static)
+                has_static_members = true;
+
+            bool add_member = static_members ? is_static : !is_static;
+            if (!add_member)
+                continue;
+
+            ToRelease<ICorDebugValue> pFieldVal;
+
+            if (fieldAttr & fdStatic)
+            {
+                pType->GetStaticFieldValue(fieldDef, pILFrame, &pFieldVal);
+            }
+            else
+            {
+                ToRelease<ICorDebugObjectValue> pObjValue;
+                if (SUCCEEDED(pValue->QueryInterface(IID_ICorDebugObjectValue, (LPVOID*) &pObjValue)))
+                    pObjValue->GetFieldValue(pClass, fieldDef, &pFieldVal);
+            }
+
+            if(pFieldVal != NULL)
+            {
+                std::string name(cName);
+                if (cName[0] == '<')
+                {
+                    size_t endOffset = name.rfind('>');
+                    name = name.substr(1, endOffset - 1);
+                    backedProperies.insert(name);
+                }
+
+                members.emplace_back(name, pFieldVal.Detach(), className);
+            }
+            else
+            {
+                // no need for backing field when we can not get its value
+                if (cName[0] == '<')
+                    continue;
+
+                members.emplace_back(cName, nullptr, className);
+            }
+        }
+    }
+    pMD->CloseEnum(fEnum);
+
+    mdProperty propertyDef;
+    ULONG numProperties = 0;
+    HCORENUM propEnum = NULL;
+    while(SUCCEEDED(pMD->EnumProperties(&propEnum, currentTypeDef, &propertyDef, 1, &numProperties)) && numProperties != 0)
+    {
+        mdTypeDef  propertyClass;
+
+        ULONG propertyNameLen = 0;
+        DWORD propFlags;
+        PCCOR_SIGNATURE pvSig;
+        ULONG pbSig;
+        DWORD dwCPlusTypeFlag;
+        UVCP_CONSTANT pDefaultValue;
+        ULONG cchDefaultValue;
+        mdMethodDef mdSetter;
+        mdMethodDef mdGetter;
+        mdMethodDef rmdOtherMethod;
+        ULONG cOtherMethod;
+        WCHAR propertyName[mdNameLen] = W("\0");
+        if (SUCCEEDED(pMD->GetPropertyProps(propertyDef,
+                                            &propertyClass,
+                                            propertyName,
+                                            mdNameLen,
+                                            &propertyNameLen,
+                                            &propFlags,
+                                            &pvSig,
+                                            &pbSig,
+                                            &dwCPlusTypeFlag,
+                                            &pDefaultValue,
+                                            &cchDefaultValue,
+                                            &mdSetter,
+                                            &mdGetter,
+                                            &rmdOtherMethod,
+                                            1,
+                                            &cOtherMethod)))
+        {
+            char cName[mdNameLen] = {0};
+            WideCharToMultiByte(CP_UTF8, 0, propertyName, (int)(propertyNameLen + 1), cName, _countof(cName), NULL, NULL);
+
+            if (backedProperies.find(cName) != backedProperies.end())
+                continue;
+
+            bool is_static = (propFlags & fdStatic);
+            if (is_static)
+                has_static_members = true;
+
+            bool add_member = static_members ? is_static : !is_static;
+            if (!add_member)
+                continue;
+
+            ToRelease<ICorDebugValue> pResultValue;
+            std::string resultTypeName;
+            if (SUCCEEDED(EvalProperty(mdGetter, pModule, pType, pInputValue, is_static, &pResultValue)))
+            {
+                members.emplace_back(cName, pResultValue.Detach(), className);
+            }
+        }
+    }
+    pMD->CloseEnum(propEnum);
+
+    return S_OK;
+}
+
+void FixupInheritedFieldNames(std::vector<VarObjValue> &members)
+{
+    std::unordered_set<std::string> names;
+    for (auto it = members.rbegin(); it != members.rend(); ++it)
+    {
+        auto r = names.insert(it->name);
+        if (!r.second)
+        {
+            it->name += " (" + it->owningType + ")";
+        }
+    }
+}
+
+void PrintChildren(std::vector<VarObjValue> &members, std::string &output)
+{
+    std::stringstream ss;
+    ss << "numchild=\"" << members.size() << "\"";
+
+    if (members.empty())
+    {
+        output = ss.str();
+        return;
+    }
+    ss << ",children=[";
+
+    const char *sep = "";
+    for (auto m : members)
+    {
+        ss << sep;
+        sep = ",";
+
+        ss << "child={name=\"" << m.varobjName << "\",exp=\"" << m.name << "\",";
+        ss << "numchild=\"" << m.numchild << "\",type=\"" << m.typeName << "\"}";
+        //thread-id="452958",has_more="0"}
+    }
+
+    ss << "]";
+    output = ss.str();
+}
+
+HRESULT ListChildren(VarObjValue &objValue, ICorDebugFrame *pFrame, std::string &output)
+{
+    HRESULT Status;
+
+    ToRelease<ICorDebugILFrame> pILFrame;
+    IfFailRet(pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame));
+
+    std::vector<VarObjValue> members;
+
+    bool has_static_members;
+
+    IfFailRet(PrintFieldsAndProperties(objValue.value,
+                                    NULL,
+                                    pILFrame,
+                                    members,
+                                    objValue.statics_only,
+                                    has_static_members));
+
+    if (!objValue.statics_only && has_static_members)
+    {
+        objValue.value->AddRef();
+        members.emplace_back(objValue.value);
+    }
+
+    FixupInheritedFieldNames(members);
+
+    for (auto &m : members)
+    {
+        std::string className;
+
+        if (!m.value)
+            continue;
+
+        Status = GetNumChild(m.value, m.numchild, m.statics_only);
+        TypePrinter::GetTypeOfValue(m.value, m.typeName);
+    }
+
+    PrintChildren(members, output);
+
+    for (auto m : members)
+    {
+        if (m.value)
+            m.value->Release();
+    }
+
+    return S_OK;
+}
+
+HRESULT ListChildren(ICorDebugValue *pInputValue, ICorDebugFrame *pFrame, std::string &output)
+{
+    VarObjValue val("?", pInputValue, "");
+    return ListChildren(val, pFrame, output);
+}
+
 HRESULT ListVariables(ICorDebugFrame *pFrame, std::string &output)
 {
     bool printValues = true;
@@ -344,7 +911,7 @@ HRESULT ListVariables(ICorDebugFrame *pFrame, std::string &output)
             if (printValues)
             {
                 std::string strVal;
-                if (SUCCEEDED(PrintValue(pValue, pILFrame, pMD, strVal)))
+                if (SUCCEEDED(PrintValue(pValue, pILFrame, strVal)))
                     ss << ",value=\"" << strVal << "\"";
             }
             if (printTypes)
@@ -353,6 +920,11 @@ HRESULT ListVariables(ICorDebugFrame *pFrame, std::string &output)
                 if (SUCCEEDED(TypePrinter::GetTypeOfValue(pValue, strVal)))
                     ss << ",type=\"" << strVal << "\"";
             }
+
+            std::string test;
+            ListChildren(pValue, pFrame, test);
+            ss << test;
+
             ss << "}";
             sep = ",";
         }
@@ -382,7 +954,7 @@ HRESULT ListVariables(ICorDebugFrame *pFrame, std::string &output)
             if (printValues)
             {
                 std::string strVal;
-                if (SUCCEEDED(PrintValue(pValue, pILFrame, pMD, strVal)))
+                if (SUCCEEDED(PrintValue(pValue, pILFrame, strVal)))
                     ss << ",value=\"" << strVal << "\"";
             }
             if (printTypes)
@@ -391,6 +963,15 @@ HRESULT ListVariables(ICorDebugFrame *pFrame, std::string &output)
                 if (SUCCEEDED(TypePrinter::GetTypeOfValue(pValue, strVal)))
                     ss << ",type=\"" << strVal << "\"";
             }
+            std::string children;
+            ListChildren(pValue, pFrame, children);
+            ss << "," << children;
+
+            ULONG numchild;
+            GetNumChild(pValue, numchild);
+
+            ss << "numchild=\"" << numchild << "\"";
+
             ss << "}";
             sep = ",";
         }