Author: Pavel Labath
Date: 2024-09-02T08:34:14+02:00
New Revision: dd5d73007240712957f2b633f795d9965afaadd6

URL: 
https://github.com/llvm/llvm-project/commit/dd5d73007240712957f2b633f795d9965afaadd6
DIFF: 
https://github.com/llvm/llvm-project/commit/dd5d73007240712957f2b633f795d9965afaadd6.diff

LOG: [lldb] Better matching of types in anonymous namespaces (#102111)

This patch extends TypeQuery matching to support anonymous namespaces. A
new flag is added to control the behavior. In the "strict" mode, the
query must match the type exactly -- all anonymous namespaces included.
The dynamic type resolver in the itanium abi (the motivating use case
for this) uses this flag, as it queries using the name from the
demangles, which includes anonymous namespaces.

This ensures we don't confuse a type with a same-named type in an
anonymous namespace. However, this does *not* ensure we don't confuse
two types in anonymous namespacs (in different CUs). To resolve this, we
would need to use a completely different lookup algorithm, which
probably also requires a DWARF extension.

In the "lax" mode (the default), the anonymous namespaces in the query
are optional, and this allows one search for the type using the usual
language rules (`::A` matches `::(anonymous namespace)::A`).

This patch also changes the type context computation algorithm in
DWARFDIE, so that it includes anonymous namespace information. This
causes a slight change in behavior: the algorithm previously stopped
computing the context after encountering an anonymous namespace, which
caused the outer namespaces to be ignored. This meant that a type like
`NS::(anonymous namespace)::A` would be (incorrectly) recognized as
`::A`). This can cause code depending on the old behavior to misbehave.
The fix is to specify all the enclosing namespaces in the query, or use
a non-exact match.

Added: 
    lldb/test/API/lang/cpp/dynamic-value/a.h
    lldb/test/API/lang/cpp/dynamic-value/anonymous-b.cpp

Modified: 
    lldb/include/lldb/Symbol/Type.h
    
lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp
    lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.cpp
    lldb/source/Symbol/Type.cpp
    lldb/test/API/lang/cpp/dynamic-value/Makefile
    lldb/test/API/lang/cpp/dynamic-value/TestDynamicValue.py
    lldb/test/API/lang/cpp/dynamic-value/pass-to-base.cpp
    lldb/test/API/lang/cpp/namespace/TestNamespace.py
    lldb/unittests/Symbol/TestType.cpp
    lldb/unittests/SymbolFile/DWARF/DWARFDIETest.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/include/lldb/Symbol/Type.h b/lldb/include/lldb/Symbol/Type.h
index 420307e0dbcf02..03d9f927997476 100644
--- a/lldb/include/lldb/Symbol/Type.h
+++ b/lldb/include/lldb/Symbol/Type.h
@@ -77,10 +77,13 @@ FLAGS_ENUM(TypeQueryOptions){
     /// If set, the query will ignore all Module entries in the type context,
     /// even for exact matches.
     e_ignore_modules = (1u << 2),
+    /// If set, all anonymous namespaces in the context must be matched exactly
+    /// by the pattern. Otherwise, superfluous namespaces are skipped.
+    e_strict_namespaces = (1u << 3),
     /// When true, the find types call should stop the query as soon as a 
single
     /// matching type is found. When false, the type query should find all
     /// matching types.
-    e_find_one = (1u << 3),
+    e_find_one = (1u << 4),
 };
 LLDB_MARK_AS_BITMASK_ENUM(TypeQueryOptions)
 
@@ -264,7 +267,22 @@ class TypeQuery {
   bool GetExactMatch() const { return (m_options & e_exact_match) != 0; }
 
   bool GetIgnoreModules() const { return (m_options & e_ignore_modules) != 0; }
-  void SetIgnoreModules() { m_options &= ~e_ignore_modules; }
+  void SetIgnoreModules(bool b) {
+    if (b)
+      m_options |= e_ignore_modules;
+    else
+      m_options &= ~e_ignore_modules;
+  }
+
+  bool GetStrictNamespaces() const {
+    return (m_options & e_strict_namespaces) != 0;
+  }
+  void SetStrictNamespaces(bool b) {
+    if (b)
+      m_options |= e_strict_namespaces;
+    else
+      m_options &= ~e_strict_namespaces;
+  }
 
   /// The \a m_context can be used in two ways: normal types searching with
   /// the context containing a stanadard declaration context for a type, or
@@ -279,7 +297,7 @@ class TypeQuery {
     if (b)
       m_options |= e_find_one;
     else
-      m_options &= (e_exact_match | e_find_one);
+      m_options &= ~e_find_one;
   }
 
   /// Access the internal compiler context array.

diff  --git 
a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp
 
b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp
index 7af768aad0bc19..4c547afe30fe81 100644
--- 
a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp
+++ 
b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp
@@ -90,6 +90,7 @@ TypeAndOrName ItaniumABILanguageRuntime::GetTypeInfo(
       TypeResults results;
       TypeQuery query(const_lookup_name.GetStringRef(),
                       TypeQueryOptions::e_exact_match |
+                          TypeQueryOptions::e_strict_namespaces |
                           TypeQueryOptions::e_find_one);
       if (module_sp) {
         module_sp->FindTypes(query, results);

diff  --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.cpp 
b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.cpp
index fb32e2adeb3fea..0a13c457a307ae 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFDIE.cpp
@@ -440,12 +440,6 @@ static void GetTypeLookupContextImpl(DWARFDIE die,
       continue;
     }
 
-    // If there is no name, then there is no need to look anything up for this
-    // DIE.
-    const char *name = die.GetName();
-    if (!name || !name[0])
-      return;
-
     // Add this DIE's contribution at the end of the chain.
     auto push_ctx = [&](CompilerContextKind kind, llvm::StringRef name) {
       context.push_back({kind, ConstString(name)});
@@ -471,7 +465,7 @@ static void GetTypeLookupContextImpl(DWARFDIE die,
       push_ctx(CompilerContextKind::Typedef, die.GetName());
       break;
     case DW_TAG_base_type:
-      push_ctx(CompilerContextKind::Builtin, name);
+      push_ctx(CompilerContextKind::Builtin, die.GetName());
       break;
     // If any of the tags below appear in the parent chain, stop the decl
     // context and return. Prior to these being in here, if a type existed in a

diff  --git a/lldb/source/Symbol/Type.cpp b/lldb/source/Symbol/Type.cpp
index eb321407e3734c..f7b44ade0da165 100644
--- a/lldb/source/Symbol/Type.cpp
+++ b/lldb/source/Symbol/Type.cpp
@@ -134,6 +134,20 @@ bool TypeQuery::ContextMatches(
     if (ctx == ctx_end)
       return false; // Pattern too long.
 
+    if (ctx->kind == CompilerContextKind::Namespace && ctx->name.IsEmpty()) {
+      // We're matching an anonymous namespace. These are optional, so we check
+      // if the pattern expects an anonymous namespace.
+      if (pat->name.IsEmpty() && (pat->kind & CompilerContextKind::Namespace) 
==
+                                     CompilerContextKind::Namespace) {
+        // Match, advance both iterators.
+        ++pat;
+      }
+      // Otherwise, only advance the context to skip over the anonymous
+      // namespace, and try matching again.
+      ++ctx;
+      continue;
+    }
+
     // See if there is a kind mismatch; they should have 1 bit in common.
     if ((ctx->kind & pat->kind) == CompilerContextKind())
       return false;
@@ -145,10 +159,16 @@ bool TypeQuery::ContextMatches(
     ++pat;
   }
 
-  // Skip over any remaining module entries if we were asked to do that.
-  while (GetIgnoreModules() && ctx != ctx_end &&
-         ctx->kind == CompilerContextKind::Module)
-    ++ctx;
+  // Skip over any remaining module and anonymous namespace entries if we were
+  // asked to do that.
+  auto should_skip = [this](const CompilerContext &ctx) {
+    if (ctx.kind == CompilerContextKind::Module)
+      return GetIgnoreModules();
+    if (ctx.kind == CompilerContextKind::Namespace && ctx.name.IsEmpty())
+      return !GetStrictNamespaces();
+    return false;
+  };
+  ctx = std::find_if_not(ctx, ctx_end, should_skip);
 
   // At this point, we have exhausted the pattern and we have a partial match 
at
   // least. If that's all we're looking for, we're done.
@@ -788,7 +808,13 @@ Type::GetTypeScopeAndBasename(llvm::StringRef name) {
     switch (pos.value()) {
     case ':':
       if (prev_is_colon && template_depth == 0) {
-        result.scope.push_back(name.slice(name_begin, pos.index() - 1));
+        llvm::StringRef scope_name = name.slice(name_begin, pos.index() - 1);
+        // The itanium demangler uses this string to represent anonymous
+        // namespaces. Convert it to a more language-agnostic form (which is
+        // also used in DWARF).
+        if (scope_name == "(anonymous namespace)")
+          scope_name = "";
+        result.scope.push_back(scope_name);
         name_begin = pos.index() + 1;
       }
       break;

diff  --git a/lldb/test/API/lang/cpp/dynamic-value/Makefile 
b/lldb/test/API/lang/cpp/dynamic-value/Makefile
index 2bba8e757f79b7..ce91dc63f473f5 100644
--- a/lldb/test/API/lang/cpp/dynamic-value/Makefile
+++ b/lldb/test/API/lang/cpp/dynamic-value/Makefile
@@ -1,3 +1,3 @@
-CXX_SOURCES := pass-to-base.cpp
+CXX_SOURCES := pass-to-base.cpp anonymous-b.cpp
 
 include Makefile.rules

diff  --git a/lldb/test/API/lang/cpp/dynamic-value/TestDynamicValue.py 
b/lldb/test/API/lang/cpp/dynamic-value/TestDynamicValue.py
index 60a2590e1559d3..e016168f047c19 100644
--- a/lldb/test/API/lang/cpp/dynamic-value/TestDynamicValue.py
+++ b/lldb/test/API/lang/cpp/dynamic-value/TestDynamicValue.py
@@ -170,7 +170,7 @@ def test_get_dynamic_vals(self):
         self.assertTrue(reallyA_value)
         reallyA_loc = int(reallyA_value.GetLocation(), 16)
 
-        # Finally continue to doSomething again, and make sure we get the 
right value for anotherA,
+        # Continue to doSomething again, and make sure we get the right value 
for anotherA,
         # which this time around is just an "A".
 
         threads = lldbutil.continue_to_breakpoint(process, do_something_bpt)
@@ -184,6 +184,19 @@ def test_get_dynamic_vals(self):
         self.assertEqual(anotherA_loc, reallyA_loc)
         self.assertEqual(anotherA_value.GetTypeName().find("B"), -1)
 
+        # Finally do the same with a B in an anonymous namespace.
+        threads = lldbutil.continue_to_breakpoint(process, do_something_bpt)
+        self.assertEqual(len(threads), 1)
+        thread = threads[0]
+
+        frame = thread.GetFrameAtIndex(0)
+        anotherA_value = frame.FindVariable("anotherA", use_dynamic)
+        self.assertTrue(anotherA_value)
+        self.assertIn("B", anotherA_value.GetTypeName())
+        anon_b_value = anotherA_value.GetChildMemberWithName("m_anon_b_value")
+        self.assertTrue(anon_b_value)
+        self.assertEqual(anon_b_value.GetValueAsSigned(), 47)
+
     def examine_value_object_of_this_ptr(
         self, this_static, this_dynamic, dynamic_location
     ):

diff  --git a/lldb/test/API/lang/cpp/dynamic-value/a.h 
b/lldb/test/API/lang/cpp/dynamic-value/a.h
new file mode 100644
index 00000000000000..708cbb79fee5cd
--- /dev/null
+++ b/lldb/test/API/lang/cpp/dynamic-value/a.h
@@ -0,0 +1,25 @@
+#ifndef A_H
+#define A_H
+
+#include <cstdio>
+#include <memory>
+
+class A {
+public:
+  A(int value) : m_a_value(value) {}
+  A(int value, A *client_A) : m_a_value(value), m_client_A(client_A) {}
+
+  virtual ~A() {}
+
+  virtual void doSomething(A &anotherA);
+
+  int Value() { return m_a_value; }
+
+private:
+  int m_a_value;
+  std::auto_ptr<A> m_client_A;
+};
+
+A *make_anonymous_B();
+
+#endif

diff  --git a/lldb/test/API/lang/cpp/dynamic-value/anonymous-b.cpp 
b/lldb/test/API/lang/cpp/dynamic-value/anonymous-b.cpp
new file mode 100644
index 00000000000000..755afcbf12a988
--- /dev/null
+++ b/lldb/test/API/lang/cpp/dynamic-value/anonymous-b.cpp
@@ -0,0 +1,13 @@
+#include "a.h"
+
+namespace {
+class B : public A {
+public:
+  B() : A(42) {}
+
+private:
+  int m_anon_b_value = 47;
+};
+} // namespace
+
+A *make_anonymous_B() { return new B(); }

diff  --git a/lldb/test/API/lang/cpp/dynamic-value/pass-to-base.cpp 
b/lldb/test/API/lang/cpp/dynamic-value/pass-to-base.cpp
index 2bccf3303823c1..be763390cc6f90 100644
--- a/lldb/test/API/lang/cpp/dynamic-value/pass-to-base.cpp
+++ b/lldb/test/API/lang/cpp/dynamic-value/pass-to-base.cpp
@@ -1,5 +1,10 @@
-#include <stdio.h>
-#include <memory>
+#include "a.h"
+
+void A::doSomething(A &anotherA) {
+  printf("In A %p doing something with %d.\n", this, m_a_value);
+  int tmp_value = anotherA.Value();
+  printf("Also have another A at %p: %d.\n", &anotherA, tmp_value); // Break 
here in doSomething.
+}
 
 class Extra
 {
@@ -11,33 +16,6 @@ class Extra
   int m_extra_two;
 };
 
-class A
-{
-public:
-  A(int value) : m_a_value (value) {}
-  A(int value, A* client_A) : m_a_value (value), m_client_A (client_A) {}
-
-  virtual ~A() {}
-
-  virtual void
-  doSomething (A &anotherA)
-  {
-    printf ("In A %p doing something with %d.\n", this, m_a_value);
-    int tmp_value = anotherA.Value();
-    printf ("Also have another A at %p: %d.\n", &anotherA, tmp_value); // 
Break here in doSomething.
-  }
-
-  int 
-  Value()
-  {
-    return m_a_value;
-  }
-
-private:
-  int m_a_value;
-  std::auto_ptr<A> m_client_A;
-};
-
 class B : public Extra, public virtual A
 {
 public:
@@ -65,5 +43,7 @@ main (int argc, char **argv)
   A reallyA (500);
   myB.doSomething (reallyA);  // Break here and get real address of reallyA.
 
+  myB.doSomething(*make_anonymous_B());
+
   return 0;
 }

diff  --git a/lldb/test/API/lang/cpp/namespace/TestNamespace.py 
b/lldb/test/API/lang/cpp/namespace/TestNamespace.py
index 84891b322180c3..8b013d928f9ca5 100644
--- a/lldb/test/API/lang/cpp/namespace/TestNamespace.py
+++ b/lldb/test/API/lang/cpp/namespace/TestNamespace.py
@@ -208,6 +208,12 @@ def test_with_run_command(self):
             patterns=[" = 3"],
         )
 
+        # Search for a type in an anonymous namespace, both with and without 
the
+        # namespace prefix.
+        self.expect("type lookup -- my_uint_t", substrs=["unsigned int"])
+        self.expect("type lookup -- (anonymous namespace)::my_uint_t",
+                    substrs=["unsigned int"])
+
         # rdar://problem/8660275
         # test/namespace: 'expression -- i+j' not working
         # This has been fixed.

diff  --git a/lldb/unittests/Symbol/TestType.cpp 
b/lldb/unittests/Symbol/TestType.cpp
index e4b56b9ff02f7c..e3bb2cf6e69e2a 100644
--- a/lldb/unittests/Symbol/TestType.cpp
+++ b/lldb/unittests/Symbol/TestType.cpp
@@ -16,6 +16,7 @@
 
 using namespace lldb;
 using namespace lldb_private;
+using testing::ElementsAre;
 using testing::Not;
 
 TEST(Type, GetTypeScopeAndBasename) {
@@ -59,8 +60,33 @@ MATCHER_P(MatchesIgnoringModules, pattern, "") {
   TypeQuery query(pattern, TypeQueryOptions::e_ignore_modules);
   return query.ContextMatches(arg);
 }
+MATCHER_P(MatchesWithStrictNamespaces, pattern, "") {
+  TypeQuery query(pattern, TypeQueryOptions::e_strict_namespaces);
+  return query.ContextMatches(arg);
+}
 } // namespace
 
+TEST(Type, TypeQueryFlags) {
+  TypeQuery q("foo", e_none);
+  auto get = [](const TypeQuery &q) -> std::vector<bool> {
+    return {q.GetFindOne(), q.GetExactMatch(), q.GetModuleSearch(),
+            q.GetIgnoreModules(), q.GetStrictNamespaces()};
+  };
+  EXPECT_THAT(get(q), ElementsAre(false, false, false, false, false));
+
+  q.SetFindOne(true);
+  EXPECT_THAT(get(q), ElementsAre(true, false, false, false, false));
+
+  q.SetIgnoreModules(true);
+  EXPECT_THAT(get(q), ElementsAre(true, false, false, true, false));
+
+  q.SetStrictNamespaces(true);
+  EXPECT_THAT(get(q), ElementsAre(true, false, false, true, true));
+
+  q.SetIgnoreModules(false);
+  EXPECT_THAT(get(q), ElementsAre(true, false, false, false, true));
+}
+
 TEST(Type, CompilerContextPattern) {
   auto make_module = [](llvm::StringRef name) {
     return CompilerContext(CompilerContextKind::Module, ConstString(name));
@@ -103,6 +129,10 @@ TEST(Type, CompilerContextPattern) {
       (std::vector{make_module("A"), make_module("B"), make_class("C")}),
       Matches(
           std::vector{make_module("A"), make_module("B"), 
make_any_type("C")}));
+  EXPECT_THAT((std::vector{make_module("A"), make_module("B"),
+                           make_namespace(""), make_class("C")}),
+              Matches(std::vector{make_module("A"), make_module("B"),
+                                  make_any_type("C")}));
   EXPECT_THAT(
       (std::vector{make_module("A"), make_module("B"), make_enum("C2")}),
       Not(Matches(std::vector{make_module("A"), make_module("B"),
@@ -111,4 +141,30 @@ TEST(Type, CompilerContextPattern) {
               Matches(std::vector{make_class("C")}));
   EXPECT_THAT((std::vector{make_namespace("NS"), make_class("C")}),
               Not(Matches(std::vector{make_any_type("C")})));
+
+  EXPECT_THAT((std::vector{make_namespace(""), make_class("C")}),
+              Matches(std::vector{make_class("C")}));
+  EXPECT_THAT((std::vector{make_namespace(""), make_class("C")}),
+              Not(MatchesWithStrictNamespaces(std::vector{make_class("C")})));
+  EXPECT_THAT((std::vector{make_namespace(""), make_class("C")}),
+              Matches(std::vector{make_namespace(""), make_class("C")}));
+  EXPECT_THAT((std::vector{make_namespace(""), make_class("C")}),
+              MatchesWithStrictNamespaces(
+                  std::vector{make_namespace(""), make_class("C")}));
+  EXPECT_THAT((std::vector{make_class("C")}),
+              Not(Matches(std::vector{make_namespace(""), make_class("C")})));
+  EXPECT_THAT((std::vector{make_class("C")}),
+              Not(MatchesWithStrictNamespaces(
+                  std::vector{make_namespace(""), make_class("C")})));
+  EXPECT_THAT((std::vector{make_namespace(""), make_namespace("NS"),
+                           make_namespace(""), make_class("C")}),
+              Matches(std::vector{make_namespace("NS"), make_class("C")}));
+  EXPECT_THAT(
+      (std::vector{make_namespace(""), make_namespace(""), 
make_namespace("NS"),
+                   make_namespace(""), make_namespace(""), make_class("C")}),
+      Matches(std::vector{make_namespace("NS"), make_class("C")}));
+  EXPECT_THAT((std::vector{make_module("A"), make_namespace("NS"),
+                           make_namespace(""), make_class("C")}),
+              MatchesIgnoringModules(
+                  std::vector{make_namespace("NS"), make_class("C")}));
 }

diff  --git a/lldb/unittests/SymbolFile/DWARF/DWARFDIETest.cpp 
b/lldb/unittests/SymbolFile/DWARF/DWARFDIETest.cpp
index 122b7de7516b6d..1e4c8f3ba07787 100644
--- a/lldb/unittests/SymbolFile/DWARF/DWARFDIETest.cpp
+++ b/lldb/unittests/SymbolFile/DWARF/DWARFDIETest.cpp
@@ -222,6 +222,9 @@ TEST(DWARFDIETest, GetContext) {
           Attributes:
             - Attribute:       DW_AT_name
               Form:            DW_FORM_string
+        - Code:            0x4
+          Tag:             DW_TAG_namespace
+          Children:        DW_CHILDREN_yes
   debug_info:
     - Version:         4
       AddrSize:        8
@@ -235,6 +238,11 @@ TEST(DWARFDIETest, GetContext) {
         - AbbrCode:        0x3
           Values:
             - CStr:            STRUCT
+        - AbbrCode:        0x4
+        - AbbrCode:        0x3
+          Values:
+            - CStr:            STRUCT
+        - AbbrCode:        0x0
         - AbbrCode:        0x0
         - AbbrCode:        0x0
 )";
@@ -245,15 +253,17 @@ TEST(DWARFDIETest, GetContext) {
   DWARFUnit *unit = symbol_file->DebugInfo().GetUnitAtIndex(0);
   ASSERT_TRUE(unit);
 
-  auto make_namespace = [](llvm::StringRef name) {
+  auto make_namespace = [](const char *name) {
     return CompilerContext(CompilerContextKind::Namespace, ConstString(name));
   };
-  auto make_struct = [](llvm::StringRef name) {
+  auto make_struct = [](const char *name) {
     return CompilerContext(CompilerContextKind::ClassOrStruct,
                            ConstString(name));
   };
   DWARFDIE struct_die = unit->DIE().GetFirstChild().GetFirstChild();
   ASSERT_TRUE(struct_die);
+  DWARFDIE anon_struct_die = struct_die.GetSibling().GetFirstChild();
+  ASSERT_TRUE(anon_struct_die);
   EXPECT_THAT(
       struct_die.GetDeclContext(),
       testing::ElementsAre(make_namespace("NAMESPACE"), 
make_struct("STRUCT")));
@@ -263,6 +273,18 @@ TEST(DWARFDIETest, GetContext) {
   EXPECT_THAT(struct_die.GetDWARFDeclContext(),
               DWARFDeclContext({{DW_TAG_structure_type, "STRUCT"},
                                 {DW_TAG_namespace, "NAMESPACE"}}));
+  EXPECT_THAT(anon_struct_die.GetDeclContext(),
+              testing::ElementsAre(make_namespace("NAMESPACE"),
+                                   make_namespace(nullptr),
+                                   make_struct("STRUCT")));
+  EXPECT_THAT(anon_struct_die.GetTypeLookupContext(),
+              testing::ElementsAre(make_namespace("NAMESPACE"),
+                                   make_namespace(nullptr),
+                                   make_struct("STRUCT")));
+  EXPECT_THAT(anon_struct_die.GetDWARFDeclContext(),
+              DWARFDeclContext({{DW_TAG_structure_type, "STRUCT"},
+                                {DW_TAG_namespace, nullptr},
+                                {DW_TAG_namespace, "NAMESPACE"}}));
 }
 
 TEST(DWARFDIETest, GetContextInFunction) {


        
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to