Variables: add set value support and test
authorAlexander Aksenov <a.aksenov@samsung.com>
Wed, 8 Aug 2018 17:45:27 +0000 (20:45 +0300)
committerPetr Bred/AI Ecosystem Lab /SRR/Staff Engineer/삼성전자 <p.bred@samsung.com>
Sun, 14 Oct 2018 07:04:19 +0000 (10:04 +0300)
Signed-off-by: Alexander Aksenov <a.aksenov@samsung.com>
17 files changed:
src/debug/netcoredbg/CMakeLists.txt
src/debug/netcoredbg/cputil.cpp
src/debug/netcoredbg/debugger.h
src/debug/netcoredbg/expr.cpp
src/debug/netcoredbg/manageddebugger.h
src/debug/netcoredbg/miprotocol.cpp
src/debug/netcoredbg/miprotocol.h
src/debug/netcoredbg/symbolreader.h
src/debug/netcoredbg/torelease.h
src/debug/netcoredbg/valuewrite.cpp [new file with mode: 0644]
src/debug/netcoredbg/valuewrite.h [new file with mode: 0644]
src/debug/netcoredbg/variables.cpp
src/debug/netcoredbg/vscodeprotocol.cpp
tests/SetValuesTest/SetValuesTest.cs [new file with mode: 0644]
tests/SetValuesTest/SetValuesTest.csproj [new file with mode: 0644]
tests/runner/Runner.cs
tests/tests.sln

index 2f815ab9c2faa488dccc662cedd090e6fcbf76b7..aaee00f7c99c40114b3cab2ad6a8024c82505d60 100644 (file)
@@ -41,7 +41,8 @@ set(netcoredbg_SRC
     frames.cpp
     jmc.cpp
     cputil.cpp
-    expr.cpp)
+    expr.cpp
+    valuewrite.cpp)
 
 set(CMAKE_INCLUDE_CURRENT_DIR ON)
 
index 676506d1b4a9f59841b60bbd342551b40a7fdd37..abaf24b3f9665477e2913d1cfb6c41d4edaa24c0 100644 (file)
@@ -1,3 +1,7 @@
+// Copyright (c) 2018 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
 #include "cputil.h"
 
 #include <codecvt>
index 064e91a2f6de595d68afe11bbd9d31481beaa923..24aa400e04c9c387053ec252fc9dd804f4c9abcf 100644 (file)
@@ -57,6 +57,8 @@ public:
     virtual HRESULT GetVariables(uint32_t variablesReference, VariablesFilter filter, int start, int count, std::vector<Variable> &variables) = 0;
     virtual int GetNamedVariables(uint32_t variablesReference) = 0;
     virtual HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable) = 0;
+    virtual HRESULT SetVariable(const std::string &name, const std::string &value, uint32_t ref) = 0;
+    virtual HRESULT SetVariableByExpression(uint64_t frameId, const std::string &name, const std::string &value) = 0;
 };
 
 class Protocol
index a8579d0f02904fe12567fbacd4056130806e8f13..668a28dc5f281db44c8cd24b5c2e467a7234f5b4 100644 (file)
@@ -710,3 +710,16 @@ HRESULT Evaluator::EvalExpr(ICorDebugThread *pThread,
 
     return S_OK;
 }
+
+HRESULT Evaluator::CreateString(ICorDebugThread *pThread, const std::string &value, ICorDebugValue **ppNewString)
+{
+    HRESULT Status;
+    auto value16t = to_utf16(value);
+
+    ToRelease<ICorDebugEval> pEval;
+    IfFailRet(pThread->CreateEval(&pEval));
+
+    IfFailRet(pEval->NewString(value16t.c_str()));
+
+    return WaitEvalResult(pThread, pEval, ppNewString);
+}
index e1b061076e4dca351603d41a3eb15d18cf1b542f..f664c0826a62cc54fc353f733c2ea230e4bc7a6b 100644 (file)
@@ -164,6 +164,11 @@ public:
 
     HRESULT WalkStackVars(ICorDebugFrame *pFrame, WalkStackVarsCallback cb);
 
+    HRESULT CreateString(
+        ICorDebugThread *pThread,
+        const std::string &value,
+        ICorDebugValue **ppNewString);
+
     void Cleanup();
 };
 
@@ -312,6 +317,20 @@ class Variables
         unsigned int &numchild,
         bool static_members = false);
 
+    HRESULT SetStackVariable(
+        uint64_t frameId,
+        ICorDebugThread *pThread,
+        ICorDebugFrame *pFrame,
+        const std::string &name,
+        const std::string &value);
+
+    HRESULT SetChild(
+        VariableReference &ref,
+        ICorDebugThread *pThread,
+        ICorDebugFrame *pFrame,
+        const std::string &name,
+        const std::string &value);
+
 public:
 
     Variables(Evaluator &evaluator) : m_evaluator(evaluator), m_nextVariableReference(1) {}
@@ -326,10 +345,28 @@ public:
         int count,
         std::vector<Variable> &variables);
 
+    HRESULT SetVariable(
+        ICorDebugProcess *pProcess,
+        const std::string &name,
+        const std::string &value,
+        uint32_t ref);
+
+    HRESULT SetVariable(
+        ICorDebugProcess *pProcess,
+        ICorDebugValue *pVariable,
+        const std::string &value,
+        uint64_t frameId);
+
     HRESULT GetScopes(ICorDebugProcess *pProcess, uint64_t frameId, std::vector<Scope> &scopes);
 
     HRESULT Evaluate(ICorDebugProcess *pProcess, uint64_t frameId, const std::string &expression, Variable &variable);
 
+    HRESULT GetValueByExpression(
+        ICorDebugProcess *pProcess,
+        uint64_t frameId,
+        const std::string &expression,
+        ICorDebugValue **ppResult);
+
     void Clear() { m_variables.clear(); m_nextVariableReference = 1; }
 };
 
@@ -435,4 +472,6 @@ public:
     HRESULT GetVariables(uint32_t variablesReference, VariablesFilter filter, int start, int count, std::vector<Variable> &variables) override;
     int GetNamedVariables(uint32_t variablesReference) override;
     HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable) override;
+    HRESULT SetVariable(const std::string &name, const std::string &value, uint32_t ref) override;
+    HRESULT SetVariableByExpression(uint64_t frameId, const std::string &expression, const std::string &value) override;
 };
index 9a29033893031f65965a63c3413491951b337835..52ca6c2e40f183b7e4c450df116cd5e2ae7c73bb 100644 (file)
@@ -266,11 +266,34 @@ HRESULT MIProtocol::PrintVariables(const std::vector<Variable> &variables, std::
     return S_OK;
 }
 
+bool MIProtocol::IsEditable(const std::string &type)
+{
+    if (type == "int"
+        || type == "bool"
+        || type == "char"
+        || type == "byte"
+        || type == "sbyte"
+        || type == "short"
+        || type == "ushort"
+        || type == "uint"
+        || type == "long"
+        || type == "ulong"
+        || type == "decimal"
+        || type == "string")
+        return true;
+
+    return false;
+}
+
 void MIProtocol::PrintVar(const std::string &varobjName, Variable &v, int threadId, int print_values, std::string &output)
 {
     std::ostringstream ss;
 
-    std::string editable = "noneditable";
+    std::string editable;
+    if (IsEditable(v.type))
+        editable = "editable";
+    else
+        editable = "noneditable";
 
     ss << "name=\"" << varobjName << "\",";
     if (print_values)
@@ -319,6 +342,17 @@ HRESULT MIProtocol::DeleteVar(const std::string &varobjName)
     return m_vars.erase(varobjName) == 0 ? E_FAIL : S_OK;
 }
 
+HRESULT MIProtocol::FindVar(const std::string &varobjName, Variable &variable)
+{
+    auto it = m_vars.find(varobjName);
+    if (it == m_vars.end())
+        return E_FAIL;
+
+    variable = it->second;
+
+    return S_OK;
+}
+
 void MIProtocol::Cleanup()
 {
     m_vars.clear();
@@ -796,8 +830,47 @@ HRESULT MIProtocol::HandleCommand(std::string command,
 
         return S_OK;
     }},
-    { "var-show-attributes", [](const std::vector<std::string> &args, std::string &output) -> HRESULT {
-        output = "status=\"noneditable\"";
+    { "var-show-attributes", [this](const std::vector<std::string> &args, std::string &output) -> HRESULT {
+        HRESULT Status;
+        Variable variable;
+        std::string varName = args.at(0);
+        std::string editable;
+
+        IfFailRet(FindVar(varName, variable));
+        if (IsEditable(variable.type))
+            editable = "editable";
+        else
+            editable = "noneditable";
+
+        output = "status=\"" + editable + "\"";
+        return S_OK;
+    }},
+    { "var-assign", [this](const std::vector<std::string> &args, std::string &output) -> HRESULT {
+        HRESULT Status;
+
+        if (args.size() < 2)
+        {
+            output = "Command requires at least 2 arguments";
+            return E_FAIL;
+        }
+
+        std::string varName = args.at(0);
+        std::string varExpr = args.at(1);
+
+        if (varExpr.size() >= 2 && varExpr.front() == '"' && varExpr.back() == '"')
+            varExpr = varExpr.substr(1, varExpr.size() - 2);
+
+        int threadId = GetIntArg(args, "--thread", m_debugger->GetLastStoppedThreadId());
+        int level = GetIntArg(args, "--frame", 0);
+        uint64_t frameId = StackFrame(threadId, level, "").id;
+
+        Variable variable;
+        IfFailRet(FindVar(varName, variable));
+
+        m_debugger->SetVariableByExpression(frameId, variable.evaluateName, varExpr);
+
+        output = "value=\"" + MIProtocol::EscapeMIValue(varExpr) + "\"";
+
         return S_OK;
     }},
     };
@@ -813,51 +886,75 @@ HRESULT MIProtocol::HandleCommand(std::string command,
     return command_it->second(args, output);
 }
 
-static std::vector<std::string> TokenizeString(const std::string &str, const char *delimiters = " \t\n\r")
+class Tokenizer
 {
-    enum {
-        StateSpace,
-        StateToken,
-        StateQuotedToken,
-        StateEscape
-    } state = StateSpace;
+    std::string m_str;
+    std::string m_delimiters;
+    size_t m_next;
+public:
 
-    std::vector<std::string> result;
+    Tokenizer(const std::string &str, const std::string &delimiters = " \t\n\r")
+        : m_str(str), m_delimiters(delimiters), m_next(0)
+    {
+        m_str.erase(m_str.find_last_not_of(m_delimiters) + 1);
+    }
 
-    for (char c : str)
+    bool Next(std::string &token)
     {
-        switch(state)
+        token = "";
+
+        if (m_next >= m_str.size())
+            return false;
+
+        enum {
+            StateSpace,
+            StateToken,
+            StateQuotedToken,
+            StateEscape
+        } state = StateSpace;
+
+        for (; m_next < m_str.size(); m_next++)
         {
-            case StateSpace:
-                if (strchr(delimiters, c))
-                    continue;
-                result.emplace_back();
-                state = c == '"' ? StateQuotedToken : StateToken;
-                if (state != StateQuotedToken)
-                    result.back() +=c;
-                break;
-            case StateToken:
-                if (strchr(delimiters, c))
-                    state = StateSpace;
-                else
-                    result.back() += c;
-                break;
-            case StateQuotedToken:
-                if (c == '\\')
-                    state = StateEscape;
-                else if (c == '"')
-                    state = StateSpace;
-                else
-                    result.back() += c;
-                break;
-            case StateEscape:
-                result.back() += c;
-                state = StateQuotedToken;
-                break;
+            char c = m_str.at(m_next);
+            switch(state)
+            {
+                case StateSpace:
+                    if (m_delimiters.find(c) != std::string::npos)
+                        continue;
+                    if (!token.empty())
+                        return true;
+                    state = c == '"' ? StateQuotedToken : StateToken;
+                    if (state != StateQuotedToken)
+                        token +=c;
+                    break;
+                case StateToken:
+                    if (m_delimiters.find(c) != std::string::npos)
+                        state = StateSpace;
+                    else
+                        token += c;
+                    break;
+                case StateQuotedToken:
+                    if (c == '\\')
+                        state = StateEscape;
+                    else if (c == '"')
+                        state = StateSpace;
+                    else
+                        token += c;
+                    break;
+                case StateEscape:
+                    token += c;
+                    state = StateQuotedToken;
+                    break;
+            }
         }
+        return state != StateEscape || token.empty();
     }
-    return result;
-}
+
+    std::string Remain() const
+    {
+        return m_str.substr(m_next);
+    }
+};
 
 static bool ParseLine(const std::string &str,
                       std::string &token,
@@ -868,24 +965,32 @@ static bool ParseLine(const std::string &str,
     cmd.clear();
     args.clear();
 
-    std::vector<std::string> result = TokenizeString(str);
+    Tokenizer tokenizer(str);
+    std::string result;
 
-    if (result.empty())
+    if (!tokenizer.Next(result) || result.empty())
         return false;
 
-    auto cmd_it = result.begin();
-
-    std::size_t i = cmd_it->find_first_not_of("0123456789");
+    std::size_t i = result.find_first_not_of("0123456789");
     if (i == std::string::npos)
         return false;
 
-    if (cmd_it->at(i) != '-')
+    if (result.at(i) != '-')
         return false;
 
-    token = cmd_it->substr(0, i);
-    cmd = cmd_it->substr(i + 1);
-    result.erase(result.begin());
-    args = result;
+    token = result.substr(0, i);
+    cmd = result.substr(i + 1);
+
+    if (cmd == "var-assign")
+    {
+        tokenizer.Next(result);
+        args.push_back(result);
+        args.push_back(tokenizer.Remain());
+        return true;
+    }
+
+    while (tokenizer.Next(result))
+        args.push_back(result);
 
     return true;
 }
index 17ace81c5d57415e927112b35a08658afdc0faf8..684a240bb0b25b037e9ee938eaa31b072372046c 100644 (file)
@@ -32,6 +32,8 @@ class MIProtocol : public Protocol
     static void Printf(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 #endif
 
+    static bool IsEditable(const std::string &type);
+
 public:
 
     MIProtocol() : Protocol(), m_varCounter(0) {}
@@ -65,6 +67,7 @@ private:
     HRESULT PrintVariables(const std::vector<Variable> &variables, std::string &output);
     HRESULT CreateVar(int threadId, int level, const std::string &varobjName, const std::string &expression, std::string &output);
     HRESULT DeleteVar(const std::string &varobjName);
+    HRESULT FindVar(const std::string &varobjName, Variable &variable);
     void PrintChildren(std::vector<Variable> &children, int threadId, int print_values, bool has_more, std::string &output);
     void PrintNewVar(std::string varobjName, Variable &v, int threadId, int print_values, std::string &output);
     HRESULT ListChildren(int threadId, int level, int childStart, int childEnd, const std::string &varName, int print_values, std::string &output);
index fdf4b0d4c0df8a1da3c84ba98f7f5d45bd108fe2..58ba424028b5de84f5f2ace426a1ce5feb85176e 100644 (file)
@@ -66,19 +66,13 @@ private:
 public:
     static const int HiddenLine;
 
-#ifdef _MSC_VER
-#define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) )
-#else
-#define PACK( __Declaration__ ) __Declaration__ __attribute__((packed))
-#endif
-
-    PACK(struct SequencePoint {
+    PACK_BEGIN struct SequencePoint {
         int32_t startLine;
         int32_t startColumn;
         int32_t endLine;
         int32_t endColumn;
         int32_t offset;
-    });
+    } PACK_END;
 
     SymbolReader()
     {
index e944ad27e49e5714dcaaa56bcda6c4f6b507d583..fa49d1e34bd950691239bfee7b810231691d9ee2 100644 (file)
@@ -118,3 +118,11 @@ typedef ULONG64 CLRDATA_ADDRESS;
 #define TO_CDADDR(taddr) ((CLRDATA_ADDRESS)(LONG_PTR)(taddr))
 
 const int mdNameLen = 2048;
+
+#ifdef _MSC_VER
+#define PACK_BEGIN __pragma( pack(push, 1) )
+#define PACK_END __pragma( pack(pop) )
+#else
+#define PACK_BEGIN
+#define PACK_END __attribute__((packed))
+#endif
diff --git a/src/debug/netcoredbg/valuewrite.cpp b/src/debug/netcoredbg/valuewrite.cpp
new file mode 100644 (file)
index 0000000..5a11e55
--- /dev/null
@@ -0,0 +1,343 @@
+// Copyright (c) 2018 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#ifndef FEATURE_PAL
+// Turn off macro definitions named max and min in <windows.h> header file
+// to avoid compile error for std::numeric_limits<uint64_t>::max().
+#define NOMINMAX
+#endif
+
+#include "valuewrite.h"
+
+#include <locale>
+#include <codecvt>
+#include <cstring>
+#include <sstream>
+#include <iomanip>
+
+#include <arrayholder.h>
+
+#include "torelease.h"
+#include "cputil.h"
+#include "typeprinter.h"
+
+
+PACK_BEGIN struct Decimal {
+    unsigned int unused : 16;
+    unsigned int exponent : 8;
+    unsigned int unused2 : 7;
+    unsigned int sign : 1;
+
+    uint32_t hi;
+    uint32_t lo;
+    uint32_t mid;
+
+    Decimal() : unused(0), exponent(0), unused2(0), sign(0), hi(0), lo(0), mid(0) {}
+    Decimal(uint32_t lo, uint32_t mid, uint32_t hi, uint8_t exp, unsigned int sign) : unused(0),
+        exponent(exp), unused2(0), sign(sign), hi(hi), lo(lo), mid(mid) {}
+    void Mul10();
+    void AddInt32(uint32_t val);
+    bool Parse(const std::string &value);
+
+private:
+    void ShiftLeft();
+    void Add(Decimal &d);
+    static bool AddCarry(uint32_t &to, uint32_t val);
+} PACK_END;
+
+bool Decimal::AddCarry(uint32_t &to, uint32_t val)
+{
+    uint32_t v = to;
+    to += val;
+    return (to < v) || (to < val);
+}
+
+void Decimal::ShiftLeft()
+{
+    uint32_t c0 = (lo & 0x80000000) != 0 ? 1u : 0u;
+    uint32_t c1 = (mid & 0x80000000) != 0 ? 1u : 0u;
+
+    lo = lo << 1;
+    mid = (mid << 1) | c0;
+    hi = (hi << 1) | c1;
+}
+
+void Decimal::Add(Decimal &d)
+{
+    if (AddCarry(lo, d.lo))
+        if (AddCarry(mid, 1))
+            AddCarry(hi, 1);
+
+    if (AddCarry(mid, d.mid))
+        AddCarry(hi, 1);
+
+    AddCarry(hi, d.hi);
+}
+
+void Decimal::Mul10()
+{
+    Decimal d(lo, mid, hi, exponent, sign);
+
+    ShiftLeft();
+    ShiftLeft();
+    Add(d);
+    ShiftLeft();
+}
+
+void Decimal::AddInt32(uint32_t val)
+{
+    if (AddCarry(lo, val))
+        if (AddCarry(mid, 1))
+            AddCarry(hi, 1);
+}
+
+
+
+
+const int DECIMAL_PRECISION = 29;
+
+bool Decimal::Parse(const std::string &value)
+{
+    std::string valueToStore(value);
+    bool is_negative = valueToStore.length() > 0 && valueToStore[0] == '-';
+
+    if (is_negative) {
+        sign = 1;
+        valueToStore.erase(0, 1);
+    }
+    else
+    {
+        sign = 0;
+    }
+
+    // Cut all meaningless first zeroes
+    const auto ix = valueToStore.find_first_not_of('0');
+    valueToStore.erase(0, ix);
+
+    const auto dotPos = valueToStore.find(".");
+    if (dotPos != std::string::npos)
+        valueToStore.erase(dotPos, 1);
+
+    if (dotPos > std::numeric_limits<int>::max())
+        return false;
+
+    int scale = static_cast<int>(dotPos);
+
+    const unsigned char* p = reinterpret_cast<const unsigned char*>(valueToStore.c_str());
+
+    if (*p == 0)
+    {
+        // To avoid risking an app-compat issue with pre 4.5 (where some app was illegally using Reflection to examine the internal scale bits), we'll only force
+        // the scale to 0 if the scale was previously positive (previously, such cases were unparsable to a bug.)
+        if (scale > 0)
+            scale = 0;
+    }
+    else
+    {
+        if (scale > DECIMAL_PRECISION)
+            return false;
+
+        while (((scale > 0) || ((*p != 0) && (scale > -(DECIMAL_PRECISION - 1)))) &&
+               ((hi < 0x19999999) || ((hi == 0x19999999) &&
+                                      ((mid < 0x99999999) || ((mid == 0x99999999) &&
+                                                              ((lo < 0x99999999) || ((lo == 0x99999999) &&
+                                                                                     (*p <= '5'))))))))
+        {
+            Mul10();
+
+            if (*p != 0)
+                AddInt32((uint32_t)(*p++ - '0'));
+            scale--;
+        }
+
+        if (*p++ >= '5')
+        {
+            bool round = true;
+
+            if ((*(p - 1) == '5') && ((*(p - 2) % 2) == 0))
+            {
+                // Check if previous digit is even, only if the when we are unsure whether hows to do
+                // Banker's rounding. For digits > 5 we will be rounding up anyway.
+                int count = 20; // Look at the next 20 digits to check to round
+                while ((*p == '0') && (count != 0))
+                {
+                    p++;
+                    count--;
+                }
+                if ((*p == '\0') || (count == 0))
+                    round = false;// Do nothing
+            }
+
+            if (round)
+            {
+                AddInt32(1);
+                if ((hi | mid | lo) == 0)
+                {
+                    // If we got here, the magnitude portion overflowed and wrapped back to 0 as the magnitude was already at the MaxValue point:
+                    //
+                    //     79,228,162,514,264,337,593,543,950,335e+X
+                    //
+                    // Manually force it to the correct result:
+                    //
+                    //      7,922,816,251,426,433,759,354,395,034e+(X+1)
+                    //
+                    // This code path can be reached by trying to parse the following as a Decimal:
+                    //
+                    //      0.792281625142643375935439503355e28
+                    //
+
+                    hi = 0x19999999;
+                    mid = 0x99999999;
+                    lo = 0x9999999A;
+                    scale++;
+                }
+            }
+        }
+    }
+
+    if (scale > 0)
+        return false; // Rounding may have caused its own overflow. For example, parsing "0.792281625142643375935439503355e29" will get here.
+
+    if (scale <= -DECIMAL_PRECISION)
+    {
+        // Parsing a large scale zero can give you more precision than fits in the decimal.
+        // This should only happen for actual zeros or very small numbers that round to zero.
+        hi = 0;
+        lo = 0;
+        mid = 0;
+        exponent = DECIMAL_PRECISION - 1;
+    }
+    else
+    {
+        exponent = -scale;
+    }
+
+    return true;
+}
+
+HRESULT WriteValue(ICorDebugValue *pValue, const std::string &value, ICorDebugThread *pThread, Evaluator &evaluator)
+{
+    HRESULT Status;
+
+    ULONG32 size;
+    IfFailRet(pValue->GetSize(&size));
+
+    ArrayHolder<BYTE> valBuf = new (std::nothrow) BYTE[size];
+    if (valBuf == nullptr)
+        return E_OUTOFMEMORY;
+
+    CorElementType corType;
+    IfFailRet(pValue->GetType(&corType));
+
+    if (corType == ELEMENT_TYPE_STRING)
+    {
+        ToRelease<ICorDebugValue> pNewString;
+        IfFailRet(evaluator.CreateString(pThread, value, &pNewString));
+
+        // Switch object addresses
+        ToRelease<ICorDebugReferenceValue> pRefNew;
+        IfFailRet(pNewString->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID *) &pRefNew));
+        ToRelease<ICorDebugReferenceValue> pRefOld;
+        IfFailRet(pValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID *) &pRefOld));
+
+        CORDB_ADDRESS addr;
+        IfFailRet(pRefNew->GetValue(&addr));
+        IfFailRet(pRefOld->SetValue(addr));
+
+        return S_OK;
+    }
+
+    ToRelease<ICorDebugGenericValue> pGenValue;
+    IfFailRet(pValue->QueryInterface(IID_ICorDebugGenericValue, (LPVOID *) &pGenValue));
+
+    std::stringstream ss(value);
+
+    switch (corType)
+    {
+        case ELEMENT_TYPE_BOOLEAN:
+            if (value == "false")
+                valBuf[0] = 0;
+            else if (value == "true")
+                valBuf[0] = 1;
+            else
+                return E_INVALIDARG;
+
+            break;
+        case ELEMENT_TYPE_CHAR:
+        {
+            auto wval = to_utf16(value);
+
+            if (wval.length() > 1)
+                return E_INVALIDARG;
+
+            std::memcpy(&valBuf[0], &wval[0], sizeof(wval[0]));
+
+            break;
+        }
+        case ELEMENT_TYPE_I1:
+        {
+            int num;
+
+            ss >> num;
+            valBuf[0] = (char)num;
+            break;
+        }
+        case ELEMENT_TYPE_U1:
+        {
+            int num;
+
+            ss >> num;
+            valBuf[0] = (unsigned char)num;
+            break;
+        }
+        case ELEMENT_TYPE_I2:
+            ss >> *(short *)&valBuf[0];
+            break;
+        case ELEMENT_TYPE_U2:
+            ss >> *(unsigned short *)&valBuf[0];
+            break;
+        case ELEMENT_TYPE_I4:
+        case ELEMENT_TYPE_I:
+            ss >> *(int *)&valBuf[0];
+            break;
+        case ELEMENT_TYPE_U4:
+        case ELEMENT_TYPE_U:
+            ss >> *(unsigned int *)&valBuf[0];
+            break;
+        case ELEMENT_TYPE_I8:
+            ss >> *(__int64 *)&valBuf[0];
+            break;
+        case ELEMENT_TYPE_U8:
+            ss >> *(unsigned __int64 *)&valBuf[0];
+            break;
+        case ELEMENT_TYPE_R4:
+            ss >> std::setprecision(8) >> *(float *)&valBuf[0];
+            break;
+        case ELEMENT_TYPE_R8:
+            ss >> std::setprecision(16) >> *(double *)&valBuf[0];
+            break;
+        case ELEMENT_TYPE_VALUETYPE:
+        case ELEMENT_TYPE_CLASS:
+        {
+            std::string typeName;
+            TypePrinter::GetTypeOfValue(pValue, typeName);
+            if (typeName == "decimal")
+            {
+                Decimal d;
+                if (!d.Parse(value))
+                    return E_INVALIDARG;
+                if (sizeof(Decimal) > size)
+                    return E_FAIL;
+                std::memcpy(&valBuf[0], &d, size);
+            }
+            break;
+        }
+        default:
+            return S_OK;
+    }
+
+    IfFailRet(pGenValue->SetValue((LPVOID) &valBuf[0]));
+
+    return S_OK;
+}
diff --git a/src/debug/netcoredbg/valuewrite.h b/src/debug/netcoredbg/valuewrite.h
new file mode 100644 (file)
index 0000000..4ea2235
--- /dev/null
@@ -0,0 +1,13 @@
+// Copyright (c) 2018 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#pragma once
+
+#include <string>
+
+#include <cor.h>
+#include <cordebug.h>
+#include "manageddebugger.h"
+
+HRESULT WriteValue(ICorDebugValue *pValue, const std::string &value, ICorDebugThread *pThread, Evaluator &evaluator);
index 9efce4a86a010c947396448ebf5975146b7849da..0ce8567e3dd9d5f4c651a0a74e78b8f90f87ef03 100644 (file)
@@ -6,9 +6,13 @@
 
 #include <unordered_set>
 #include <vector>
+#include <cstring>
+
+#include <sstream>
 
 #include "typeprinter.h"
 #include "valueprint.h"
+#include "valuewrite.h"
 #include "frames.h"
 
 
@@ -427,3 +431,150 @@ HRESULT Variables::Evaluate(
 
     return S_OK;
 }
+
+HRESULT ManagedDebugger::SetVariable(const std::string &name, const std::string &value, uint32_t ref)
+{
+    return m_variables.SetVariable(m_pProcess, name, value, ref);
+}
+
+HRESULT Variables::SetVariable(
+    ICorDebugProcess *pProcess,
+    const std::string &name,
+    const std::string &value,
+    uint32_t ref)
+{
+    if (pProcess == nullptr)
+        return E_FAIL;
+
+    auto it = m_variables.find(ref);
+    if (it == m_variables.end())
+        return E_FAIL;
+
+    VariableReference &varRef = it->second;
+    HRESULT Status;
+
+    StackFrame stackFrame(varRef.frameId);
+    ToRelease<ICorDebugThread> pThread;
+    IfFailRet(pProcess->GetThread(stackFrame.GetThreadId(), &pThread));
+    ToRelease<ICorDebugFrame> pFrame;
+    IfFailRet(GetFrameAt(pThread, stackFrame.GetLevel(), &pFrame));
+
+    if (varRef.IsScope())
+    {
+        IfFailRet(SetStackVariable(varRef.frameId, pThread, pFrame, name, value));
+    }
+    else
+    {
+        IfFailRet(SetChild(varRef, pThread, pFrame, name, value));
+    }
+
+    return S_OK;
+}
+
+HRESULT Variables::SetStackVariable(
+    uint64_t frameId,
+    ICorDebugThread *pThread,
+    ICorDebugFrame *pFrame,
+    const std::string &name,
+    const std::string &value)
+{
+    HRESULT Status;
+
+    // TODO Exception?
+
+    IfFailRet(m_evaluator.WalkStackVars(pFrame, [&](
+        ICorDebugILFrame *pILFrame,
+        ICorDebugValue *pValue,
+        const std::string &varName) -> HRESULT
+    {
+        if (varName == name)
+            IfFailRet(WriteValue(pValue, value, pThread, m_evaluator));
+
+        return S_OK;
+    }));
+
+    return S_OK;
+}
+
+HRESULT Variables::SetChild(
+    VariableReference &ref,
+    ICorDebugThread *pThread,
+    ICorDebugFrame *pFrame,
+    const std::string &name,
+    const std::string &value)
+{
+    if (ref.IsScope())
+        return E_INVALIDARG;
+
+    if (!ref.value)
+        return S_OK;
+
+    HRESULT Status;
+
+    ToRelease<ICorDebugILFrame> pILFrame;
+    if (pFrame)
+        IfFailRet(pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame));
+
+    IfFailRet(m_evaluator.WalkMembers(ref.value, pThread, pILFrame, [&](
+        mdMethodDef mdGetter,
+        ICorDebugModule *pModule,
+        ICorDebugType *pType,
+        ICorDebugValue *pValue,
+        bool is_static,
+        const std::string &varName) -> HRESULT
+    {
+        if (varName == name)
+            IfFailRet(WriteValue(pValue, value, pThread, m_evaluator));
+
+        return S_OK;
+    }));
+
+    return S_OK;
+}
+
+HRESULT ManagedDebugger::SetVariableByExpression(
+    uint64_t frameId,
+    const std::string &expression,
+    const std::string &value)
+{
+    HRESULT Status;
+    ToRelease<ICorDebugValue> pResultValue;
+
+    IfFailRet(m_variables.GetValueByExpression(m_pProcess, frameId, expression, &pResultValue));
+    return m_variables.SetVariable(m_pProcess, pResultValue, value, frameId);
+}
+
+HRESULT Variables::GetValueByExpression(ICorDebugProcess *pProcess, uint64_t frameId, const std::string &expression,
+                                        ICorDebugValue **ppResult)
+{
+    if (pProcess == nullptr)
+        return E_FAIL;
+
+    HRESULT Status;
+
+    StackFrame stackFrame(frameId);
+    ToRelease<ICorDebugThread> pThread;
+    IfFailRet(pProcess->GetThread(stackFrame.GetThreadId(), &pThread));
+    ToRelease<ICorDebugFrame> pFrame;
+    IfFailRet(GetFrameAt(pThread, stackFrame.GetLevel(), &pFrame));
+
+    return m_evaluator.EvalExpr(pThread, pFrame, expression, ppResult);
+}
+
+HRESULT Variables::SetVariable(
+    ICorDebugProcess *pProcess,
+    ICorDebugValue *pVariable,
+    const std::string &value,
+    uint64_t frameId)
+{
+    HRESULT Status;
+
+    if (pProcess == nullptr)
+        return E_FAIL;
+
+    StackFrame stackFrame(frameId);
+    ToRelease<ICorDebugThread> pThread;
+    IfFailRet(pProcess->GetThread(stackFrame.GetThreadId(), &pThread));
+
+    return WriteValue(pVariable, value, pThread, m_evaluator);
+}
index b15835b8a3a7d1a3eb67f996c61906e421a6ccaa..b437059da8a8fcfc7c72c87bcdcab6c5d4c644fd 100644 (file)
@@ -409,6 +409,18 @@ HRESULT VSCodeProtocol::HandleCommand(const std::string &command, const json &ar
             return E_INVALIDARG;
 
         return m_debugger->Attach(processId);
+    } },
+    { "setVariable", [this](const json &arguments, json &body) {
+        HRESULT Status;
+
+        std::string name = arguments.at("name");
+        std::string value = arguments.at("value");
+        int ref = arguments.at("variablesReference");
+
+        IfFailRet(m_debugger->SetVariable(name, value, ref));
+        body["value"] = value;
+
+        return S_OK;
     } }
     };
 
diff --git a/tests/SetValuesTest/SetValuesTest.cs b/tests/SetValuesTest/SetValuesTest.cs
new file mode 100644 (file)
index 0000000..022c629
--- /dev/null
@@ -0,0 +1,228 @@
+/*\r
+Send("1-file-exec-and-symbols dotnet");\r
+Send("2-exec-arguments " + TestBin);\r
+Send("3-exec-run");\r
+\r
+var r = Expect("*stopped");\r
+Assert.Equal("entry-point-hit", r.FindString("reason"));\r
+\r
+Send(String.Format("4-break-insert -f {0}:{1}", TestSource, Lines["BREAK1"]));\r
+Expect("4^done");\r
+\r
+Send(String.Format("5-break-insert -f {0}:{1}", TestSource, Lines["BREAK2"]));\r
+Expect("5^done");\r
+\r
+Send("6-exec-continue");\r
+*/\r
+\r
+using System;\r
+\r
+namespace SetValuesTest\r
+{\r
+    public struct TestStruct1\r
+    {\r
+        public int val1;\r
+        public byte val2;\r
+\r
+        public TestStruct1(int v1, byte v2)\r
+        {\r
+            val1 = v1;\r
+            val2 = v2;\r
+        }\r
+    }\r
+\r
+    public struct TestStruct2\r
+    {\r
+        public int val1;\r
+        public TestStruct1 struct2;\r
+\r
+        public TestStruct2(int v1, int v2, byte v3)\r
+        {\r
+            val1 = v1;\r
+            struct2.val1 = v2;\r
+            struct2.val2 = v3;\r
+        }\r
+    }\r
+\r
+    class Program\r
+    {\r
+        static void Main(string[] args)\r
+        {               // //@START@\r
+            TestStruct2 ts = new TestStruct2(1, 5, 10); \r
+\r
+            bool testBool = false;\r
+            char testChar = 'ㅎ';\r
+            byte testByte = (byte)10;\r
+            sbyte testSByte = (sbyte)-100;\r
+            short testShort = (short)-500;\r
+            ushort testUShort = (ushort)500;\r
+            int testInt = -999999;\r
+            uint testUInt = 999999;\r
+            long testLong = -999999999;\r
+            ulong testULong = 9999999999;\r
+\r
+            decimal b = 0000001.000000000000000000000000006M;\r
+            int[] arrs = decimal.GetBits(b);\r
+            string testString = "someNewString that I'll test with";\r
+\r
+            int dummy1 = 1;            // //@BREAK1@\r
+/*\r
+r = Expect("*stopped");\r
+Assert.Equal("breakpoint-hit", r.FindString("reason"));\r
+Assert.Equal(Lines["BREAK1"], r.Find("frame").FindInt("line"));\r
+\r
+Send(String.Format("8-var-create - * \"{0}\"", "ts.struct2.val1"));\r
+r = Expect("8^done");\r
+string val1 = r.FindString("name");\r
+Send(String.Format("9-var-assign {0} \"666\"", val1));\r
+r = Expect("9^done");\r
+\r
+Send(String.Format("10-var-create - * \"{0}\"", "testBool"));\r
+r = Expect("10^done");\r
+string testBool = r.FindString("name");\r
+Send(String.Format("11-var-assign {0} \"true\"", testBool));\r
+r = Expect("11^done");\r
+\r
+Send(String.Format("12-var-create - * \"{0}\"", "testChar"));\r
+r = Expect("12^done");\r
+string testChar = r.FindString("name");\r
+Send(String.Format("13-var-assign {0} \"a\"", testChar));\r
+r = Expect("13^done");\r
+\r
+Send(String.Format("14-var-create - * \"{0}\"", "testByte"));\r
+r = Expect("14^done");\r
+string testByte = r.FindString("name");\r
+Send(String.Format("15-var-assign {0} \"200\"", testByte));\r
+r = Expect("15^done");\r
+\r
+Send(String.Format("16-var-create - * \"{0}\"", "testSByte"));\r
+r = Expect("16^done");\r
+string testSByte = r.FindString("name");\r
+Send(String.Format("17-var-assign {0} \"-1\"", testSByte));\r
+r = Expect("17^done");\r
+\r
+Send(String.Format("18-var-create - * \"{0}\"", "testShort"));\r
+r = Expect("18^done");\r
+string testShort = r.FindString("name");\r
+Send(String.Format("19-var-assign {0} \"-666\"", testShort));\r
+r = Expect("19^done");\r
+\r
+Send(String.Format("20-var-create - * \"{0}\"", "testUShort"));\r
+r = Expect("20^done");\r
+string testUShort = r.FindString("name");\r
+Send(String.Format("21-var-assign {0} \"666\"", testUShort));\r
+r = Expect("21^done");\r
+\r
+Send(String.Format("22-var-create - * \"{0}\"", "testInt"));\r
+r = Expect("22^done");\r
+string testInt = r.FindString("name");\r
+Send(String.Format("23-var-assign {0} \"666666\"", testInt));\r
+r = Expect("23^done");\r
+\r
+Send(String.Format("24-var-create - * \"{0}\"", "testUInt"));\r
+r = Expect("24^done");\r
+string testUInt = r.FindString("name");\r
+Send(String.Format("25-var-assign {0} \"666666\"", testUInt));\r
+r = Expect("25^done");\r
+\r
+Send(String.Format("26-var-create - * \"{0}\"", "testLong"));\r
+r = Expect("26^done");\r
+string testLong = r.FindString("name");\r
+Send(String.Format("27-var-assign {0} \"-666666666\"", testLong));\r
+r = Expect("27^done");\r
+\r
+Send(String.Format("28-var-create - * \"{0}\"", "testULong"));\r
+r = Expect("28^done");\r
+string testULong = r.FindString("name");\r
+Send(String.Format("29-var-assign {0} \"666666666\"", testULong));\r
+r = Expect("29^done");\r
+\r
+Send(String.Format("30-var-create - * \"{0}\"", "b"));\r
+r = Expect("30^done");\r
+string b = r.FindString("name");\r
+Send(String.Format("31-var-assign {0} \"-1.000000000000000000000017\"", b));\r
+r = Expect("31^done");\r
+\r
+Send(String.Format("32-var-create - * \"{0}\"", "testString"));\r
+r = Expect("32^done");\r
+string testString = r.FindString("name");\r
+Send(String.Format("33-var-assign {0} \"edited string\"", testString));\r
+r = Expect("33^done");\r
+Send("34-exec-continue");\r
+*/\r
+                 int dummy2 = 2;         // //@BREAK2@\r
+/*\r
+r = Expect("*stopped");\r
+Assert.Equal("breakpoint-hit", r.FindString("reason"));\r
+Assert.Equal(Lines["BREAK2"], r.Find("frame").FindInt("line"));\r
+\r
+Send(String.Format("35-var-create - * \"{0}\"", "ts.struct2"));\r
+r = Expect("35^done");\r
+string struct2 = r.FindString("name");\r
+Send(String.Format("36-var-list-children --simple-values \"{0}\"", struct2));\r
+r = Expect("36^done");\r
+string val = r.Find("children").Find("child").FindString("value");\r
+Assert.Equal("666", val);\r
+\r
+\r
+\r
+\r
+\r
+\r
+Send(String.Format("37-var-create - * \"{0}\"", "testBool"));\r
+r = Expect("37^done");\r
+Assert.Equal(r.FindString("value"), "true");\r
+\r
+Send(String.Format("38-var-create - * \"{0}\"", "testChar"));\r
+r = Expect("38^done");\r
+Assert.Equal(r.FindString("value"), "97 \'a\'");\r
+\r
+Send(String.Format("39-var-create - * \"{0}\"", "testByte"));\r
+r = Expect("39^done");\r
+Assert.Equal(r.FindString("value"), "200");\r
+\r
+Send(String.Format("40-var-create - * \"{0}\"", "testSByte"));\r
+r = Expect("40^done");\r
+Assert.Equal(r.FindString("value"), "-1");\r
+\r
+Send(String.Format("41-var-create - * \"{0}\"", "testShort"));\r
+r = Expect("41^done");\r
+Assert.Equal(r.FindString("value"), "-666");\r
+\r
+Send(String.Format("42-var-create - * \"{0}\"", "testUShort"));\r
+r = Expect("42^done");\r
+Assert.Equal(r.FindString("value"), "666");\r
+\r
+Send(String.Format("43-var-create - * \"{0}\"", "testInt"));\r
+r = Expect("43^done");\r
+Assert.Equal(r.FindString("value"), "666666");\r
+\r
+Send(String.Format("44-var-create - * \"{0}\"", "testUInt"));\r
+r = Expect("44^done");\r
+Assert.Equal(r.FindString("value"), "666666");\r
+\r
+Send(String.Format("45-var-create - * \"{0}\"", "testLong"));\r
+r = Expect("45^done");\r
+Assert.Equal(r.FindString("value"), "-666666666");\r
+\r
+Send(String.Format("46-var-create - * \"{0}\"", "testULong"));\r
+r = Expect("46^done");\r
+Assert.Equal(r.FindString("value"), "666666666");\r
+\r
+Send(String.Format("47-var-create - * \"{0}\"", "b"));\r
+r = Expect("47^done");\r
+Assert.Equal(r.FindString("value"), "-1.000000000000000000000017");\r
+\r
+Send(String.Format("48-var-create - * \"{0}\"", "testString"));\r
+r = Expect("48^done");\r
+Assert.Equal(r.FindString("value"), "\"edited string\"");\r
+\r
+*/\r
+        }\r
+    }\r
+}\r
+/*\r
+Send("49-exec-continue");\r
+r = Expect("*stopped");\r
+Assert.Equal("exited", r.FindString("reason"));\r
+*/\r
diff --git a/tests/SetValuesTest/SetValuesTest.csproj b/tests/SetValuesTest/SetValuesTest.csproj
new file mode 100644 (file)
index 0000000..8e047a0
--- /dev/null
@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">\r
+\r
+  <PropertyGroup>\r
+    <OutputType>Exe</OutputType>\r
+    <TargetFramework>netcoreapp2.0</TargetFramework>\r
+  </PropertyGroup>\r
+\r
+</Project>\r
index 47f820b0c0ecb4e225a7ab819627272aa65f6c00..b690d30d1cc6acf3232f3a63766b0ed1585d302b 100644 (file)
@@ -36,6 +36,9 @@ namespace Runner
         [Fact]
         public void BreakpointAddRemoveTest() => ExecuteTest();
 
+        [Fact]
+        public void SetValuesTest() => ExecuteTest();
+
         private const int DefaultTimeoutSec = 20;
         private int expectTimeoutSec;
 
index c429a102b66902a8cb5efbb5404fc5132a3ae41d..9a23f3e3f3c948d6673e78b845aba96f99cf3c61 100644 (file)
@@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExceptionTest", "ExceptionT
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BreakpointAddRemoveTest", "BreakpointAddRemoveTest\BreakpointAddRemoveTest.csproj", "{48403D8A-08D8-48FC-BF1B-F0239154AD85}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SetValuesTest", "SetValuesTest\SetValuesTest.csproj", "{9E6CD7E6-0B84-4088-9097-1839A14B49DB}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
@@ -86,5 +88,17 @@ Global
                {48403D8A-08D8-48FC-BF1B-F0239154AD85}.Release|x64.Build.0 = Release|x64
                {48403D8A-08D8-48FC-BF1B-F0239154AD85}.Release|x86.ActiveCfg = Release|x86
                {48403D8A-08D8-48FC-BF1B-F0239154AD85}.Release|x86.Build.0 = Release|x86
+               {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Debug|x64.ActiveCfg = Debug|x64
+               {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Debug|x64.Build.0 = Debug|x64
+               {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Debug|x86.ActiveCfg = Debug|x86
+               {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Debug|x86.Build.0 = Debug|x86
+               {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|Any CPU.Build.0 = Release|Any CPU
+               {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|x64.ActiveCfg = Release|x64
+               {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|x64.Build.0 = Release|x64
+               {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|x86.ActiveCfg = Release|x86
+               {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|x86.Build.0 = Release|x86
        EndGlobalSection
 EndGlobal