https://github.com/charles-zablit updated 
https://github.com/llvm/llvm-project/pull/212521

>From c9142af03257a4f0d278e29a25ed1a4fc0b9e0f6 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Tue, 28 Jul 2026 16:14:51 +0100
Subject: [PATCH 1/2] [lldb] Fix C++ expression evaluation with the MS C++ ABI

IRForTarget mishandled the MS ABI: it could match a dynamic
initializer function instead of the result variable, and
__cxa_atexit stripping missed plain atexit calls, leaving
dangling references to freed JIT memory.
---
 .../ExpressionParser/Clang/IRForTarget.cpp    | 33 +++++++++++++++++--
 1 file changed, 30 insertions(+), 3 deletions(-)

diff --git a/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp 
b/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp
index adfb5bd1eca94..114e44b34888c 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp
@@ -180,6 +180,11 @@ bool IRForTarget::CreateResultVariable(llvm::Function 
&llvm_function) {
     // on Windows, so let's only check for Itanium guard variables.
     bool is_guard_var = isGuardVariableSymbol(result_name, /*MS ABI*/ false);
 
+    // Skip non-globals, e.g. the MS ABI dynamic initializer function that
+    // shares a mangled name with the result variable.
+    if (!isa<GlobalVariable>(value_symbol.second))
+      continue;
+
     if (result_name.contains("$__lldb_expr_result_ptr") && !is_guard_var) {
       found_result = true;
       m_result_is_pointer = true;
@@ -1181,6 +1186,7 @@ bool IRForTarget::HandleObjCClass(Value 
*classlist_reference) {
 
 bool IRForTarget::RemoveCXAAtExit(BasicBlock &basic_block) {
   std::vector<CallInst *> calls_to_remove;
+  llvm::SmallVector<llvm::Function *, 2> dead_atexit_callbacks;
 
   for (Instruction &inst : basic_block) {
     CallInst *call = dyn_cast<CallInst>(&inst);
@@ -1193,21 +1199,42 @@ bool IRForTarget::RemoveCXAAtExit(BasicBlock 
&basic_block) {
 
     llvm::Function *func = call->getCalledFunction();
 
-    if (func && func->getName() == "__cxa_atexit")
+    // Itanium ABI uses __cxa_atexit; MS ABI uses plain atexit.
+    if (func &&
+        (func->getName() == "__cxa_atexit" || func->getName() == "atexit"))
       remove = true;
 
     llvm::Value *val = call->getCalledOperand();
 
-    if (val && val->getName() == "__cxa_atexit")
+    if (val && (val->getName() == "__cxa_atexit" || val->getName() == 
"atexit"))
       remove = true;
 
-    if (remove)
+    if (remove) {
+      // MS ABI callback thunks (mangled "??__F...") reference the static
+      // they destroy; track them to clear once the call is gone.
+      if (call->arg_size() > 0)
+        if (auto *cb = dyn_cast<llvm::Function>(
+                call->getArgOperand(0)->stripPointerCasts()))
+          if (cb->hasInternalLinkage() && cb->getName().starts_with("??__"))
+            dead_atexit_callbacks.push_back(cb);
       calls_to_remove.push_back(call);
+    }
   }
 
   for (CallInst *ci : calls_to_remove)
     ci->eraseFromParent();
 
+  // Clear the body of any orphaned atexit-destructor thunk so it no longer
+  // references the statics it used to destroy.
+  for (llvm::Function *cb : dead_atexit_callbacks) {
+    if (!cb->use_empty())
+      continue;
+    cb->deleteBody();
+    llvm::BasicBlock *entry =
+        llvm::BasicBlock::Create(cb->getContext(), "", cb);
+    llvm::ReturnInst::Create(cb->getContext(), entry);
+  }
+
   return true;
 }
 

>From bde6793573b865c7f86a97a5417c0c5744326218 Mon Sep 17 00:00:00 2001
From: Charles Zablit <[email protected]>
Date: Wed, 29 Jul 2026 15:19:04 +0100
Subject: [PATCH 2/2] add test

---
 .../result-with-destructor/Makefile           |  3 ++
 .../TestResultWithDestructor.py               | 42 +++++++++++++++++++
 .../result-with-destructor/main.cpp           | 25 +++++++++++
 3 files changed, 70 insertions(+)
 create mode 100644 
lldb/test/API/commands/expression/result-with-destructor/Makefile
 create mode 100644 
lldb/test/API/commands/expression/result-with-destructor/TestResultWithDestructor.py
 create mode 100644 
lldb/test/API/commands/expression/result-with-destructor/main.cpp

diff --git a/lldb/test/API/commands/expression/result-with-destructor/Makefile 
b/lldb/test/API/commands/expression/result-with-destructor/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/commands/expression/result-with-destructor/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git 
a/lldb/test/API/commands/expression/result-with-destructor/TestResultWithDestructor.py
 
b/lldb/test/API/commands/expression/result-with-destructor/TestResultWithDestructor.py
new file mode 100644
index 0000000000000..ec08be0cf8d95
--- /dev/null
+++ 
b/lldb/test/API/commands/expression/result-with-destructor/TestResultWithDestructor.py
@@ -0,0 +1,42 @@
+"""
+Test evaluating expressions whose result is an rvalue with a non-trivial
+destructor.
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+    @no_debug_info_test
+    def test(self):
+        self.build()
+        target, process, _, _ = lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.cpp")
+        )
+
+        # An lvalue result is turned into a '$__lldb_expr_result_ptr' and needs
+        # no destructor.
+        self.expect_expr("f.x", result_type="int", result_value="42")
+
+        # An rvalue result is turned into a static '$__lldb_expr_result' whose
+        # destructor gets registered with atexit/__cxa_atexit.
+        self.expect_expr(
+            "make_foo()",
+            result_type="Foo",
+            result_children=[ValueCheck(name="x", value="42")],
+        )
+        self.expect_expr(
+            "make_widget()",
+            result_type="Widget",
+            result_children=[ValueCheck(name="x", value="47")],
+        )
+
+        # Make sure evaluating the expressions didn't leave a dangling
+        # destructor registered in the inferior.
+        target.DeleteAllBreakpoints()
+        process.Continue()
+        self.assertState(process.GetState(), lldb.eStateExited)
+        self.assertEqual(process.GetExitStatus(), 0)
diff --git a/lldb/test/API/commands/expression/result-with-destructor/main.cpp 
b/lldb/test/API/commands/expression/result-with-destructor/main.cpp
new file mode 100644
index 0000000000000..2252259d4e0f1
--- /dev/null
+++ b/lldb/test/API/commands/expression/result-with-destructor/main.cpp
@@ -0,0 +1,25 @@
+struct Foo {
+  Foo() : x(42) {}
+  ~Foo() {}
+  int x;
+};
+
+struct Widget {
+  Widget() : x(47) {}
+  ~Widget() {}
+  int x;
+};
+
+Foo make_foo() { return Foo(); }
+
+Widget make_widget() { return Widget(); }
+
+void bar() {
+  static Foo f;
+  // break here
+}
+
+int main() {
+  bar();
+  return 0;
+}

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to