Refactor printing stack variables
authorIgor Kulaychuk <i.kulaychuk@samsung.com>
Tue, 16 Jan 2018 15:53:10 +0000 (18:53 +0300)
committerIgor Kulaychuk <i.kulaychuk@samsung.com>
Tue, 16 Jan 2018 15:53:10 +0000 (18:53 +0300)
src/debug/netcoredbg/commands.cpp
src/debug/netcoredbg/debugger.h
src/debug/netcoredbg/protocol.h
src/debug/netcoredbg/varobj.cpp
src/debug/netcoredbg/varobj.h

index b86fcf7..00461c8 100644 (file)
@@ -340,6 +340,32 @@ HRESULT MIProtocol::PrintFrames(int threadId, std::string &output, int lowFrame,
     return S_OK;
 }
 
+HRESULT MIProtocol::PrintVariables(const std::vector<Variable> &variables, std::string &output)
+{
+    const bool printValues = true;
+    const bool printTypes = false;
+
+    HRESULT Status;
+
+    std::stringstream ss;
+    ss << "variables=[";
+    const char *sep = "";
+
+    for (const Variable &var : variables)
+    {
+        ss << sep;
+        sep = ",";
+
+        ss << "{name=\"" << EscapeMIValue(var.name) << "\"";
+        ss << ",value=\"" << EscapeMIValue(var.value) << "\"";
+        ss << "}";
+    }
+
+    ss << "]";
+    output = ss.str();
+    return S_OK;
+}
+
 void MIProtocol::EmitStoppedEvent(StoppedEvent event)
 {
     HRESULT Status;
@@ -520,17 +546,19 @@ HRESULT MIProtocol::HandleCommand(std::string command,
         GetIndices(args, lowFrame, highFrame);
         return PrintFrames(threadId, output, lowFrame, highFrame);
     }},
-    { "stack-list-variables", [](ICorDebugProcess *pProcess, const std::vector<std::string> &args, std::string &output) -> HRESULT {
-        if (!pProcess) return E_FAIL;
+    { "stack-list-variables", [this](ICorDebugProcess *, const std::vector<std::string> &args, std::string &output) -> HRESULT {
         HRESULT Status;
-        ToRelease<ICorDebugThread> pThread;
-        DWORD threadId = GetIntArg(args, "--thread", GetLastStoppedThreadId());
-        IfFailRet(pProcess->GetThread(threadId, &pThread));
 
-        ToRelease<ICorDebugFrame> pFrame;
-        IfFailRet(GetFrameAt(pThread, GetIntArg(args, "--frame", 0), &pFrame));
+        StackFrame stackFrame(GetIntArg(args, "--thread", GetLastStoppedThreadId()), GetIntArg(args, "--frame", 0), "");
+        std::vector<Scope> scopes;
+        std::vector<Variable> variables;
+        IfFailRet(m_debugger->GetScopes(stackFrame.id, scopes));
+        if (!scopes.empty() && scopes[0].variablesReference != 0)
+        {
+            IfFailRet(m_debugger->GetVariables(scopes[0].variablesReference, VariablesNamed, 0, 0, variables));
+        }
 
-        IfFailRet(ListVariables(pThread, pFrame, output));
+        PrintVariables(variables, output);
 
         return S_OK;
     }},
index 20b3275..44b8f8c 100644 (file)
@@ -3,7 +3,7 @@
 // See the LICENSE file in the project root for more information.
 
 #include "protocol.h"
-
+#include <unordered_map>
 
 class ManagedCallback;
 class Protocol;
@@ -35,6 +35,53 @@ private:
     DWORD m_processId;
     std::string m_clrPath;
 
+    struct VariableReference
+    {
+        uint32_t variablesReference; // key
+        int namedVariables;
+        int indexedVariables;
+
+        std::string evaluateName;
+
+        enum ValueKind
+        {
+            ValueIsScope,
+            ValueIsClass,
+            ValueIsVariable
+        };
+        ValueKind valueKind;
+        ToRelease<ICorDebugValue> value;
+        uint64_t frameId;
+
+        VariableReference(uint32_t variablesReference, uint64_t frameId, ToRelease<ICorDebugValue> value, ValueKind valueKind) :
+            variablesReference(variablesReference),
+            namedVariables(0),
+            indexedVariables(0),
+            valueKind(valueKind),
+            value(std::move(value)),
+            frameId(frameId)
+        {}
+
+        VariableReference(uint32_t variablesReference, uint64_t frameId, int namedVariables) :
+            variablesReference(variablesReference),
+            namedVariables(namedVariables),
+            indexedVariables(0),
+            valueKind(ValueIsScope),
+            value(nullptr),
+            frameId(frameId)
+        {}
+
+        bool IsScope() const { return valueKind == ValueIsScope; }
+
+        VariableReference(VariableReference &&that) = default;
+    private:
+        VariableReference(const VariableReference &that) = delete;
+    };
+    std::unordered_map<uint32_t, VariableReference> m_variables;
+    uint32_t m_nextVariableReference;
+
+    void AddVariableReference(Variable &variable, uint64_t frameId, ICorDebugValue *value, VariableReference::ValueKind valueKind);
+
     HRESULT CheckNoProcess();
 
     static VOID StartupCallback(IUnknown *pCordb, PVOID parameter, HRESULT hr);
@@ -44,6 +91,8 @@ private:
 
     static HRESULT SetupStep(ICorDebugThread *pThread, StepType stepType);
 
+    HRESULT GetStackVariables(uint64_t frameId, ICorDebugThread *pThread, ICorDebugFrame *pFrame, int start, int count, std::vector<Variable> &variables);
+    HRESULT GetChildren(VariableReference &ref, ICorDebugThread *pThread, ICorDebugFrame *pFrame, int start, int count, std::vector<Variable> &variables);
 public:
     static bool IsJustMyCode() { return m_justMyCode; }
     static void SetJustMyCode(bool enable) { m_justMyCode = enable; }
@@ -55,7 +104,8 @@ public:
         m_startupReady(false),
         m_startupResult(S_OK),
         m_unregisterToken(nullptr),
-        m_processId(0) {}
+        m_processId(0),
+        m_nextVariableReference(1) {}
 
     ~Debugger();
 
@@ -77,6 +127,8 @@ public:
     HRESULT SetBreakpoint(std::string filename, int linenum, Breakpoint &breakpoint);
     HRESULT GetStackTrace(int threadId, int lowFrame, int highFrame, std::vector<StackFrame> &stackFrames);
     HRESULT StepCommand(int threadId, StepType stepType);
+    HRESULT GetScopes(uint64_t frameId, std::vector<Scope> &scopes);
+    HRESULT GetVariables(uint32_t variablesReference, VariablesFilter filter, int start, int count, std::vector<Variable> &variables);
 };
 
 class Protocol
@@ -121,6 +173,7 @@ private:
                         std::string &output,
                         Debugger::StepType stepType);
     HRESULT PrintFrames(int threadId, std::string &output, int lowFrame, int highFrame);
+    HRESULT PrintVariables(const std::vector<Variable> &variables, std::string &output);
 };
 
 HRESULT DisableAllSteppers(ICorDebugProcess *pProcess);
index 6ff8e47..653031e 100644 (file)
@@ -7,6 +7,8 @@
 #include "platform.h"
 #include <string>
 
+// From https://github.com/Microsoft/vscode-debugadapter-node/blob/master/protocol/src/debugProtocol.ts
+
 struct Thread
 {
     int id;
@@ -59,6 +61,8 @@ struct StackFrame
         id |= level;
     }
 
+    StackFrame(uint64_t id) : id(id), line(0), column(0), endLine(0), endColumn(0) {}
+
     uint32_t GetLevel() const { return id & 0xFFFFFFFFul; }
     int GetThreadId() const { return id >> 32; }
 };
@@ -151,3 +155,51 @@ struct OutputEvent
 
     OutputEvent(OutputCategory category, std::string output) : category(OutputConsole), output(output) {}
 };
+
+struct Scope
+{
+    std::string name;
+    uint32_t variablesReference;
+    int namedVariables;
+    int indexedVariables;
+    bool expensive;
+
+    Scope() : variablesReference(0), namedVariables(0), expensive(false) {}
+
+    Scope(uint32_t variablesReference, const std::string &name, int namedVariables) :
+        name(name),
+        variablesReference(variablesReference),
+        namedVariables(namedVariables),
+        expensive(false)
+    {}
+};
+
+
+// TODO: Replace strings with enums
+struct VariablePresentationHint
+{
+    std::string kind;
+    std::vector<std::string> attributes;
+    std::string visibility;
+};
+
+struct Variable
+{
+    std::string name;
+    std::string value;
+    std::string type;
+    VariablePresentationHint presentationHint;
+    std::string evaluateName;
+    uint32_t variablesReference;
+    int namedVariables;
+    int indexedVariables;
+
+    Variable() : variablesReference(0), namedVariables(0), indexedVariables(0) {}
+};
+
+enum VariablesFilter
+{
+    VariablesNamed,
+    VariablesIndexed,
+    VariablesBoth
+};
index f5ad27a..7feb217 100644 (file)
@@ -21,6 +21,7 @@
 #include "valueprint.h"
 #include "varobj.h"
 #include "expr.h"
+#include "frames.h"
 
 
 HRESULT GetNumChild(ICorDebugValue *pValue,
@@ -275,6 +276,85 @@ static HRESULT FetchFieldsAndProperties(ICorDebugValue *pInputValue,
     return S_OK;
 }
 
+struct Member
+{
+    std::string name;
+    std::string ownerType;
+    ToRelease<ICorDebugValue> value;
+    Member(const std::string &name, const std::string ownerType, ToRelease<ICorDebugValue> value) :
+        name(name),
+        ownerType(ownerType),
+        value(std::move(value))
+    {}
+    Member(Member &&that) = default;
+private:
+    Member(const Member &that) = delete;
+};
+
+static HRESULT FetchFieldsAndProperties2(ICorDebugValue *pInputValue,
+                                        ICorDebugThread *pThread,
+                                        ICorDebugILFrame *pILFrame,
+                                        std::vector<Member> &members,
+                                        bool fetchOnlyStatic,
+                                        bool &hasStaticMembers,
+                                        int childStart,
+                                        int childEnd)
+{
+    hasStaticMembers = false;
+    HRESULT Status;
+
+    DWORD threadId = 0;
+    IfFailRet(pThread->GetID(&threadId));
+
+    int currentIndex = -1;
+
+    IfFailRet(WalkMembers(pInputValue, pThread, pILFrame, [&](
+        mdMethodDef mdGetter,
+        ICorDebugModule *pModule,
+        ICorDebugType *pType,
+        ICorDebugValue *pValue,
+        bool is_static,
+        const std::string &name)
+    {
+        if (is_static)
+            hasStaticMembers = true;
+
+        bool addMember = fetchOnlyStatic ? is_static : !is_static;
+        if (!addMember)
+            return S_OK;
+
+        ++currentIndex;
+        if (currentIndex < childStart)
+            return S_OK;
+        if (currentIndex >= childEnd)
+            return S_OK;
+
+        std::string className;
+        if (pType)
+            TypePrinter::GetTypeOfValue(pType, className);
+
+        ToRelease<ICorDebugValue> pResultValue;
+
+        if (mdGetter != mdMethodDefNil)
+        {
+            ToRelease<ICorDebugFunction> pFunc;
+            if (SUCCEEDED(pModule->GetFunctionFromToken(mdGetter, &pFunc)))
+                EvalFunction(pThread, pFunc, pType, is_static ? nullptr : pInputValue, &pResultValue);
+        }
+        else
+        {
+            if (pValue)
+                pValue->AddRef();
+            pResultValue = pValue;
+        }
+
+        members.emplace_back(name, className, std::move(pResultValue));
+        return S_OK;
+    }));
+
+    return S_OK;
+}
+
 static void FixupInheritedFieldNames(std::vector<VarObjValue> &members)
 {
     std::unordered_set<std::string> names;
@@ -424,66 +504,207 @@ HRESULT ListChildren(
     return ListChildren(childStart, childEnd, it->second, print_values, pThread, pFrame, output);
 }
 
-HRESULT ListVariables(ICorDebugThread *pThread, ICorDebugFrame *pFrame, std::string &output)
+HRESULT Debugger::GetVariables(uint32_t variablesReference, VariablesFilter filter, int start, int count, std::vector<Variable> &variables)
 {
-    const bool printValues = true;
-    const bool printTypes = false;
+    auto it = m_variables.find(variablesReference);
+    if (it == m_variables.end())
+        return E_FAIL;
+
+    VariableReference &ref = it->second;
 
     HRESULT Status;
 
-    std::stringstream ss;
-    ss << "variables=[";
-    const char *sep = "";
+    StackFrame stackFrame(ref.frameId);
+    ToRelease<ICorDebugThread> pThread;
+    IfFailRet(m_pProcess->GetThread(stackFrame.GetThreadId(), &pThread));
+    ToRelease<ICorDebugFrame> pFrame;
+    IfFailRet(GetFrameAt(pThread, stackFrame.GetLevel(), &pFrame));
+
+    // Named and Indexed variables are in the same index (internally), Named variables go first
+    if (filter == VariablesNamed && (start + count > ref.namedVariables || count == 0))
+        count = ref.namedVariables - start;
+    if (filter == VariablesIndexed)
+        start += ref.namedVariables;
+
+    if (ref.IsScope())
+    {
+        IfFailRet(GetStackVariables(ref.frameId, pThread, pFrame, start, count, variables));
+    } else {
+        IfFailRet(GetChildren(ref, pThread, pFrame, start, count, variables));
+    }
+    return S_OK;
+}
+
+void Debugger::AddVariableReference(Variable &variable, uint64_t frameId, ICorDebugValue *value, VariableReference::ValueKind valueKind)
+{
+    HRESULT Status;
+    unsigned int numChild = 0;
+    GetNumChild(value, numChild, valueKind == VariableReference::ValueIsClass);
+    if (numChild == 0)
+        return;
+
+    variable.namedVariables = numChild;
+    variable.variablesReference = m_nextVariableReference++;
+    value->AddRef();
+    VariableReference variableReference(variable.variablesReference, frameId, value, valueKind);
+    variableReference.evaluateName = variable.evaluateName;
+    m_variables.emplace(std::make_pair(variable.variablesReference, std::move(variableReference)));
+}
+
+HRESULT Debugger::GetStackVariables(uint64_t frameId, ICorDebugThread *pThread, ICorDebugFrame *pFrame, int start, int count, std::vector<Variable> &variables)
+{
+    HRESULT Status;
+
+    int currentIndex = -1;
 
     ToRelease<ICorDebugValue> pExceptionValue;
     if (SUCCEEDED(pThread->GetCurrentException(&pExceptionValue)) && pExceptionValue != nullptr)
     {
-        ss << sep;
-        sep = ",";
-
-        ss << "{name=\"" << "$exception" << "\"";
-        if (printValues)
-        {
-            std::string strVal;
-            if (SUCCEEDED(PrintValue(pExceptionValue, strVal)))
-                ss << ",value=\"" << strVal << "\"";
-        }
-        if (printTypes)
+        ++currentIndex;
+        bool outOfRange = currentIndex < start || (count != 0 && currentIndex >= start + count);
+        if (!outOfRange)
         {
-            std::string strVal;
-            if (SUCCEEDED(TypePrinter::GetTypeOfValue(pExceptionValue, strVal)))
-                ss << ",type=\"" << strVal << "\"";
+            Variable var;
+            var.name = "$exception";
+            var.evaluateName = var.name;
+            bool escape = true;
+            PrintValue(pExceptionValue, var.value, escape);
+            TypePrinter::GetTypeOfValue(pExceptionValue, var.type);
+            AddVariableReference(var, frameId, pExceptionValue, VariableReference::ValueIsVariable);
+            variables.push_back(var);
         }
-
-        ss << "}";
     }
 
     IfFailRet(WalkStackVars(pFrame, [&](ICorDebugILFrame *pILFrame, ICorDebugValue *pValue, const std::string &name) -> HRESULT
     {
-        ss << sep;
-        sep = ",";
+        ++currentIndex;
+        if (currentIndex < start || (count != 0 && currentIndex >= start + count))
+            return S_OK;
+        Variable var;
+        var.name = name;
+        var.evaluateName = var.name;
+        bool escape = true;
+        PrintValue(pValue, var.value, escape);
+        TypePrinter::GetTypeOfValue(pValue, var.type);
+        AddVariableReference(var, frameId, pValue, VariableReference::ValueIsVariable);
+        variables.push_back(var);
+        return S_OK;
+    }));
 
-        ss << "{name=\"" << name << "\"";
-        if (printValues)
-        {
-            std::string strVal;
-            if (SUCCEEDED(PrintValue(pValue, strVal)))
-                ss << ",value=\"" << strVal << "\"";
-        }
-        if (printTypes)
+    return S_OK;
+}
+
+HRESULT Debugger::GetScopes(uint64_t frameId, std::vector<Scope> &scopes)
+{
+    HRESULT Status;
+
+    StackFrame stackFrame(frameId);
+    ToRelease<ICorDebugThread> pThread;
+    IfFailRet(m_pProcess->GetThread(stackFrame.GetThreadId(), &pThread));
+    ToRelease<ICorDebugFrame> pFrame;
+    IfFailRet(GetFrameAt(pThread, stackFrame.GetLevel(), &pFrame));
+
+    int namedVariables = 0;
+    uint32_t variablesReference = 0;
+
+    ToRelease<ICorDebugValue> pExceptionValue;
+    if (SUCCEEDED(pThread->GetCurrentException(&pExceptionValue)) && pExceptionValue != nullptr)
+        namedVariables++;
+
+    IfFailRet(WalkStackVars(pFrame, [&](ICorDebugILFrame *pILFrame, ICorDebugValue *pValue, const std::string &name) -> HRESULT
+    {
+        namedVariables++;
+        return S_OK;
+    }));
+
+    if (namedVariables > 0)
+    {
+        variablesReference = m_nextVariableReference++;
+        VariableReference scopeReference(variablesReference, frameId, namedVariables);
+        m_variables.emplace(std::make_pair(variablesReference, std::move(scopeReference)));
+    }
+
+    scopes.emplace_back(variablesReference, "Locals", frameId);
+
+    return S_OK;
+}
+
+static void FixupInheritedFieldNames2(std::vector<Member> &members)
+{
+    std::unordered_set<std::string> names;
+    for (auto &it : members)
+    {
+        auto r = names.insert(it.name);
+        if (!r.second)
         {
-            std::string strVal;
-            if (SUCCEEDED(TypePrinter::GetTypeOfValue(pValue, strVal)))
-                ss << ",type=\"" << strVal << "\"";
+            it.name += " (" + it.ownerType + ")";
         }
+    }
+}
 
-        ss << "}";
+HRESULT Debugger::GetChildren(VariableReference &ref,
+                              ICorDebugThread *pThread,
+                              ICorDebugFrame *pFrame,
+                              int start,
+                              int count,
+                              std::vector<Variable> &variables)
+{
+    if (ref.IsScope())
+        return E_INVALIDARG;
 
+    HRESULT Status;
+
+    ToRelease<ICorDebugILFrame> pILFrame;
+    if (pFrame)
+        IfFailRet(pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame));
+
+    std::vector<Member> members;
+
+    bool hasStaticMembers = false;
+
+    if (!ref.value)
         return S_OK;
-    }));
 
-    ss << "]";
-    output = ss.str();
+    IfFailRet(FetchFieldsAndProperties2(ref.value,
+                                       pThread,
+                                       pILFrame,
+                                       members,
+                                       ref.valueKind == VariableReference::ValueIsClass,
+                                       hasStaticMembers,
+                                       start,
+                                       count == 0 ? INT_MAX : start + count));
+
+    FixupInheritedFieldNames2(members);
+
+    for (auto &it : members)
+    {
+        Variable var;
+        var.name = it.name;
+        bool isIndex = !it.name.empty() && it.name.at(0) == '[';
+        if (var.name.find('(') == std::string::npos) // expression evaluator does not support typecasts
+            var.evaluateName = ref.evaluateName + (isIndex ? "" : ".") + var.name;
+        bool escape = true;
+        PrintValue(it.value, var.value, escape);
+        TypePrinter::GetTypeOfValue(it.value, var.type);
+        AddVariableReference(var, ref.frameId, it.value, VariableReference::ValueIsVariable);
+        variables.push_back(var);
+    }
+
+    if (ref.valueKind == VariableReference::ValueIsVariable && hasStaticMembers)
+    {
+        bool staticsInRange = start < ref.namedVariables && (count == 0 || start + count >= ref.namedVariables);
+        if (staticsInRange)
+        {
+            RunClassConstructor(pThread, ref.value);
+
+            Variable var;
+            var.name = "Static members";
+            TypePrinter::GetTypeOfValue(ref.value, var.evaluateName); // do not expose type for this fake variable
+            AddVariableReference(var, ref.frameId, ref.value, VariableReference::ValueIsClass);
+            variables.push_back(var);
+        }
+    }
+
     return S_OK;
 }
 
index 88f5a2a..4d0f5c0 100644 (file)
@@ -2,7 +2,6 @@
 // Distributed under the MIT License.
 // See the LICENSE file in the project root for more information.
 
-HRESULT ListVariables(ICorDebugThread *pThread, ICorDebugFrame *pFrame, std::string &output);
 HRESULT CreateVar(ICorDebugThread *pThread, ICorDebugFrame *pFrame, const std::string &varobjName, const std::string &expression, std::string &output);
 HRESULT ListChildren(int childStart, int childEnd, const std::string &name, int print_values, ICorDebugThread *pThread, ICorDebugFrame *pFrame, std::string &output);
 HRESULT DeleteVar(const std::string &varobjName);