Add exception bp for vscode and single thread (#111)
authorAnton Zhukov/AI Ecosystem Lab /SRR/Engineer/삼성전자 <a.zhukov@samsung.com>
Fri, 31 May 2019 15:04:47 +0000 (11:04 -0400)
committerAlexander Soldatov/AI Ecosystem Lab /SRR/Staff Engineer/삼성전자 <soldatov.a@samsung.com>
Fri, 31 May 2019 15:04:47 +0000 (18:04 +0300)
* Add exception bp for vscode and single thread
* Add innerException for exception-bp
* Add nested InnerException for exceptionInfo response by vscode
* Fix BreakMode response
* Minor fixes from previous pull-request comments
* Fix ASan errors
* Move JSON header to third_party directory
* Add Catch2 v2.6.1 unit testing framework
* Add license files
* Empty commit for check building after merge with updstream master.
* Add exception bp for MI and single thread
* Fix User-Unhandled Exceptions
* Fix Exception BP MI commands
* Minor fix MI BP exception for single thread
* Add MI test for exception BP
* supported only single thread debug mode

14 files changed:
src/debug/netcoredbg/breakpoints.cpp
src/debug/netcoredbg/debugger.h
src/debug/netcoredbg/manageddebugger.cpp
src/debug/netcoredbg/manageddebugger.h
src/debug/netcoredbg/miprotocol.cpp
src/debug/netcoredbg/miprotocol.h
src/debug/netcoredbg/protocol.h
src/debug/netcoredbg/variables.cpp
src/debug/netcoredbg/vscodeprotocol.cpp
src/debug/netcoredbg/vscodeprotocol.h
tests/ExceptionBreakpointTest/ExceptionBreakpointTest.cs [new file with mode: 0644]
tests/ExceptionBreakpointTest/ExceptionBreakpointTest.csproj [new file with mode: 0644]
tests/runner/Runner.cs
tests/tests.sln

index b329498099676e5a2c40edaf9d2c6759135c36d6..9f96b56e212a2242488ee31f20ff306da4849da1 100644 (file)
@@ -9,9 +9,9 @@
 #include <fstream>
 #include "typeprinter.h"
 #include "logger.h"
+#include "cputil.h"
 
-
-
+using std::string;
 
 static HRESULT IsSameFunctionBreakpoint(
     ICorDebugFunctionBreakpoint *pBreakpoint1,
@@ -258,19 +258,6 @@ bool Breakpoints::HitEntry(ICorDebugThread *pThread, ICorDebugBreakpoint *pBreak
     return true;
 }
 
-void ManagedDebugger::InsertExceptionBreakpoint(const std::string &name, Breakpoint &breakpoint)
-{
-    LogFuncEntry();
-
-    m_breakpoints.InsertExceptionBreakpoint(name, breakpoint);
-}
-
-void Breakpoints::InsertExceptionBreakpoint(const std::string &name, Breakpoint &breakpoint)
-{
-    std::lock_guard<std::mutex> lock(m_breakpointsMutex);
-    breakpoint.id = m_nextBreakpointId++;
-}
-
 void Breakpoints::DeleteAllBreakpoints()
 {
     std::lock_guard<std::mutex> lock(m_breakpointsMutex);
@@ -593,8 +580,6 @@ HRESULT Breakpoints::ResolveFunctionBreakpoint(ManagedFunctionBreakpoint &fbp)
     return S_OK;
 }
 
-#include "cputil.h"
-
 HRESULT Breakpoints::ResolveFunctionBreakpointInModule(ICorDebugModule *pModule, ManagedFunctionBreakpoint &fbp)
 {
     HRESULT Status;
@@ -706,5 +691,120 @@ HRESULT Breakpoints::SetFunctionBreakpoints(
     }
 
 
+    return S_OK;
+}
+
+HRESULT Breakpoints::InsertExceptionBreakpoint(const ExceptionBreakMode &mode,
+    const string &name, uint32_t &rid)
+{
+    std::lock_guard<std::mutex> lock(m_breakpointsMutex);
+    HRESULT Status;
+    IfFailRet(m_exceptionBreakpoints.Insert(m_nextBreakpointId, mode, name));
+    rid = m_nextBreakpointId;
+    ++m_nextBreakpointId;
+    return S_OK;
+}
+
+HRESULT Breakpoints::DeleteExceptionBreakpoint(const uint32_t id)
+{
+    std::lock_guard<std::mutex> lock(m_breakpointsMutex);
+    return m_exceptionBreakpoints.Delete(id);
+}
+
+HRESULT Breakpoints::GetExceptionBreakMode(ExceptionBreakMode &mode,
+    const string &name)
+{
+    std::lock_guard<std::mutex> lock(m_breakpointsMutex);
+    return m_exceptionBreakpoints.GetExceptionBreakMode(mode, name);
+}
+
+bool Breakpoints::MatchExceptionBreakpoint(const string &name,
+    const ExceptionBreakCategory category)
+{
+    std::lock_guard<std::mutex> lock(m_breakpointsMutex);
+    return m_exceptionBreakpoints.Match(name, category);
+}
+
+HRESULT ExceptionBreakpointStorage::Insert(uint32_t id,
+    const ExceptionBreakMode &mode, const string &name)
+{
+    HRESULT Status = S_OK;
+    // vsdbg each time creates a new exception breakpoint id.
+    // But, for "*" name, the last `id' silently are deleted by vsdbg.
+    if (name.compare("*") == 0) {
+        if (bp.current_asterix_id != 0) {
+            // Silent remove for global filter
+            Status = Delete(bp.current_asterix_id);
+        }
+        bp.current_asterix_id = id;
+    }
+
+    bp.exceptionBreakpoints.insert(std::make_pair(name, mode));
+    bp.table[id] = name;
+
+    return Status;
+}
+
+HRESULT ExceptionBreakpointStorage::Delete(uint32_t id) {
+    const auto it = bp.table.find(id);
+    if (it == bp.table.end()) {
+        return E_FAIL;
+    }
+    const string name = it->second;
+    if (name.compare("*") == 0) {
+        bp.current_asterix_id = 0;
+    }
+    bp.exceptionBreakpoints.erase(name);
+    bp.table.erase(id);
+
+    return S_OK;
+}
+
+bool ExceptionBreakpointStorage::Match(const string &exceptionName,
+    const ExceptionBreakCategory category) const
+{
+    // Try to match exactly by name after check global name "*"
+    // ExceptionBreakMode can be specialized by explicit filter.
+    ExceptionBreakMode mode;
+    GetExceptionBreakMode(mode, "*");
+    GetExceptionBreakMode(mode, exceptionName);
+    if (category == ExceptionBreakCategory::ANY ||
+        category == mode.category)
+    {
+        if (mode.BothUnhandledAndUserUnhandled())
+        {
+            const string SystemPrefix = "System.";
+            if (exceptionName.compare(0, SystemPrefix.size(), SystemPrefix) == 0)
+            {
+                // Expected user-applications exceptions from throw(), but get
+                // explicit/implicit exception from `System.' clases.
+                return false;
+            }
+        }
+
+        return mode.Any();
+    }
+
+    return false;
+}
+
+HRESULT ExceptionBreakpointStorage::GetExceptionBreakMode(ExceptionBreakMode &out,
+    const string &name) const
+{
+    auto p = bp.exceptionBreakpoints.equal_range(name);
+    if (p.first == bp.exceptionBreakpoints.end()) {
+        return E_FAIL;
+    }
+
+    out.category = p.first->second.category;
+    out.flags |= p.first->second.flags;
+    ++p.first;
+    while (p.first != p.second) {
+        if (out.category == ExceptionBreakCategory::ANY ||
+            out.category == p.first->second.category)
+            out.flags |= p.first->second.flags;
+        ++p.first;
+    }
+
     return S_OK;
 }
index b37afd21af8fb8be7aae8892e25ea90963717643..eefc255da40600ea5bdefb819259db2cf12d7bc1 100644 (file)
@@ -51,7 +51,6 @@ public:
     virtual HRESULT GetThreads(std::vector<Thread> &threads) = 0;
     virtual HRESULT SetBreakpoints(std::string filename, const std::vector<SourceBreakpoint> &srcBreakpoints, std::vector<Breakpoint> &breakpoints) = 0;
     virtual HRESULT SetFunctionBreakpoints(const std::vector<FunctionBreakpoint> &funcBreakpoints, std::vector<Breakpoint> &breakpoints) = 0;
-    virtual void InsertExceptionBreakpoint(const std::string &name, Breakpoint &breakpoint) = 0;
     virtual HRESULT GetStackTrace(int threadId, int startFrame, int levels, std::vector<StackFrame> &stackFrames, int &totalFrames) = 0;
     virtual HRESULT StepCommand(int threadId, StepType stepType) = 0;
     virtual HRESULT GetScopes(uint64_t frameId, std::vector<Scope> &scopes) = 0;
@@ -60,6 +59,9 @@ public:
     virtual HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable, std::string &output) = 0;
     virtual HRESULT SetVariable(const std::string &name, const std::string &value, uint32_t ref, std::string &output) = 0;
     virtual HRESULT SetVariableByExpression(uint64_t frameId, const std::string &name, const std::string &value, std::string &output) = 0;
+    virtual HRESULT GetExceptionInfoResponse(int threadId, ExceptionInfoResponse &exceptionResponse) = 0;
+    virtual HRESULT DeleteExceptionBreakpoint(const uint32_t id) = 0;
+    virtual HRESULT InsertExceptionBreakpoint(const ExceptionBreakMode &mode, const std::string& names, uint32_t &id) = 0;
 };
 
 class Protocol
index 1136301b8f864503b564735cd44474d678c0e67d..3d7071c4e2063e7bded57bfebdf3fbd41e494a99 100644 (file)
@@ -17,6 +17,8 @@
 #include "frames.h"
 #include "logger.h"
 
+using std::string;
+
 // From dbgshim.h
 struct dbgshim_t
 {
@@ -444,11 +446,11 @@ public:
         {
             LogFuncEntry();
 
-            std::string excType;
-            std::string excModule;
+            string excType;
+            string excModule;
             GetExceptionInfo(pThread, excType, excModule);
 
-            if (unhandled)
+            if (unhandled || m_debugger.MatchExceptionBreakpoint(excType, ExceptionBreakCategory::CLR))
             {
                 DWORD threadId = 0;
                 pThread->GetID(&threadId);
@@ -460,11 +462,15 @@ public:
 
                 m_debugger.SetLastStoppedThread(pThread);
 
-                std::string details = "An unhandled exception of type '" + excType + "' occurred in " + excModule;
+                string details;
+                if (unhandled)
+                    details = "An unhandled exception of type '" + excType + "' occurred in " + excModule;
+                else
+                    details = "Exception thrown: '" + excType + "' in " + excModule;
 
                 ToRelease<ICorDebugValue> pExceptionValue;
 
-                auto emitFunc = [=](const std::string &message) {
+                auto emitFunc = [=](const string &message) {
                     StoppedEvent event(StopException, threadId);
                     event.text = excType;
                     event.description = message.empty() ? details : message;
@@ -477,10 +483,11 @@ public:
                 {
                     emitFunc(details);
                 }
+
             }
             else
             {
-                std::string text = "Exception thrown: '" + excType + "' in " + excModule + "\n";
+                string text = "Exception thrown: '" + excType + "' in " + excModule + "\n";
                 OutputEvent event(OutputConsole, text);
                 event.source = "target-exception";
                 m_debugger.m_protocol->EmitOutputEvent(event);
@@ -802,6 +809,11 @@ public:
             /* [in] */ ICorDebugThread *pThread,
             /* [in] */ ICorDebugMDA *pMDA)
         {
+            // TODO: MDA notification should be supported with exception breakpoint feature
+            // https://docs.microsoft.com/ru-ru/dotnet/framework/unmanaged-api/debugging/icordebugmanagedcallback2-mdanotification-method
+            // https://docs.microsoft.com/ru-ru/dotnet/framework/debug-trace-profile/diagnosing-errors-with-managed-debugging-assistants#enable-and-disable-mdas
+            //
+
             LogFuncEntry();
             return E_NOTIMPL;
         }
@@ -1016,7 +1028,7 @@ HRESULT ManagedDebugger::Pause()
         return Status;
     if (!running)
         return S_OK;
-    
+
     Status = m_pProcess->Stop(0);
     if (Status != S_OK)
         return Status;
@@ -1394,3 +1406,38 @@ HRESULT ManagedDebugger::AttachToProcess(DWORD pid)
     m_unregisterToken = nullptr;
     return Startup(pCordb, pid);
 }
+
+// VSCode
+HRESULT ManagedDebugger::GetExceptionInfoResponse(int threadId,
+    ExceptionInfoResponse &exceptionInfoResponse)
+{
+    LogFuncEntry();
+
+    HRESULT Status;
+    ExceptionBreakMode mode;
+    IfFailRet(m_breakpoints.GetExceptionBreakMode(mode, "*"));
+    return m_variables.GetExceptionInfoResponseData(m_pProcess, threadId, mode, exceptionInfoResponse);
+}
+
+// MI
+HRESULT ManagedDebugger::InsertExceptionBreakpoint(const ExceptionBreakMode &mode,
+    const string &name, uint32_t &id)
+{
+    LogFuncEntry();
+    return m_breakpoints.InsertExceptionBreakpoint(mode, name, id);
+}
+
+// MI
+HRESULT ManagedDebugger::DeleteExceptionBreakpoint(const uint32_t id)
+{
+    LogFuncEntry();
+    return m_breakpoints.DeleteExceptionBreakpoint(id);
+}
+
+// MI and VSCode
+bool ManagedDebugger::MatchExceptionBreakpoint(const string &exceptionName,
+    const ExceptionBreakCategory category)
+{
+    LogFuncEntry();
+    return m_breakpoints.MatchExceptionBreakpoint(exceptionName, category);
+}
index 911d71cbf6530e4318b0898acf5ef6703d90f794..7528a6746d5503339d43b5ce1c5e074d25db034f 100644 (file)
@@ -244,6 +244,7 @@ class Breakpoints
     std::mutex m_breakpointsMutex;
     std::unordered_map<std::string, std::unordered_map<int, ManagedBreakpoint> > m_breakpoints;
     std::unordered_map<std::string, ManagedFunctionBreakpoint > m_funcBreakpoints;
+    ExceptionBreakpointStorage m_exceptionBreakpoints;
 
     HRESULT ResolveBreakpointInModule(ICorDebugModule *pModule, ManagedBreakpoint &bp);
     HRESULT ResolveBreakpoint(ManagedBreakpoint &bp);
@@ -290,8 +291,6 @@ public:
 
     void TryResolveBreakpointsForModule(ICorDebugModule *pModule, std::vector<BreakpointEvent> &events);
 
-    void InsertExceptionBreakpoint(const std::string &name, Breakpoint &breakpoint);
-
     HRESULT SetBreakpoints(
         ICorDebugProcess *pProcess,
         std::string filename,
@@ -304,6 +303,11 @@ public:
         std::vector<Breakpoint> &breakpoints);
 
     void SetStopAtEntry(bool stopAtEntry);
+
+    HRESULT InsertExceptionBreakpoint(const ExceptionBreakMode &mode, const std::string &name, uint32_t &output);
+    HRESULT DeleteExceptionBreakpoint(const uint32_t id);
+    HRESULT GetExceptionBreakMode(ExceptionBreakMode &mode, const std::string &name);
+    bool MatchExceptionBreakpoint(const std::string &exceptionName, const ExceptionBreakCategory category);
 };
 
 class Variables
@@ -346,12 +350,42 @@ class Variables
     };
 
     Evaluator &m_evaluator;
+    struct Member;
 
     std::unordered_map<uint32_t, VariableReference> m_variables;
     uint32_t m_nextVariableReference;
 
     void AddVariableReference(Variable &variable, uint64_t frameId, ICorDebugValue *value, ValueKind valueKind);
 
+    HRESULT GetExceptionInfoResponseDetailsMembers(
+        ICorDebugProcess *pProcess,
+        ICorDebugThread *pThread,
+        uint64_t frameId,
+        Variable &var,
+        ExceptionDetails &details,
+        bool &isFoundInnerException,
+        std::vector<Member> &members);
+
+    HRESULT GetICorDebugValueMembers(
+        ICorDebugProcess *pProcess,
+        ICorDebugThread *pThread,
+        uint64_t frameId,
+        ICorDebugValue *value,
+        bool fetchOnlyStatic,
+        std::vector<Member> &members);
+
+    HRESULT GetVariableMembers(
+        ICorDebugProcess *pProcess,
+        ICorDebugThread *pThread,
+        uint64_t frameId,
+        Variable &var,
+        std::vector<Member> &members);
+
+    HRESULT GetExceptionVariable(
+        uint64_t frameId,
+        ICorDebugThread *pThread,
+        Variable &variable);
+
     HRESULT GetStackVariables(
         uint64_t frameId,
         ICorDebugThread *pThread,
@@ -368,8 +402,6 @@ class Variables
         int count,
         std::vector<Variable> &variables);
 
-    struct Member;
-
     static void FixupInheritedFieldNames(std::vector<Member> &members);
 
     HRESULT FetchFieldsAndProperties(
@@ -405,14 +437,18 @@ class Variables
 
     static BOOL VarGetChild(void *opaque, uint32_t varRef, const char* name, int *typeId, void **data);
     bool GetChildDataByName(uint32_t varRef, const std::string &name, int *typeId, void **data);
+    void FillValueAndType(Member &member, Variable &var, bool escape = true);
 
 public:
 
-    Variables(Evaluator &evaluator) : m_evaluator(evaluator), m_nextVariableReference(1) {}
+    Variables(Evaluator &evaluator) :
+        m_evaluator(evaluator),
+        m_nextVariableReference(1)
+    {}
 
     int GetNamedVariables(uint32_t variablesReference);
 
-    HRESULT Variables::GetVariables(
+    HRESULT GetVariables(
         ICorDebugProcess *pProcess,
         uint32_t variablesReference,
         VariablesFilter filter,
@@ -445,6 +481,8 @@ public:
         ICorDebugValue **ppResult);
 
     void Clear() { m_variables.clear(); m_nextVariableReference = 1; }
+
+    HRESULT GetExceptionInfoResponseData(ICorDebugProcess *pProcess, int threadId, const ExceptionBreakMode &mode, ExceptionInfoResponse &exceptionInfoResponse);
 };
 
 class ManagedCallback;
@@ -546,7 +584,6 @@ public:
     HRESULT GetThreads(std::vector<Thread> &threads) override;
     HRESULT SetBreakpoints(std::string filename, const std::vector<SourceBreakpoint> &srcBreakpoints, std::vector<Breakpoint> &breakpoints) override;
     HRESULT SetFunctionBreakpoints(const std::vector<FunctionBreakpoint> &funcBreakpoints, std::vector<Breakpoint> &breakpoints) override;
-    void InsertExceptionBreakpoint(const std::string &name, Breakpoint &breakpoint) override;
     HRESULT GetStackTrace(int threadId, int startFrame, int levels, std::vector<StackFrame> &stackFrames, int &totalFrames) override;
     HRESULT StepCommand(int threadId, StepType stepType) override;
     HRESULT GetScopes(uint64_t frameId, std::vector<Scope> &scopes) override;
@@ -555,4 +592,9 @@ public:
     HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable, std::string &output) override;
     HRESULT SetVariable(const std::string &name, const std::string &value, uint32_t ref, std::string &output) override;
     HRESULT SetVariableByExpression(uint64_t frameId, const std::string &expression, const std::string &value, std::string &output) override;
+    HRESULT GetExceptionInfoResponse(int threadId, ExceptionInfoResponse &exceptionResponse) override;
+    HRESULT InsertExceptionBreakpoint(const ExceptionBreakMode &mode, const std::string &name, uint32_t &output) override;
+    HRESULT DeleteExceptionBreakpoint(const uint32_t id) override;
+private:
+    bool MatchExceptionBreakpoint(const std::string &exceptionName, const ExceptionBreakCategory category);
 };
index e7108575a4df4dc4841c98e0f509ffcb0edb844f..2690eba29b574b19211d35edbb2d18e453c3f0f1 100644 (file)
 
 #include "logger.h"
 
-
 using namespace std::placeholders;
+using std::unordered_set;
+using std::string;
+using std::vector;
 
 typedef std::function<HRESULT(
     const std::vector<std::string> &args,
@@ -669,6 +671,51 @@ void MIProtocol::DeleteFunctionBreakpoints(const std::unordered_set<uint32_t> &i
     m_debugger->SetFunctionBreakpoints(remainingFuncBreakpoints, tmpBreakpoints);
 }
 
+HRESULT MIProtocol::InsertExceptionBreakpoints(const ExceptionBreakMode &mode,
+    const vector<string>& names, string &output)
+{
+    if (names.empty())
+        return E_FAIL;
+
+    HRESULT Status;
+    string buf = "";
+    uint32_t id = 0;
+    for (const auto &name : names) {
+        Status = m_debugger->InsertExceptionBreakpoint(mode, name, id);
+        if (S_OK != Status) {
+            return Status;
+        }
+        buf += "{number=\"" + std::to_string(id) + "\"},";
+    }
+    if (!buf.empty())
+        buf.pop_back();
+
+    // This line fixes double comma ',,' in output
+    if (names.size() > 1) {
+        output = "^done,bkpt=[" + buf + "]";
+    }
+    else {
+    // This sensitive for current CI Runner.cs
+        output = "^done,bkpt=" + buf;
+    }
+
+    return S_OK;
+}
+
+HRESULT MIProtocol::DeleteExceptionBreakpoints(const std::unordered_set<uint32_t> &ids,
+    string &output)
+{
+    HRESULT Status;
+    for (const auto &id : ids) {
+        Status = m_debugger->DeleteExceptionBreakpoint(id);
+        if (S_OK != Status) {
+            output = "Cannot delete exception breakpoint by id=:'" + std::to_string(id) + "'";
+            return Status;
+        }
+    }
+    return S_OK;
+}
+
 void MIProtocol::EmitStoppedEvent(StoppedEvent event)
 {
     LogFuncEntry();
@@ -1084,28 +1131,75 @@ HRESULT MIProtocol::HandleCommand(std::string command,
     { "interpreter-exec", [](const std::vector<std::string> &args, std::string &output) -> HRESULT {
         return S_OK;
     }},
-    { "break-exception-insert", [this](const std::vector<std::string> &args, std::string &output) -> HRESULT {
-        if (args.empty())
+    { "break-exception-insert", [this](const vector<string> &args, string &output) -> HRESULT {
+        // That's all info about MI "-break-exception-insert" feature:
+        // https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI.html#GDB_002fMI
+        // https://raw.githubusercontent.com/gregg-miskelly/MIEngine/f5f22f53908644aacffdc3f843fba20b639d07bb/src/MICore/MICommandFactory.cs
+        // https://github.com/OmniSharp/omnisharp-vscode/files/626936/vscodelog.txt
+        if (args.size() < 2) {
+            output = "Command usage: -break-exception-insert [--mda] <unhandled|user-unhandled|throw|throw+user-unhandled> *|<Exception names>";
+            return E_INVALIDARG;
+        }
+
+        size_t i = 0;
+        ExceptionBreakMode filterValue;
+        if (args.at(i) == "--mda") {
+            filterValue.category = ExceptionBreakCategory::MDA;
+            ++i;
+        }
+
+        // Unavailale for changing by user
+        if (args.at(i).compare("unhandled") == 0) {
+            return S_OK;
+        }
+
+        if (args.at(i).compare("user-unhandled") == 0) {
+            filterValue.setUserUnhandled();
+        }
+
+        if (args.at(i).compare("throw") == 0 ) {
+            filterValue.setThrow();
+        }
+
+        if (args.at(i).compare("throw+user-unhandled") == 0) {
+            filterValue.setAll();
+        }
+
+        if (!filterValue.Any()) {
+            output = "Command requires only:'unhandled','user-unhandled','throw','throw+user-unhandled' arguments as an exception stages";
             return E_FAIL;
-        size_t i = 1;
-        if (args.at(0) == "--mda")
-            i = 2;
+        }
 
-        std::ostringstream ss;
-        const char *sep = "";
-        ss << "bkpt=[";
-        for (; i < args.size(); i++)
+        // Exception names example:
+        // And vsdbg have common numbers for all type of breakpoints
+        //-break-exception-insert throw A B C
+        //^done,bkpt=[{number="1"},{number="2"},{number="3"}]
+        //(gdb)
+        //-break-insert Class1.cs:1
+        //^done,bkpt={number="4",type="breakpoint",disp="keep",enabled="y"}
+        //(gdb)
+        const vector<string> names(args.begin() + (i + 1), args.end());
+        return InsertExceptionBreakpoints(filterValue, names, output);
+    }},
+    { "break-exception-delete", [this](const vector<string> &args, string &output) -> HRESULT {
+        if (args.empty()) {
+            output = "Command usage: -break-exception-delete <Exception indexes>";
+            return E_INVALIDARG;
+        }
+        unordered_set<uint32_t> indexes;
+        for (const string &id : args)
         {
-            Breakpoint b;
-            m_debugger->InsertExceptionBreakpoint(args.at(i), b);
-            ss << sep;
-            sep = ",";
-            ss << "{number=\"" << b.id << "\"}";
+            bool isTrue = false;
+            int value = ParseInt(id, isTrue);
+            if (isTrue) {
+                indexes.insert(value);
+            }
+            else {
+                output = "Invalid argument:'"+ id + "'";
+                return E_INVALIDARG;
+            }
         }
-        ss << "]";
-        output = ss.str();
-
-        return S_OK;
+        return DeleteExceptionBreakpoints(indexes, output);
     }},
     { "var-show-attributes", [this](const std::vector<std::string> &args, std::string &output) -> HRESULT {
         HRESULT Status;
index 7c9182a524d8f0bb628387a07fff40d6d7d11b69..c928ae07049f4b199ddccb86bceaaaae0fc94f09 100644 (file)
@@ -79,4 +79,6 @@ private:
     void DeleteBreakpoints(const std::unordered_set<uint32_t> &ids);
     void DeleteFunctionBreakpoints(const std::unordered_set<uint32_t> &ids);
     static HRESULT PrintFrameLocation(const StackFrame &stackFrame, std::string &output);
+    HRESULT InsertExceptionBreakpoints(const ExceptionBreakMode &mode, const std::vector<std::string>& names, std::string &output);
+    HRESULT DeleteExceptionBreakpoints(const std::unordered_set<uint32_t> &ids, std::string &output);
 };
index 3f9f97e9dd52d4db2ef9d37b0cc5220e5af866b2..dea6c96875376affd1cb5548201833062243db17 100644 (file)
@@ -6,6 +6,9 @@
 
 #include <string>
 #include <vector>
+#include <bitset>
+#include <unordered_map>
+
 #include "platform.h"
 
 // From https://github.com/Microsoft/vscode-debugadapter-node/blob/master/protocol/src/debugProtocol.ts
@@ -89,7 +92,7 @@ struct Breakpoint
 enum SymbolStatus
 {
     SymbolsSkipped, // "Skipped loading symbols."
-    SymbolsLoaded,  // "Symbols loaded." 
+    SymbolsLoaded,  // "Symbols loaded."
     SymbolsNotFound
 };
 
@@ -230,6 +233,7 @@ struct Variable
     std::string name;
     std::string value;
     std::string type;
+    std::string module;
     VariablePresentationHint presentationHint;
     std::string evaluateName;
     uint32_t variablesReference;
@@ -271,3 +275,225 @@ struct FunctionBreakpoint
         condition(cond)
     {}
 };
+
+enum struct ExceptionBreakCategory : int
+{
+    CLR = 0,
+    MDA = 1,
+
+    ANY, // CLR or MDA does not matter
+};
+
+class ExceptionBreakMode
+{
+private:
+    enum Flag : int
+    {
+        F_Unhandled = 0, // Always enabled for catch of System Exceptions.
+        F_Throw = 1, // All events raised by 'throw new' operator.
+        F_UserUnhandled, // Break on unhandled exception in user code.
+
+        COUNT // Flag enum element counter
+    };
+
+public:
+    std::bitset<Flag::COUNT> flags;
+
+    ExceptionBreakCategory category;
+
+    ExceptionBreakMode() : category(ExceptionBreakCategory::CLR) {
+        flags.set(Flag::F_Unhandled);
+    }
+
+    bool Unhandled() const {
+        return flags.test(Flag::F_Unhandled);
+    }
+
+    bool Throw() const {
+        return flags.test(Flag::F_Throw);
+    }
+
+    bool UserUnhandled() const {
+        return flags.test(Flag::F_UserUnhandled);
+    }
+
+    void setThrow() {
+        flags.set(Flag::F_Throw);
+    }
+
+    void setUserUnhandled() {
+        flags.set(Flag::F_UserUnhandled);
+    }
+
+    void resetThrow() {
+        flags.reset(Flag::F_Throw);
+    }
+
+    void resetUserUnhandled() {
+        flags.reset(Flag::F_UserUnhandled);
+    }
+
+    // 'All', 'Never' values for VScode
+    void setAll() {
+        // setUnhandled() not supported
+        // its looks as a problem with unconsistent state
+        setThrow();
+        setUserUnhandled();
+    }
+
+    void resetAll() {
+        // resetUnhandled() not supported
+        // its looks as a problem with unconsistent state
+        resetThrow();
+        resetUserUnhandled();
+    }
+
+    bool All() const {
+        return Unhandled() && Throw() && UserUnhandled();
+    }
+
+    bool Never() const {
+        // Always false because Unhandled() always throw
+        return !Unhandled() && !Throw() && !UserUnhandled();
+    }
+
+    // Logical extentions for freindly using of class
+    bool Any() const {
+        return Throw() || UserUnhandled();
+    }
+
+    bool OnlyUnhandled() const {
+        return Unhandled() && !Throw() && !UserUnhandled();
+    }
+
+    bool BothUnhandledAndUserUnhandled() const {
+        return Unhandled() && !Throw() && UserUnhandled();
+    }
+
+};
+
+struct ExceptionBreakpointStorage
+{
+private:
+    // vsdbg not supported list of exception breakpoint command
+    struct ExceptionBreakpoint {
+        ExceptionBreakpoint() : current_asterix_id(0) {}
+        std::unordered_map<uint32_t, std::string> table;
+        // For global filter (*) we need to know last id
+        uint32_t current_asterix_id;
+        // vsdbg supports any clones of named exception filters, but
+        // for customers its will to come some difficult for matching.
+        // For netcoredbg approach based on single unique name for each
+        // next of user exception.
+        //std::unordered_map<std::string, ExceptionBreakMode> exceptionBreakpoints;
+        std::unordered_multimap<std::string, ExceptionBreakMode> exceptionBreakpoints;
+    };
+
+    ExceptionBreakpoint bp;
+
+public:
+    HRESULT Insert(uint32_t id, const ExceptionBreakMode &mode, const std::string &name);
+    HRESULT Delete(uint32_t id);
+    bool Match(const std::string &exceptionName, const ExceptionBreakCategory category) const;
+    HRESULT GetExceptionBreakMode(ExceptionBreakMode &out, const std::string &name) const;
+
+    ExceptionBreakpointStorage() = default;
+    ExceptionBreakpointStorage(ExceptionBreakpointStorage &&that) = default;
+    ExceptionBreakpointStorage(const ExceptionBreakpointStorage &that) = delete;
+};
+
+// An ExceptionPathSegment represents a segment in a path that is used to match
+// leafs or nodes in a tree of exceptions. If a segment consists of more than
+// one name, it matches the names provided if 'negate' is false or missing
+// or it matches anything except the names provided if 'negate' is true.
+struct ExceptionPathSegment
+{
+    // If false or missing this segment matches the names provided, otherwise
+    // it matches anything except the names provided.
+    bool negate;
+    // Depending on the value of 'negate' the names
+    // that should match or not match.
+    std::vector<std::string> names;
+};
+
+// An ExceptionOptions assigns configuration options to a set of exceptions.
+struct ExceptionOptions
+{
+    // A path that selects a single or multiple exceptions in a tree.
+    // If 'path' is missing, the whole tree is selected.
+    // By convention the first segment of the path is a category that is used to
+    // group exceptions in the UI.
+    std::vector<ExceptionPathSegment> path;
+    // Condition when a thrown exception should result in a break.
+    ExceptionBreakMode breakMode;
+};
+
+// The request configures the debuggers response to thrown exceptions.
+// If an exception is configured to break, a 'stopped' event is fired
+// (with reason 'exception').
+struct SetExceptionBreakpointsRequest
+{
+    // IDs of checked exception options. The set of IDs is returned via the
+    // 'exceptionBreakpointFilters' capability.
+    std::vector<std::string> filters;
+    // Configuration options for selected exceptions.
+    std::vector<ExceptionOptions> exceptionOptions;
+};
+
+struct ExceptionBreakpointsFilter
+{
+    // The internal ID of the filter. This value is passed to the
+    // setExceptionBreakpoints request.
+    std::string filter;
+    // The name of the filter. This will be shown in the UI.
+    std::string label;
+    // Initial value of the filter. If not specified a value 'false' is assumed.
+    bool default_value;
+
+    ExceptionBreakpointsFilter(const std::string &fr, const std::string &ll,
+        bool df = false) : filter(fr), label(ll), default_value(df) {}
+};
+
+struct Capabilities
+{
+    // Available filters or options for the setExceptionBreakpoints request.
+    std::vector<ExceptionBreakpointsFilter> exceptionBreakpointFilters;
+    // The debug adapter supports 'exceptionOptions' on the
+    // setExceptionBreakpoints request.
+    bool supportsExceptionOptions;
+    // The debug adapter supports the 'exceptionInfo' request.
+    bool supportsExceptionInfoRequest;
+    //
+    // ... many other features
+};
+
+struct ExceptionDetails
+{
+    // Message contained in the exception.
+    std::string message;
+    // Short type name of the exception object.
+    std::string typeName;
+    // Fully-qualified type name of the exception object.
+    std::string fullTypeName;
+    // Optional expression that can be evaluated in the current scope
+    // to obtain the exception object.
+    std::string evaluateName;
+    // Stack trace at the time the exception was thrown.
+    std::string stackTrace;
+    // Details of the exception contained by this exception, if any.
+    std::vector<ExceptionDetails> innerException;
+};
+
+struct ExceptionInfoResponse
+{
+    // ID of the exception that was thrown.
+    std::string exceptionId;
+    // Descriptive text for the exception provided by the debug adapter.
+    std::string description;
+    // Mode that caused the exception notification to be raised.
+    ExceptionBreakMode breakMode;
+    // Detailed information about the exception.
+    ExceptionDetails details;
+
+    std::string getVSCodeBreakMode() const;
+};
index 3631085ef8078f6921fb1153bee5e0424595c629..50ad2a24f389427030f9ad735e592f0c084fb8fd 100644 (file)
 #include "frames.h"
 #include "symbolreader.h"
 #include "logger.h"
+#include "cputil.h"
+#include "protocol.h"
 
+using std::string;
+using std::vector;
 
 HRESULT Variables::GetNumChild(
     ICorDebugValue *pValue,
@@ -73,6 +77,17 @@ struct Variables::Member
     Member(const Member &that) = delete;
 };
 
+void Variables::FillValueAndType(Member &member, Variable &var, bool escape)
+{
+    if (member.value == nullptr)
+    {
+        var.value = "<error>";
+        return;
+    }
+    PrintValue(member.value, var.value, escape);
+    TypePrinter::GetTypeOfValue(member.value, var.type);
+}
+
 HRESULT Variables::FetchFieldsAndProperties(
     ICorDebugValue *pInputValue,
     ICorDebugThread *pThread,
@@ -219,6 +234,228 @@ void Variables::AddVariableReference(Variable &variable, uint64_t frameId, ICorD
     m_variables.emplace(std::make_pair(variable.variablesReference, std::move(variableReference)));
 }
 
+HRESULT Variables::GetICorDebugValueMembers(
+    ICorDebugProcess *pProcess,
+    ICorDebugThread *pThread,
+    uint64_t frameId,
+    ICorDebugValue *value,
+    bool fetchOnlyStatic,
+    vector<Member> &members)
+{
+    if (pProcess == nullptr || pThread == nullptr || value == nullptr)
+        return E_FAIL;
+
+    HRESULT Status;
+
+    StackFrame stackFrame(frameId);
+    ToRelease<ICorDebugFrame> pFrame;
+    IfFailRet(GetFrameAt(pThread, stackFrame.GetLevel(), &pFrame));
+
+    ToRelease<ICorDebugILFrame> pILFrame;
+    if (pFrame)
+        IfFailRet(pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*)&pILFrame));
+
+    bool hasStaticMembers = false;
+    IfFailRet(FetchFieldsAndProperties(value,
+        pThread,
+        pILFrame,
+        members,
+        fetchOnlyStatic,
+        hasStaticMembers,
+        0,
+        INT_MAX));
+
+    return S_OK;
+}
+
+HRESULT Variables::GetVariableMembers(
+    ICorDebugProcess *pProcess,
+    ICorDebugThread *pThread,
+    uint64_t frameId,
+    Variable &var,
+    vector<Member> &members)
+{
+    if (pProcess == nullptr || pThread == nullptr)
+        return E_FAIL;
+
+    VariableReference &ref = m_variables.at(var.variablesReference);
+    if (ref.IsScope())
+        return E_INVALIDARG;
+
+    if (!ref.value)
+        return E_FAIL;
+
+    bool fetchOnlyStatic = ref.valueKind == ValueIsClass;
+    return GetICorDebugValueMembers(pProcess, pThread, frameId, ref.value,
+        fetchOnlyStatic, members);
+}
+
+static HRESULT GetModuleName(ICorDebugThread *pThread, std::string &module) {
+    HRESULT Status;
+    ToRelease<ICorDebugFrame> pFrame;
+    IfFailRet(pThread->GetActiveFrame(&pFrame));
+
+    if (pFrame == nullptr)
+        return E_FAIL;
+
+    ToRelease<ICorDebugFunction> pFunc;
+    IfFailRet(pFrame->GetFunction(&pFunc));
+
+    ToRelease<ICorDebugModule> pModule;
+    IfFailRet(pFunc->GetModule(&pModule));
+
+    ToRelease<IUnknown> pMDUnknown;
+    ToRelease<IMetaDataImport> pMDImport;
+    IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+    IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*)&pMDImport));
+
+    WCHAR mdName[mdNameLen];
+    ULONG nameLen;
+    IfFailRet(pMDImport->GetScopeProps(mdName, _countof(mdName), &nameLen, nullptr));
+    module = to_utf8(mdName);
+
+    return S_OK;
+}
+
+HRESULT Variables::GetExceptionInfoResponseDetailsMembers(
+    ICorDebugProcess *pProcess,
+    ICorDebugThread *pThread,
+    uint64_t frameId,
+    Variable &varRoot,
+    ExceptionDetails &details,
+    bool &isFoundInnerException,
+    vector<Member> &members)
+{
+    HRESULT Status;
+
+    details.evaluateName = varRoot.name;
+    details.typeName = varRoot.type;
+    details.fullTypeName = varRoot.type;
+
+    bool isMessage, isStackTrace, isInnerException;
+    for (auto &it : members)
+    {
+        isMessage = isStackTrace = isInnerException = false;
+        if ( !(isMessage = (it.name == "Message")) &&
+             !(isStackTrace = (it.name == "StackTrace")) &&
+             !(isInnerException = (it.name == "InnerException")))
+            continue;
+
+        Variable var;
+        var.name = it.name;
+        FillValueAndType(it, var);
+
+        if (isMessage)
+        {
+            details.message = var.value;
+            continue;
+        }
+
+        if (isStackTrace)
+        {
+            details.stackTrace = var.value;
+            continue;
+        }
+
+        if (isInnerException)
+        {
+            vector<Member> mem;
+            if ((Status = GetICorDebugValueMembers(pProcess, pThread, frameId,
+                 it.value, false, mem)) != S_OK)
+                return Status;
+
+            if (!mem.empty())
+            {
+                isFoundInnerException = true;
+                details.innerException.emplace_back(std::move(ExceptionDetails()));
+                bool dummy;
+                if ((Status = GetExceptionInfoResponseDetailsMembers(pProcess, pThread,
+                    frameId, var, details.innerException.back(), dummy, mem)) != S_OK)
+                {
+                    details.innerException.pop_back();
+                    return Status;
+                }
+            }
+        }
+    }
+
+    return S_OK;
+}
+
+HRESULT Variables::GetExceptionInfoResponseData(
+    ICorDebugProcess *pProcess,
+    int threadId,
+    const ExceptionBreakMode &mode,
+    ExceptionInfoResponse &exceptionInfoResponse)
+{
+    HRESULT Status;
+
+    uint64_t frameId = StackFrame(threadId, 0, "").id;
+    ToRelease<ICorDebugThread> pThread;
+    IfFailRet(pProcess->GetThread(threadId, &pThread));
+
+    Variable varException;
+    if (GetExceptionVariable(frameId, pThread, varException))
+        return E_FAIL;
+
+    vector<Member> members;
+    if ((Status = GetVariableMembers(pProcess, pThread, frameId, varException, members)) != S_OK)
+       return Status;
+
+    if (mode.OnlyUnhandled() || mode.UserUnhandled())
+    {
+        exceptionInfoResponse.description = "An unhandled exception of type '" + varException.type +
+            "' occurred in " + varException.module;
+    }
+    else
+    {
+        exceptionInfoResponse.description = "Exception thrown: '" + varException.type +
+            "' in " + varException.module;
+    }
+
+    exceptionInfoResponse.exceptionId = varException.type;
+    exceptionInfoResponse.breakMode = mode;
+
+    bool isFoundInnerException = false;
+    if ((Status = GetExceptionInfoResponseDetailsMembers(pProcess, pThread, frameId,
+            varException, exceptionInfoResponse.details, isFoundInnerException, members)) != S_OK)
+        return Status;
+
+    if (isFoundInnerException)
+        exceptionInfoResponse.description += "\n Inner exception found, see $exception in variables window for more details.";
+
+    return S_OK;
+}
+
+HRESULT Variables::GetExceptionVariable(
+    uint64_t frameId,
+    ICorDebugThread *pThread,
+    Variable &var)
+{
+    HRESULT Status;
+    ToRelease<ICorDebugValue> pExceptionValue;
+    if (SUCCEEDED(pThread->GetCurrentException(&pExceptionValue)) && pExceptionValue != nullptr)
+    {
+        var.name = "$exception";
+        var.evaluateName = var.name;
+
+        bool escape = true;
+        PrintValue(pExceptionValue, var.value, escape);
+        TypePrinter::GetTypeOfValue(pExceptionValue, var.type);
+
+        // AddVariableReference is re-interable function.
+        AddVariableReference(var, frameId, pExceptionValue, ValueIsVariable);
+
+        string excModule;
+        IfFailRet(GetModuleName(pThread, excModule));
+        var.module = excModule;
+
+        return S_OK;
+    }
+
+    return E_FAIL;
+}
+
 HRESULT Variables::GetStackVariables(
     uint64_t frameId,
     ICorDebugThread *pThread,
@@ -228,25 +465,11 @@ HRESULT Variables::GetStackVariables(
     std::vector<Variable> &variables)
 {
     HRESULT Status;
-
     int currentIndex = -1;
-
-    ToRelease<ICorDebugValue> pExceptionValue;
-    if (SUCCEEDED(pThread->GetCurrentException(&pExceptionValue)) && pExceptionValue != nullptr)
-    {
+    Variable var;
+    if (GetExceptionVariable(frameId, pThread, var) == S_OK) {
+        variables.push_back(var);
         ++currentIndex;
-        bool outOfRange = currentIndex < start || (count != 0 && currentIndex >= start + count);
-        if (!outOfRange)
-        {
-            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, ValueIsVariable);
-            variables.push_back(var);
-        }
     }
 
     IfFailRet(m_evaluator.WalkStackVars(pFrame, [&](
@@ -374,16 +597,7 @@ HRESULT Variables::GetChildren(
         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;
-        if (it.value == nullptr)
-        {
-            var.value = "<error>";
-        }
-        else
-        {
-            PrintValue(it.value, var.value, escape);
-            TypePrinter::GetTypeOfValue(it.value, var.type);
-        }
+        FillValueAndType(it, var);
         AddVariableReference(var, ref.frameId, it.value, ValueIsVariable);
         variables.push_back(var);
     }
index 3d4bddb84e263518bb54cd94565079936ad3e838..0e2568b9df1e7584481acddfc2497fe63d73fe27 100644 (file)
 #include "cputil.h"
 #include "logger.h"
 
+using std::string;
+using std::vector;
+using std::min;
+
 // for convenience
 using json = nlohmann::json;
 
@@ -74,6 +78,28 @@ void to_json(json &j, const Variable &v) {
     }
 }
 
+static json getVSCode(const ExceptionDetails &self) {
+    json details = json({});
+
+    details["message"] = self.message;
+    details["typeName"] = self.typeName;
+    details["fullTypeName"] = self.fullTypeName;
+    details["evaluateName"] = self.evaluateName;
+    details["stackTrace"] = self.stackTrace;
+
+    json arr = json::array();
+    if (!self.innerException.empty()) {
+        // INFO: Visual Studio Code does not display inner exception,
+        // but vsdbg fill all nested InnerExceptions in Response.
+        const auto it = self.innerException.begin();
+        json inner = getVSCode(*it);
+        arr.push_back(inner);
+    }
+    details["innerException"] = arr;
+
+    return details;
+}
+
 void VSCodeProtocol::EmitStoppedEvent(StoppedEvent event)
 {
     LogFuncEntry();
@@ -282,6 +308,7 @@ void VSCodeProtocol::AddCapabilitiesTo(json &capabilities)
     capabilities["supportsFunctionBreakpoints"] = true;
     capabilities["supportsConditionalBreakpoints"] = true;
     capabilities["supportTerminateDebuggee"] = true;
+    capabilities["supportsExceptionInfoRequest"] = true;
 }
 
 HRESULT VSCodeProtocol::HandleCommand(const std::string &command, const json &arguments, json &body)
@@ -292,13 +319,64 @@ HRESULT VSCodeProtocol::HandleCommand(const std::string &command, const json &ar
         EmitCapabilitiesEvent();
 
         m_debugger->Initialize();
+
         AddCapabilitiesTo(body);
 
         return S_OK;
     } },
+    { "setExceptionBreakpoints", [this](const json &arguments, json &body) {
+        vector<string> filters = arguments.value("filters", vector<string>());
+        ExceptionBreakMode mode;
+
+        namespace KW = VSCodeExceptionBreakModeKeyWord;
+
+        for (unsigned i = 0; i < filters.size(); i++)
+        {
+            if (filters[i].compare(KW::ALL) == 0 ||
+                filters[i].compare(KW::ALWAYS) == 0) {
+                mode.setAll();
+            }
+            if (filters[i].compare(KW::USERUNHANDLED) == 0 ||
+                filters[i].compare(KW::USERUNHANDLED_A) == 0) {
+                mode.setUserUnhandled();
+            }
+            // Nothing to do for "unhandled"
+            if (filters[i].compare(KW::NEVER) == 0) {
+                mode.resetAll();
+            }
+        }
+
+        const string globalExceptionBreakpoint = "*";
+        uint32_t id;
+        m_debugger->InsertExceptionBreakpoint(mode, globalExceptionBreakpoint, id);
+
+        //
+        // TODO:
+        // - implement options support. options not supported in
+        // current vscode 1.31.1 with C# plugin 1.17.1
+        // - use ExceptionBreakpointStorage type for support options feature
+        body["supportsExceptionOptions"] = false;
+
+        return S_OK;
+    } },
     { "configurationDone", [this](const json &arguments, json &body){
         return m_debugger->ConfigurationDone();
     } },
+    { "exceptionInfo", [this](const json &arguments, json &body) {
+        int threadId = arguments.at("threadId");
+        ExceptionInfoResponse exceptionResponse;
+        if (!m_debugger->GetExceptionInfoResponse(threadId, exceptionResponse))
+        {
+            body["breakMode"] = exceptionResponse.getVSCodeBreakMode();
+            body["exceptionId"] = exceptionResponse.exceptionId;
+            body["description"] = exceptionResponse.description;
+            body["details"] = getVSCode(exceptionResponse.details);
+
+            return S_OK;
+        }
+
+        return E_FAIL;
+    } },
     { "setBreakpoints", [this](const json &arguments, json &body){
         HRESULT Status;
 
@@ -669,3 +747,27 @@ void VSCodeProtocol::Log(const std::string &prefix, const std::string &text)
         }
     }
 }
+
+string ExceptionInfoResponse::getVSCodeBreakMode() const
+{
+    namespace KW = VSCodeExceptionBreakModeKeyWord;
+
+    if (breakMode.Never())
+        return KW::NEVER;
+
+    if (breakMode.All())
+        return KW::ALWAYS;
+
+    if (breakMode.OnlyUnhandled())
+        return KW::UNHANDLED;
+
+    // Throw() not supported for VSCode
+    //  - description of "always: always breaks".
+    // if (breakMode.Throw())
+
+    if (breakMode.UserUnhandled())
+        return KW::USERUNHANDLED;
+
+    // Logical Error
+    return "undefined";
+}
index 4700adf0e988615b2755166fce88452df6725976..69a5b260b24fee5304a0d51c59973a03b1c333b2 100644 (file)
 #include "json/json.hpp"
 #include "debugger.h"
 
+namespace VSCodeExceptionBreakModeKeyWord
+{
+    static const std::string ALL = "all";
+    static const std::string ALWAYS = "always";
+    static const std::string NEVER = "never";
+    static const std::string USERUNHANDLED = "userUnhandled";
+    static const std::string USERUNHANDLED_A = "user-unhandled";
+    static const std::string UNHANDLED = "unhandled";
+}
 
 class VSCodeProtocol : public Protocol
 {
diff --git a/tests/ExceptionBreakpointTest/ExceptionBreakpointTest.cs b/tests/ExceptionBreakpointTest/ExceptionBreakpointTest.cs
new file mode 100644 (file)
index 0000000..de2da14
--- /dev/null
@@ -0,0 +1,431 @@
+/*
+using System.IO;
+Send("1-file-exec-and-symbols dotnet");
+Send("2-exec-arguments " + TestBin);
+
+string filename = Path.GetFileName(TestSource);
+
+Send("3-break-exception-insert throw+user-unhandled A B C");
+var r = Expect("3^done");
+Send("4-break-exception-delete 3 2 1");
+r = Expect("4^done");
+
+//////// Silent removing of previous global exception filter
+Send("200-break-exception-insert throw *");
+r = Expect("200^done");
+int be200 = r.Find("bkpt").FindInt("number");
+
+Send("201-break-exception-insert throw+user-unhandled *");
+r = Expect("201^done");
+int be201 = r.Find("bkpt").FindInt("number");
+
+Send(String.Format("202-break-exception-delete {0}", be200));
+r = Expect("202^error");
+
+Send(String.Format("203-break-exception-delete {0}", be201));
+r = Expect("203^done");
+////////
+
+Send(String.Format("5-break-insert -f {0}:{1}", filename, Lines["PIT_STOP_A"]));
+r = Expect("5^done");
+int b1 = r.Find("bkpt").FindInt("number");
+
+Send(String.Format("6-break-insert -f {0}:{1}", filename, Lines["PIT_STOP_B"]));
+r = Expect("6^done");
+int b2 = r.Find("bkpt").FindInt("number");
+
+Send("7-exec-run");
+
+r = Expect("*stopped");
+Assert.Equal("entry-point-hit", r.FindString("reason"));
+Assert.Equal(Lines["MAIN"], r.Find("frame").FindInt("line"));
+
+//////// Expected result => Not found any exception breakpoits.
+//////// "State: !Thow() && !UserUnhandled() name := '*'";
+Send("8-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+//////// Expected result => Not found any exception breakpoits.
+//////// "State: !Thow() && UserUnhandled() name := '*'";
+Send("10-break-exception-insert user-unhandled *");
+r = Expect("10^done");
+int be1 = r.Find("bkpt").FindInt("number");
+
+Send("11-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("12-break-exception-delete {0}", be1));
+r = Expect("12^done");
+
+//////// Expected result => Raised EXCEPTION_A and EXCEPTION_B.
+//////// "State: Thow() && !UserUnhandled() name := '*'";
+Send("13-break-exception-insert throw *");
+r = Expect("13^done");
+int be2 = r.Find("bkpt").FindInt("number");
+
+Send("14-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_A"], r.Find("frame").FindInt("line"));
+
+Send("15-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_B"], r.Find("frame").FindInt("line"));
+
+Send("17-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("18-break-exception-delete {0}", be2));
+r = Expect("18^done");
+
+//////// Expected result => Raised EXCEPTION_A and EXCEPTION_B.
+//////// "State: Thow() && UserUnhandled() name := '*'";
+Send("19-break-exception-insert throw+user-unhandled *");
+r = Expect("19^done");
+int be3 = r.Find("bkpt").FindInt("number");
+
+Send("20-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_A"], r.Find("frame").FindInt("line"));
+
+Send("21-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_B"], r.Find("frame").FindInt("line"));
+
+Send("22-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("23-break-exception-delete {0}", be3));
+r = Expect("23^done");
+
+//////// Expected result => Raised EXCEPTION_A and EXCEPTION_B.
+//////// "State: Thow() && UserUnhandled() name := '*'";
+Send("19-break-exception-insert throw+user-unhandled *");
+r = Expect("19^done");
+int be4 = r.Find("bkpt").FindInt("number");
+
+Send("20-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_A"], r.Find("frame").FindInt("line"));
+
+Send("21-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_B"], r.Find("frame").FindInt("line"));
+
+Send("22-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("23-break-exception-delete {0}", be4));
+r = Expect("23^done");
+
+//////// Expected result => Raised EXCEPTION_A and EXCEPTION_B.
+//////// "State: Thow() && UserUnhandled()";
+//////// "name := System.DivideByZeroException";
+Send("24-break-exception-insert throw+user-unhandled System.DivideByZeroException");
+r = Expect("24^done");
+int be5 = r.Find("bkpt").FindInt("number");
+
+Send("25-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_A"], r.Find("frame").FindInt("line"));
+
+Send("26-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_B"], r.Find("frame").FindInt("line"));
+
+Send("27-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("28-break-exception-delete {0}", be5));
+r = Expect("28^done");
+
+//////// Expected result => Raised EXCEPTION_A and EXCEPTION_B.
+////////  "State: Thow()";
+////////  "name := System.DivideByZeroException";
+////////  "State: UserUnhandled()";
+////////  "name := System.DivideByZeroException";
+Send("29-break-exception-insert throw System.DivideByZeroException");
+r = Expect("29^done");
+int be6 = r.Find("bkpt").FindInt("number");
+
+Send("30-break-exception-insert user-unhandled System.DivideByZeroException");
+r = Expect("30^done");
+int be7 = r.Find("bkpt").FindInt("number");
+
+Send("31-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_A"], r.Find("frame").FindInt("line"));
+
+Send("32-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_B"], r.Find("frame").FindInt("line"));
+
+Send("33-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("34-break-exception-delete {0}", be7));
+r = Expect("34^done");
+
+Send(String.Format("35-break-exception-delete {0}", be6));
+r = Expect("35^done");
+
+//////// Expected result => Raised EXCEPTION_A and EXCEPTION_B.
+//////// "State: Thow() && UserUnhandled()";
+//////// "name := System.DivideByZeroExceptionWrong and *";
+Send("36-break-exception-insert throw+user-unhandled *");
+r = Expect("36^done");
+int be8 = r.Find("bkpt").FindInt("number");
+
+Send("37-break-exception-insert throw+user-unhandled DivideByZeroExceptionWrong");
+r = Expect("37^done");
+int be9 = r.Find("bkpt").FindInt("number");
+
+Send("38-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_A"], r.Find("frame").FindInt("line"));
+
+Send("39-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_B"], r.Find("frame").FindInt("line"));
+
+Send("40-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_A"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("41-break-exception-delete {0}", be8));
+r = Expect("41^done");
+
+Send(String.Format("42-break-exception-delete {0}", be9));
+r = Expect("42^done");
+
+//////// Expected result => Not found any exception breakpoits.
+//////// "State: Thow() && UserUnhandled()";
+//////// "name := System.DivideByZeroExceptionWrong";
+
+Send("36-break-exception-insert throw+user-unhandled System.DivideByZeroExceptionWrong");
+r = Expect("36^done");
+int be10 = r.Find("bkpt").FindInt("number");
+
+Send("37-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("breakpoint-hit", r.FindString("reason"));
+Assert.Equal(Lines["PIT_STOP_B"], r.Find("frame").FindInt("line"));
+
+Send(String.Format("38-break-exception-delete {0}", be10));
+r = Expect("38^done");
+
+//////// Expected result => Raised EXCEPTION_C, EXCEPTION_D, EXCEPTION_E and exit after unhandled EXCEPTION_E.
+//////// "State: !Thow() && UserUnhandled()";
+//////// "name := AppException";
+
+//////// Test of setting unhandled - read only mode.
+Send("39-break-exception-insert unhandled AppException");
+r = Expect("39^done");
+
+Send("40-break-exception-insert user-unhandled AppException");
+r = Expect("40^done");
+int be11 = r.Find("bkpt").FindInt("number");
+
+Send("41-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_C"], r.Find("frame").FindInt("line"));
+
+Send("42-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_D"], r.Find("frame").FindInt("line"));
+
+Send("43-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_E"], r.Find("frame").FindInt("line"));
+
+Send("44-exec-continue");
+
+r = Expect("*stopped");
+Assert.Equal("exception-received", r.FindString("reason"));
+Assert.Equal(Lines["EXCEPTION_E"], r.Find("frame").FindInt("line"));
+
+Send("45-exec-continue");
+r = Expect("*stopped");
+Assert.Equal("exited", r.FindString("reason"));
+
+Send(String.Format("46-break-exception-delete {0}", be11));
+r = Expect("46^done");
+
+Send(String.Format("99-break-delete {0}", b1));
+Expect("99^done");
+Send(String.Format("100-break-delete {0}", b2));
+Expect("100^done");
+
+Send("-gdb-exit");
+*/
+
+using System;
+using System.Threading;
+
+public class AppException : Exception
+{
+    public AppException(String message) : base(message) { }
+    public AppException(String message, Exception inner) : base(message, inner) { }
+}
+
+public class Test
+{
+    public void TestSystemException()
+    { // //@PIT_STOP_A@
+        int zero = 1 - 1;
+        try
+        {
+            int a = 1 / zero; // //@EXCEPTION_A@
+        }
+        catch (Exception e)
+        {
+            Console.WriteLine($"Implicit Exception: {e.Message}");
+        }
+        try
+        {
+            throw new System.DivideByZeroException(); // //@EXCEPTION_B@
+        }
+        catch (Exception e)
+        {
+            Console.WriteLine($"Explicit Exception: {e.Message}");
+        }
+        Console.WriteLine("Complete system exception test\n");
+    }
+    public void TestAppException()
+    { // //@PIT_STOP_B@
+        try
+        {
+            CatchInner();
+        }
+        catch (AppException e)
+        {
+            Console.WriteLine("Caught: {0}", e.Message);
+            if (e.InnerException != null)
+            {
+                Console.WriteLine("Inner exception: {0}", e.InnerException);
+            }
+            throw new AppException("Error again in CatchInner caused by calling the ThrowInner method.", e);  // //@EXCEPTION_E@
+        }
+        Console.WriteLine("Complete application exception test\n");
+    }
+    public void ThrowInner()
+    {
+        throw new AppException("Exception in ThrowInner method."); // //@EXCEPTION_C@
+    }
+    public void CatchInner()
+    {
+        try
+        {
+            this.ThrowInner();
+        }
+        catch (AppException e)
+        {
+            throw new AppException("Error in CatchInner caused by calling the ThrowInner method.", e); // //@EXCEPTION_D@
+        }
+    }
+}
+
+public class MainClass
+{
+    public static void Main()
+    { // //@MAIN@
+        Test test = new Test();
+
+        //////// "State: !Thow() && !UserUnhandled() name := '*'";
+        test.TestSystemException();
+
+        //////// "State: !Thow() && UserUnhandled() name := '*'";
+        test.TestSystemException();
+
+        //////// "State: Thow() && !UserUnhandled() name := '*'";
+        test.TestSystemException();
+
+        ////////  "State: Thow() && UserUnhandled() name := '*'";
+        test.TestSystemException();
+
+        ////////  "State: Thow() && UserUnhandled()";
+        ////////  "name := System.DivideByZeroException";
+        test.TestSystemException();
+
+        ////////  "State: Thow()";
+        ////////  "name := System.DivideByZeroException";
+        ////////  "State: UserUnhandled()";
+        ////////  "name := System.DivideByZeroException";
+        test.TestSystemException();
+
+        ////////  "State: Thow() && UserUnhandled()";
+        ////////  "name := System.DivideByZeroExceptionWrong and *";
+        test.TestSystemException();
+
+        ////////  "State: Thow() && UserUnhandled()";
+        ////////  "name := System.DivideByZeroExceptionWrong";
+        test.TestSystemException();
+
+        //////// "TODO:\n"
+        //////// "Test for check forever waiting:\n"
+        //////// "threads in NetcoreDBG (unsupported now)\n"
+        //////// "Thread threadA = new Thread(new ThreadStart(test.TestSystemException));\n"
+        //////// "Thread threadB = new Thread(new ThreadStart(test.TestSystemException));\n"
+        //////// "threadA.Start();\n"
+        //////// "threadB.Start();\n"
+        //////// "threadA.Join();\n"
+        //////// "threadB.Join();\n"
+
+        //////// "Test with process exit at the end, need re-run"
+        //////// "INFO: State: !Thow() && UserUnhandled() name := AppException";
+        test.TestAppException();
+    }
+}
diff --git a/tests/ExceptionBreakpointTest/ExceptionBreakpointTest.csproj b/tests/ExceptionBreakpointTest/ExceptionBreakpointTest.csproj
new file mode 100644 (file)
index 0000000..ce1697a
--- /dev/null
@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp2.0</TargetFramework>
+  </PropertyGroup>
+
+</Project>
index bf1b4bfb38d83df9dbf71a040c167e7e550dfb7b..a3dd61d2ce3c86886392dc6c2a720caf4968088c 100644 (file)
@@ -36,6 +36,9 @@ namespace Runner
         [Fact]
         public void BreakpointAddRemoveTest() => ExecuteTest();
 
+        [Fact]
+        public void ExceptionBreakpointTest() => ExecuteTest();
+
         [Fact]
         public void SetValuesTest() => ExecuteTest();
 
index 96bd903800045039faa2e43bb8e9a7ee7f8b7355..b715e8a0a422c903825631461d7474dcea1593dd 100644 (file)
@@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionsTest", "Expressi
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LambdaTest", "LambdaTest\LambdaTest.csproj", "{70F91400-AB4E-4A19-A137-9B5BF36C8543}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExceptionBreakpointTest", "ExceptionBreakpointTest\ExceptionBreakpointTest.csproj", "{361771B0-7948-412E-86ED-D41157F6DE6B}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
@@ -128,5 +130,17 @@ Global
                {70F91400-AB4E-4A19-A137-9B5BF36C8543}.Release|x64.Build.0 = Release|x64
                {70F91400-AB4E-4A19-A137-9B5BF36C8543}.Release|x86.ActiveCfg = Release|x86
                {70F91400-AB4E-4A19-A137-9B5BF36C8543}.Release|x86.Build.0 = Release|x86
+               {361771B0-7948-412E-86ED-D41157F6DE6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {361771B0-7948-412E-86ED-D41157F6DE6B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {361771B0-7948-412E-86ED-D41157F6DE6B}.Debug|x64.ActiveCfg = Debug|x64
+               {361771B0-7948-412E-86ED-D41157F6DE6B}.Debug|x64.Build.0 = Debug|x64
+               {361771B0-7948-412E-86ED-D41157F6DE6B}.Debug|x86.ActiveCfg = Debug|x86
+               {361771B0-7948-412E-86ED-D41157F6DE6B}.Debug|x86.Build.0 = Debug|x86
+               {361771B0-7948-412E-86ED-D41157F6DE6B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {361771B0-7948-412E-86ED-D41157F6DE6B}.Release|Any CPU.Build.0 = Release|Any CPU
+               {361771B0-7948-412E-86ED-D41157F6DE6B}.Release|x64.ActiveCfg = Release|x64
+               {361771B0-7948-412E-86ED-D41157F6DE6B}.Release|x64.Build.0 = Release|x64
+               {361771B0-7948-412E-86ED-D41157F6DE6B}.Release|x86.ActiveCfg = Release|x86
+               {361771B0-7948-412E-86ED-D41157F6DE6B}.Release|x86.Build.0 = Release|x86
        EndGlobalSection
 EndGlobal