Make the stop-on-sharedlibrary-events setting work.

The StopInfoBreakpoint::PerformAction was overriding the synchronous
breakpoint's ShouldStop report.  Fix that and add a test.

This fixes two bugs in the original submission:
1) Actually generate both dylibs by including the second one in the Makefile
2) Don't ask synchronous callbacks for their opinion on whether to stop
   in the async context, that info is taken care of by recording the m_should_stop
   on entry to PerformAction.

Differential Revision: https://reviews.llvm.org/D98914

GitOrigin-RevId: c8faa8c2669c1867ef6ac33466f219a39d5faaa7
diff --git a/include/lldb/Breakpoint/BreakpointLocation.h b/include/lldb/Breakpoint/BreakpointLocation.h
index 4e1c57a..a12696b 100644
--- a/include/lldb/Breakpoint/BreakpointLocation.h
+++ b/include/lldb/Breakpoint/BreakpointLocation.h
@@ -230,6 +230,12 @@
   ///     \b true if the target should stop at this breakpoint and \b
   ///     false not.
   bool InvokeCallback(StoppointCallbackContext *context);
+  
+  /// Report whether the callback for this location is synchronous or not.
+  ///
+  /// \return
+  ///     \b true if the callback is synchronous and \b false if not.
+  bool IsCallbackSynchronous();
 
   /// Returns whether we should resolve Indirect functions in setting the
   /// breakpoint site for this location.
diff --git a/source/Breakpoint/BreakpointLocation.cpp b/source/Breakpoint/BreakpointLocation.cpp
index d3d6ea0..617b89b 100644
--- a/source/Breakpoint/BreakpointLocation.cpp
+++ b/source/Breakpoint/BreakpointLocation.cpp
@@ -195,6 +195,13 @@
     return m_owner.InvokeCallback(context, GetID());
 }
 
+bool BreakpointLocation::IsCallbackSynchronous() {
+  if (m_options_up != nullptr && m_options_up->HasCallback())
+    return m_options_up->IsCallbackSynchronous();
+  else
+    return m_owner.GetOptions()->IsCallbackSynchronous();
+}
+
 void BreakpointLocation::SetCallback(BreakpointHitCallback callback,
                                      void *baton, bool is_synchronous) {
   // The default "Baton" class will keep a copy of "baton" and won't free or
diff --git a/source/Breakpoint/BreakpointOptions.cpp b/source/Breakpoint/BreakpointOptions.cpp
index 2fdb53e..920f843 100644
--- a/source/Breakpoint/BreakpointOptions.cpp
+++ b/source/Breakpoint/BreakpointOptions.cpp
@@ -453,8 +453,6 @@
                                           : nullptr,
                       context, break_id, break_loc_id);
     } else if (IsCallbackSynchronous()) {
-      // If a synchronous callback is called at async time, it should not say
-      // to stop.
       return false;
     }
   }
diff --git a/source/Target/StopInfo.cpp b/source/Target/StopInfo.cpp
index 7e830c6..95efd0a 100644
--- a/source/Target/StopInfo.cpp
+++ b/source/Target/StopInfo.cpp
@@ -305,6 +305,20 @@
           // location said we should stop. But that's better than not running
           // all the callbacks.
 
+          // There's one other complication here.  We may have run an async
+          // breakpoint callback that said we should stop.  We only want to
+          // override that if another breakpoint action says we shouldn't 
+          // stop.  If nobody else has an opinion, then we should stop if the
+          // async callback says we should.  An example of this is the async
+          // shared library load notification breakpoint and the setting
+          // stop-on-sharedlibrary-events.
+          // We'll keep the async value in async_should_stop, and track whether
+          // anyone said we should NOT stop in actually_said_continue.
+          bool async_should_stop = false;
+          if (m_should_stop_is_valid)
+            async_should_stop = m_should_stop;
+          bool actually_said_continue = false;
+
           m_should_stop = false;
 
           // We don't select threads as we go through them testing breakpoint
@@ -422,9 +436,10 @@
 
             bool precondition_result =
                 bp_loc_sp->GetBreakpoint().EvaluatePrecondition(context);
-            if (!precondition_result)
+            if (!precondition_result) {
+              actually_said_continue = true;
               continue;
-
+            }
             // Next run the condition for the breakpoint.  If that says we
             // should stop, then we'll run the callback for the breakpoint.  If
             // the callback says we shouldn't stop that will win.
@@ -462,6 +477,7 @@
                   // the condition fails. We've already bumped it by the time
                   // we get here, so undo the bump:
                   bp_loc_sp->UndoBumpHitCount();
+                  actually_said_continue = true;
                   continue;
                 }
               }
@@ -494,16 +510,22 @@
             // When we figure out how to nest breakpoint hits then this will
             // change.
 
-            Debugger &debugger = thread_sp->CalculateTarget()->GetDebugger();
-            bool old_async = debugger.GetAsyncExecution();
-            debugger.SetAsyncExecution(true);
+            // Don't run async callbacks in PerformAction.  They have already
+            // been taken into account with async_should_stop.
+            if (!bp_loc_sp->IsCallbackSynchronous()) {
+              Debugger &debugger = thread_sp->CalculateTarget()->GetDebugger();
+              bool old_async = debugger.GetAsyncExecution();
+              debugger.SetAsyncExecution(true);
 
-            callback_says_stop = bp_loc_sp->InvokeCallback(&context);
+              callback_says_stop = bp_loc_sp->InvokeCallback(&context);
 
-            debugger.SetAsyncExecution(old_async);
+              debugger.SetAsyncExecution(old_async);
 
-            if (callback_says_stop && auto_continue_says_stop)
-              m_should_stop = true;
+              if (callback_says_stop && auto_continue_says_stop)
+                m_should_stop = true;
+              else
+                actually_said_continue = true;
+            }
                   
             // If we are going to stop for this breakpoint, then remove the
             // breakpoint.
@@ -517,9 +539,15 @@
             // here.
             if (HasTargetRunSinceMe()) {
               m_should_stop = false;
+              actually_said_continue = true;
               break;
             }
           }
+          // At this point if nobody actually told us to continue, we should
+          // give the async breakpoint callback a chance to weigh in:
+          if (!actually_said_continue && !m_should_stop) {
+            m_should_stop = async_should_stop;
+          }
         }
         // We've figured out what this stop wants to do, so mark it as valid so
         // we don't compute it again.
diff --git a/test/API/functionalities/stop-on-sharedlibrary-load/Makefile b/test/API/functionalities/stop-on-sharedlibrary-load/Makefile
new file mode 100644
index 0000000..4abcab8
--- /dev/null
+++ b/test/API/functionalities/stop-on-sharedlibrary-load/Makefile
@@ -0,0 +1,16 @@
+CXX_SOURCES := main.cpp
+USE_LIBDL := 1
+
+a.out: lib_a lib_b
+
+include Makefile.rules
+
+lib_a:
+	$(MAKE) -f $(MAKEFILE_RULES) \
+		DYLIB_ONLY=YES DYLIB_CXX_SOURCES=a.cpp DYLIB_NAME=load_a
+
+lib_b:
+	$(MAKE) -f $(MAKEFILE_RULES) \
+		DYLIB_ONLY=YES DYLIB_CXX_SOURCES=b.cpp DYLIB_NAME=load_b
+
+
diff --git a/test/API/functionalities/stop-on-sharedlibrary-load/TestStopOnSharedlibraryEvents.py b/test/API/functionalities/stop-on-sharedlibrary-load/TestStopOnSharedlibraryEvents.py
new file mode 100644
index 0000000..81f00f2
--- /dev/null
+++ b/test/API/functionalities/stop-on-sharedlibrary-load/TestStopOnSharedlibraryEvents.py
@@ -0,0 +1,99 @@
+""" Test that stop-on-sharedlibrary-events works and cooperates with breakpoints. """
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+class TestStopOnSharedlibraryEvents(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    @skipIfRemote
+    @skipIfWindows
+    @no_debug_info_test
+    def test_stopping_breakpoints(self):
+        self.do_test()
+
+    @skipIfRemote
+    @skipIfWindows
+    @no_debug_info_test
+    def test_auto_continue(self):
+        def auto_continue(bkpt):
+            bkpt.SetAutoContinue(True)
+        self.do_test(auto_continue)
+
+    @skipIfRemote
+    @skipIfWindows
+    @no_debug_info_test
+    def test_failing_condition(self):
+        def condition(bkpt):
+            bkpt.SetCondition("1 == 2")
+        self.do_test(condition)
+        
+    @skipIfRemote
+    @skipIfWindows
+    @no_debug_info_test
+    def test_continue_callback(self):
+        def bkpt_callback(bkpt):
+            bkpt.SetScriptCallbackBody("return False")
+        self.do_test(bkpt_callback)
+
+    def do_test(self, bkpt_modifier = None):
+        self.build()
+        main_spec = lldb.SBFileSpec("main.cpp")
+        # Launch and stop before the dlopen call.
+        target, process, thread, _ = lldbutil.run_to_source_breakpoint(self,
+                                                                  "// Set a breakpoint here", main_spec)
+
+        # Now turn on shared library events, continue and make sure we stop for the event.
+        self.runCmd("settings set target.process.stop-on-sharedlibrary-events 1")
+        self.addTearDownHook(lambda: self.runCmd(
+            "settings set target.process.stop-on-sharedlibrary-events 0"))
+
+        # Since I don't know how to check that we are at the "right place" to stop for
+        # shared library events, make an breakpoint after the load is done and
+        # make sure we don't stop there:
+        backstop_bkpt_1 = target.BreakpointCreateBySourceRegex("Set another here - we should not hit this one", main_spec)
+        self.assertGreater(backstop_bkpt_1.GetNumLocations(), 0, "Set our second breakpoint")
+        
+        process.Continue() 
+        self.assertEqual(process.GetState(), lldb.eStateStopped, "We didn't stop for the load")
+        self.assertEqual(backstop_bkpt_1.GetHitCount(), 0, "Hit our backstop breakpoint")
+        
+        # We should be stopped after the library is loaded, check that:
+        found_it = False
+        for module in target.modules:
+            if module.file.basename.find("load_a") > -1:
+                found_it = True
+                break
+        self.assertTrue(found_it, "Found the loaded module.")
+
+        # Now capture the place where we stopped so we can set a breakpoint and make
+        # sure the breakpoint there works correctly:
+        load_address = process.GetSelectedThread().frames[0].addr
+        load_bkpt = target.BreakpointCreateBySBAddress(load_address)
+        self.assertGreater(load_bkpt.GetNumLocations(), 0, "Set the load breakpoint")
+
+        backstop_bkpt_1.SetEnabled(False)
+
+        backstop_bkpt_2 = target.BreakpointCreateBySourceRegex("Set a third here - we should not hit this one", main_spec)
+        self.assertGreater(backstop_bkpt_2.GetNumLocations(), 0, "Set our third breakpoint")
+            
+        if bkpt_modifier == None:
+            process.Continue()
+            self.assertEqual(process.GetState(), lldb.eStateStopped, "We didn't stop for the load")
+            self.assertEqual(backstop_bkpt_2.GetHitCount(), 0, "Hit our backstop breakpoint")
+            self.assertEqual(thread.stop_reason, lldb.eStopReasonBreakpoint, "We attributed the stop to the breakpoint")
+            self.assertEqual(load_bkpt.GetHitCount(), 1, "We hit our breakpoint at the load address")
+        else:
+            bkpt_modifier(load_bkpt)
+            process.Continue()
+            self.assertEqual(process.GetState(), lldb.eStateStopped, "We didn't stop")
+            self.assertTrue(thread.IsValid(), "Our thread was no longer valid.")
+            self.assertEqual(thread.stop_reason, lldb.eStopReasonBreakpoint, "We didn't hit some breakpoint")
+            self.assertEqual(backstop_bkpt_2.GetHitCount(), 1, "We continued to the right breakpoint")
+
+        
+        
+        
+        
diff --git a/test/API/functionalities/stop-on-sharedlibrary-load/a.cpp b/test/API/functionalities/stop-on-sharedlibrary-load/a.cpp
new file mode 100644
index 0000000..b7b702c
--- /dev/null
+++ b/test/API/functionalities/stop-on-sharedlibrary-load/a.cpp
@@ -0,0 +1,6 @@
+extern int a_has_a_function();
+
+int
+a_has_a_function() {
+  return 10;
+}
diff --git a/test/API/functionalities/stop-on-sharedlibrary-load/b.cpp b/test/API/functionalities/stop-on-sharedlibrary-load/b.cpp
new file mode 100644
index 0000000..5a347e6
--- /dev/null
+++ b/test/API/functionalities/stop-on-sharedlibrary-load/b.cpp
@@ -0,0 +1,6 @@
+extern int b_has_a_function();
+
+int
+b_has_a_function() {
+  return 100;
+}
diff --git a/test/API/functionalities/stop-on-sharedlibrary-load/main.cpp b/test/API/functionalities/stop-on-sharedlibrary-load/main.cpp
new file mode 100644
index 0000000..96b1e1d
--- /dev/null
+++ b/test/API/functionalities/stop-on-sharedlibrary-load/main.cpp
@@ -0,0 +1,27 @@
+#include "dylib.h"
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int main(int argc, char const *argv[]) {
+  const char *a_name = "load_a";
+  void *a_dylib_handle = NULL;
+
+  a_dylib_handle = dylib_open(a_name); // Set a breakpoint here.
+  if (a_dylib_handle == NULL) { // Set another here - we should not hit this one
+    fprintf(stderr, "%s\n", dylib_last_error());
+    exit(1);
+  }
+
+  const char *b_name = "load_b";
+  void *b_dylib_handle = NULL;
+
+  b_dylib_handle = dylib_open(b_name);
+  if (b_dylib_handle == NULL) { // Set a third here - we should not hit this one
+    fprintf(stderr, "%s\n", dylib_last_error());
+    exit(1);
+  }
+
+  return 0;
+}