Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lldb-dap] Support throw and catch exception breakpoints for dynamica… #97871

Merged
merged 1 commit into from
Jul 10, 2024

Conversation

walter-erquinigo
Copy link
Member

…lly registered languages

First of all, this is done to support exceptions for the Mojo language, but it's done in a way that will benefit any other plugin language.

  1. I added a new lldb-dap CLI argument (not DAP field) called pre-init-commands. These commands are executed before DAP initialization. The other init-commands are executed after DAP initialization. It's worth mentioning that the debug adapter returns to VSCode the list of supported exception breakpoints during DAP initialization, which means that I need to register the Mojo plugin before that initialization step, hence the need for pre-init-commands. In general, language plugins should be registered in that step, as they affect the capabilities of the debugger.
  2. I added a set of APIs for lldb-dap to query information of each language related to exception breakpoints. E.g. whether a language supports throw or catch breakpoints, how the throw keyword is called in each particular language, etc.
  3. I'm realizing that the Swift support for exception breakpoints in lldb-dap should have been implemented in this way, instead of hardcoding it.

@llvmbot
Copy link
Collaborator

llvmbot commented Jul 6, 2024

@llvm/pr-subscribers-lldb

Author: Walter Erquinigo (walter-erquinigo)

Changes

…lly registered languages

First of all, this is done to support exceptions for the Mojo language, but it's done in a way that will benefit any other plugin language.

  1. I added a new lldb-dap CLI argument (not DAP field) called pre-init-commands. These commands are executed before DAP initialization. The other init-commands are executed after DAP initialization. It's worth mentioning that the debug adapter returns to VSCode the list of supported exception breakpoints during DAP initialization, which means that I need to register the Mojo plugin before that initialization step, hence the need for pre-init-commands. In general, language plugins should be registered in that step, as they affect the capabilities of the debugger.
  2. I added a set of APIs for lldb-dap to query information of each language related to exception breakpoints. E.g. whether a language supports throw or catch breakpoints, how the throw keyword is called in each particular language, etc.
  3. I'm realizing that the Swift support for exception breakpoints in lldb-dap should have been implemented in this way, instead of hardcoding it.

Full diff: https://github.com/llvm/llvm-project/pull/97871.diff

7 Files Affected:

  • (modified) lldb/include/lldb/API/SBLanguageRuntime.h (+26)
  • (modified) lldb/include/lldb/Target/Language.h (+8)
  • (modified) lldb/source/API/SBLanguageRuntime.cpp (+40)
  • (modified) lldb/tools/lldb-dap/DAP.cpp (+54-1)
  • (modified) lldb/tools/lldb-dap/DAP.h (+2)
  • (modified) lldb/tools/lldb-dap/Options.td (+8)
  • (modified) lldb/tools/lldb-dap/lldb-dap.cpp (+16-3)
diff --git a/lldb/include/lldb/API/SBLanguageRuntime.h b/lldb/include/lldb/API/SBLanguageRuntime.h
index 38aac05d490c19..acdc256fa2ac5a 100644
--- a/lldb/include/lldb/API/SBLanguageRuntime.h
+++ b/lldb/include/lldb/API/SBLanguageRuntime.h
@@ -18,6 +18,32 @@ class SBLanguageRuntime {
   static lldb::LanguageType GetLanguageTypeFromString(const char *string);
 
   static const char *GetNameForLanguageType(lldb::LanguageType language);
+
+  /// Returns whether the given language is any version of  C++.
+  static bool LanguageIsCPlusPlus(lldb::LanguageType language);
+
+  /// Returns whether the given language is Obj-C or Obj-C++.
+  static bool LanguageIsObjC(lldb::LanguageType language);
+
+  /// Returns whether the given language is any version of C, C++ or Obj-C.
+  static bool LanguageIsCFamily(lldb::LanguageType language);
+
+  /// Returns whether the given language supports exception breakpoints on
+  /// throw statements.
+  static bool SupportsExceptionBreakpointsOnThrow(lldb::LanguageType language);
+
+  /// Returns whether the given language supports exception breakpoints on
+  /// catch statements.
+  static bool SupportsExceptionBreakpointsOnCatch(lldb::LanguageType language);
+
+  /// Returns the keyword used for throw statements in the given language, e.g.
+  /// Python uses \b raise. Returns \b nullptr if the language is not supported.
+  static const char *GetThrowKeywordForLanguage(lldb::LanguageType language);
+
+  /// Returns the keyword used for catch statements in the given language, e.g.
+  /// Python uses \b except. Returns \b nullptr if the language is not
+  /// supported.
+  static const char *GetCatchKeywordForLanguage(lldb::LanguageType language);
 };
 
 } // namespace lldb
diff --git a/lldb/include/lldb/Target/Language.h b/lldb/include/lldb/Target/Language.h
index 83bf7635e369a5..41d8eeef469eab 100644
--- a/lldb/include/lldb/Target/Language.h
+++ b/lldb/include/lldb/Target/Language.h
@@ -371,6 +371,14 @@ class Language : public PluginInterface {
   /// a corresponding LanguageRuntime plugin.
   virtual bool SupportsExceptionBreakpointsOnCatch() const { return false; }
 
+  /// Returns the keyword used for throw statements in this language, e.g.
+  /// Python uses \b raise. Defaults to \b throw.
+  virtual llvm::StringRef GetThrowKeyword() const { return "throw"; }
+
+  /// Returns the keyword used for catch statements in this language, e.g.
+  /// Python uses \b except. Defaults to \b catch.
+  virtual llvm::StringRef GetCatchKeyword() const { return "catch"; }
+
 protected:
   // Classes that inherit from Language can see and modify these
 
diff --git a/lldb/source/API/SBLanguageRuntime.cpp b/lldb/source/API/SBLanguageRuntime.cpp
index d571f282fce03d..3926c57efedf54 100644
--- a/lldb/source/API/SBLanguageRuntime.cpp
+++ b/lldb/source/API/SBLanguageRuntime.cpp
@@ -26,3 +26,43 @@ SBLanguageRuntime::GetNameForLanguageType(lldb::LanguageType language) {
 
   return Language::GetNameForLanguageType(language);
 }
+
+bool SBLanguageRuntime::LanguageIsCPlusPlus(lldb::LanguageType language) {
+  return Language::LanguageIsCPlusPlus(language);
+}
+
+bool SBLanguageRuntime::LanguageIsObjC(lldb::LanguageType language) {
+  return Language::LanguageIsObjC(language);
+}
+
+bool SBLanguageRuntime::LanguageIsCFamily(lldb::LanguageType language) {
+  return Language::LanguageIsCFamily(language);
+}
+
+bool SBLanguageRuntime::SupportsExceptionBreakpointsOnThrow(
+    lldb::LanguageType language) {
+  if (Language *lang_plugin = Language::FindPlugin(language))
+    return lang_plugin->SupportsExceptionBreakpointsOnThrow();
+  return false;
+}
+
+bool SBLanguageRuntime::SupportsExceptionBreakpointsOnCatch(
+    lldb::LanguageType language) {
+  if (Language *lang_plugin = Language::FindPlugin(language))
+    return lang_plugin->SupportsExceptionBreakpointsOnCatch();
+  return false;
+}
+
+const char *
+SBLanguageRuntime::GetThrowKeywordForLanguage(lldb::LanguageType language) {
+  if (Language *lang_plugin = Language::FindPlugin(language))
+    return lang_plugin->GetThrowKeyword().data();
+  return nullptr;
+}
+
+const char *
+SBLanguageRuntime::GetCatchKeywordForLanguage(lldb::LanguageType language) {
+  if (Language *lang_plugin = Language::FindPlugin(language))
+    return lang_plugin->GetCatchKeyword().data();
+  return nullptr;
+}
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 0196aed819f2b4..569f0539877e56 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -58,10 +58,17 @@ DAP::DAP()
 
 DAP::~DAP() = default;
 
+/// Return string with first character capitalized.
+static std::string capitalize(llvm::StringRef str) {
+  if (str.empty())
+    return "";
+  return ((llvm::Twine)llvm::toUpper(str[0]) + str.drop_front()).str();
+}
+
 void DAP::PopulateExceptionBreakpoints() {
   llvm::call_once(init_exception_breakpoints_flag, [this]() {
     exception_breakpoints = std::vector<ExceptionBreakpoint> {};
-    
+
     if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) {
       exception_breakpoints->emplace_back("cpp_catch", "C++ Catch",
                                           lldb::eLanguageTypeC_plus_plus);
@@ -80,6 +87,46 @@ void DAP::PopulateExceptionBreakpoints() {
       exception_breakpoints->emplace_back("swift_throw", "Swift Throw",
                                           lldb::eLanguageTypeSwift);
     }
+    // Besides handling the hardcoded list of languages from above, we try to
+    // find any other languages that support exception breakpoints using the
+    // SB API.
+    for (int raw_lang = lldb::eLanguageTypeUnknown;
+         raw_lang < lldb::eNumLanguageTypes; ++raw_lang) {
+      lldb::LanguageType lang = static_cast<lldb::LanguageType>(raw_lang);
+
+      // We first discard any languages already handled above.
+      if (lldb::SBLanguageRuntime::LanguageIsCFamily(lang) ||
+          lang == lldb::eLanguageTypeSwift)
+        continue;
+
+      if (!lldb::SBDebugger::SupportsLanguage(lang))
+        continue;
+
+      const char *name = lldb::SBLanguageRuntime::GetNameForLanguageType(lang);
+      if (!name)
+        continue;
+      std::string raw_lang_name = name;
+      std::string capitalized_lang_name = capitalize(name);
+      const char *raw_throw_keyword =
+          lldb::SBLanguageRuntime::GetThrowKeywordForLanguage(lang);
+      const char *raw_catch_keyword =
+          lldb::SBLanguageRuntime::GetCatchKeywordForLanguage(lang);
+      std::string throw_keyword =
+          raw_throw_keyword ? raw_throw_keyword : "throw";
+      std::string catch_keyword =
+          raw_catch_keyword ? raw_catch_keyword : "catch";
+
+      if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnThrow(lang)) {
+        exception_breakpoints->emplace_back(
+            raw_lang_name + "_" + throw_keyword,
+            capitalized_lang_name + " " + capitalize(throw_keyword), lang);
+      }
+      if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnCatch(lang)) {
+        exception_breakpoints->emplace_back(
+            raw_lang_name + "_" + catch_keyword,
+            capitalized_lang_name + " " + capitalize(catch_keyword), lang);
+      }
+    }
     assert(!exception_breakpoints->empty() && "should not be empty");
   });
 }
@@ -514,6 +561,12 @@ llvm::Error DAP::RunInitCommands() {
   return llvm::Error::success();
 }
 
+llvm::Error DAP::RunPreInitCommands() {
+  if (!RunLLDBCommands("Running preInitCommands:", pre_init_commands))
+    return createRunLLDBCommandsErrorMessage("preInitCommands");
+  return llvm::Error::success();
+}
+
 llvm::Error DAP::RunPreRunCommands() {
   if (!RunLLDBCommands("Running preRunCommands:", pre_run_commands))
     return createRunLLDBCommandsErrorMessage("preRunCommands");
diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h
index 37e57d58968d90..57562a14983519 100644
--- a/lldb/tools/lldb-dap/DAP.h
+++ b/lldb/tools/lldb-dap/DAP.h
@@ -158,6 +158,7 @@ struct DAP {
   FunctionBreakpointMap function_breakpoints;
   std::optional<std::vector<ExceptionBreakpoint>> exception_breakpoints;
   llvm::once_flag init_exception_breakpoints_flag;
+  std::vector<std::string> pre_init_commands;
   std::vector<std::string> init_commands;
   std::vector<std::string> pre_run_commands;
   std::vector<std::string> post_run_commands;
@@ -246,6 +247,7 @@ struct DAP {
 
   llvm::Error RunAttachCommands(llvm::ArrayRef<std::string> attach_commands);
   llvm::Error RunLaunchCommands(llvm::ArrayRef<std::string> launch_commands);
+  llvm::Error RunPreInitCommands();
   llvm::Error RunInitCommands();
   llvm::Error RunPreRunCommands();
   void RunPostRunCommands();
diff --git a/lldb/tools/lldb-dap/Options.td b/lldb/tools/lldb-dap/Options.td
index 571967b232b4a2..d7b4a065abec01 100644
--- a/lldb/tools/lldb-dap/Options.td
+++ b/lldb/tools/lldb-dap/Options.td
@@ -43,3 +43,11 @@ def debugger_pid: S<"debugger-pid">,
 def repl_mode: S<"repl-mode">,
   MetaVarName<"<mode>">,
   HelpText<"The mode for handling repl evaluation requests, supported modes: variable, command, auto.">;
+
+def pre_init_command: S<"pre-init-command">,
+  MetaVarName<"<command>">,
+  HelpText<"A command to execute before the DAP initialization request and "
+    "right after a Debugger has been created.">;
+def: Separate<["-"], "c">,
+  Alias<pre_init_command>,
+  HelpText<"Alias for --pre-init-command">;
diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp
index b74474b9d383c0..b50d40acb51a21 100644
--- a/lldb/tools/lldb-dap/lldb-dap.cpp
+++ b/lldb/tools/lldb-dap/lldb-dap.cpp
@@ -1600,6 +1600,10 @@ void request_modules(const llvm::json::Object &request) {
 //   }]
 // }
 void request_initialize(const llvm::json::Object &request) {
+  llvm::json::Object response;
+  FillResponse(request, response);
+  llvm::json::Object body;
+
   auto log_cb = [](const char *buf, void *baton) -> void {
     g_dap.SendOutput(OutputType::Console, llvm::StringRef{buf});
   };
@@ -1611,6 +1615,13 @@ void request_initialize(const llvm::json::Object &request) {
   bool source_init_file = GetBoolean(arguments, "sourceInitFile", true);
 
   g_dap.debugger = lldb::SBDebugger::Create(source_init_file, log_cb, nullptr);
+  if (llvm::Error err = g_dap.RunPreInitCommands()) {
+    response["success"] = false;
+    EmplaceSafeString(response, "message", llvm::toString(std::move(err)));
+    g_dap.SendJSON(llvm::json::Value(std::move(response)));
+    return;
+  }
+
   g_dap.PopulateExceptionBreakpoints();
   auto cmd = g_dap.debugger.GetCommandInterpreter().AddMultiwordCommand(
       "lldb-dap", "Commands for managing lldb-dap.");
@@ -1630,9 +1641,6 @@ void request_initialize(const llvm::json::Object &request) {
   // process and more.
   g_dap.event_thread = std::thread(EventThreadFunction);
 
-  llvm::json::Object response;
-  FillResponse(request, response);
-  llvm::json::Object body;
   // The debug adapter supports the configurationDoneRequest.
   body.try_emplace("supportsConfigurationDoneRequest", true);
   // The debug adapter supports function breakpoints.
@@ -4318,6 +4326,11 @@ int main(int argc, char *argv[]) {
     g_dap.output.descriptor = StreamDescriptor::from_file(new_stdout_fd, false);
   }
 
+  for (const std::string &arg :
+       input_args.getAllArgValues(OPT_pre_init_command)) {
+    g_dap.pre_init_commands.push_back(arg);
+  }
+
   bool CleanExit = true;
   if (auto Err = g_dap.Loop()) {
     if (g_dap.log)

@walter-erquinigo
Copy link
Member Author

This is a screenshot of how it's looking for me on VSCode.
image

lldb/source/API/SBLanguageRuntime.cpp Outdated Show resolved Hide resolved
lldb/tools/lldb-dap/DAP.cpp Show resolved Hide resolved
lldb/tools/lldb-dap/DAP.cpp Show resolved Hide resolved
lldb/tools/lldb-dap/DAP.cpp Show resolved Hide resolved
Copy link
Member

@bulbazord bulbazord left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems okay to me, but I'm not exactly an expert when it comes to lldb-dap. You'll probably want to get sign-off from at least another person.

lldb/include/lldb/API/SBLanguageRuntime.h Outdated Show resolved Hide resolved
lldb/source/API/SBLanguageRuntime.cpp Outdated Show resolved Hide resolved
lldb/tools/lldb-dap/DAP.cpp Outdated Show resolved Hide resolved
…lly registered languages

First of all, this is done to support exceptions for the Mojo language, but it's done in a way that will benefit any other plugin language.

1. I added a new lldb-dap CLI argument (not DAP field) called `pre-init-commands`. These commands are executed before DAP initialization. The other `init-commands` are executed after DAP initialization. It's worth mentioning that the debug adapter returns to VSCode the list of supported exception breakpoints during DAP initialization, which means that I need to register the Mojo plugin before that initialization step, hence the need for `pre-init-commands`. In general, language plugins should be registered in that step, as they affect the capabilities of the debugger.
2. I added a set of APIs for lldb-dap to query information of each language related to exception breakpoints. E.g. whether a language supports throw or catch breakpoints, how the throw keyword is called in each particular language, etc.
3. I'm realizing that the Swift support for exception breakpoints in lldb-dap should have been implemented in this way, instead of hardcoding it.
@walter-erquinigo
Copy link
Member Author

This is a very benign change, so I'll be merging it. Happy to make change post-merge if anyone provides any feedback.

@walter-erquinigo walter-erquinigo merged commit 541f22e into llvm:main Jul 10, 2024
4 of 5 checks passed
@walter-erquinigo walter-erquinigo deleted the walter/exception-pr branch July 10, 2024 23:05
aaryanshukla pushed a commit to aaryanshukla/llvm-project that referenced this pull request Jul 14, 2024
llvm#97871)

…lly registered languages

First of all, this is done to support exceptions for the Mojo language,
but it's done in a way that will benefit any other plugin language.

1. I added a new lldb-dap CLI argument (not DAP field) called
`pre-init-commands`. These commands are executed before DAP
initialization. The other `init-commands` are executed after DAP
initialization. It's worth mentioning that the debug adapter returns to
VSCode the list of supported exception breakpoints during DAP
initialization, which means that I need to register the Mojo plugin
before that initialization step, hence the need for `pre-init-commands`.
In general, language plugins should be registered in that step, as they
affect the capabilities of the debugger.
2. I added a set of APIs for lldb-dap to query information of each
language related to exception breakpoints. E.g. whether a language
supports throw or catch breakpoints, how the throw keyword is called in
each particular language, etc.
3. I'm realizing that the Swift support for exception breakpoints in
lldb-dap should have been implemented in this way, instead of hardcoding
it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants