[Lldb-commits] [lldb] a8350ce - [lldb] Add support for using variables with C++ keywords names in non-C++ expressions

Raphael Isemann via lldb-commits lldb-commits at lists.llvm.org
Mon Nov 16 07:05:04 PST 2020


Author: Raphael Isemann
Date: 2020-11-16T16:04:44+01:00
New Revision: a8350ce79d167643b53d06e2167535d24fe68dc3

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

LOG: [lldb] Add support for using variables with C++ keywords names in non-C++ expressions

LLDB is currently always activating C++ when parsing expressions as LLDB itself
is using C++ features when creating the final AST that will be codegen'd
(specifically, references to variables, namespaces and using declarations are
used).

This is causing problems for users that have variables in non-C++ programs (e.g.
plain C or Objective-C) that have names which are keywords in C++. Expressions
referencing those variables fail to parse as LLDB's Clang parser thinks those
identifiers are C++ keywords and not identifiers that may belong to a
declaration.

We can't just disable C++ in the expression parser for those situations as
replacing the functionality of the injected C++ code isn't trivial. So this
patch is just disabling most keywords that are exclusive to C++ in LLDB's Clang
parser when we are in a non-C++ expression. There are a few keywords we can't
disable for now:

* `using` as that's currently used in some situations to inject variables into the expression function.
* `__null` as that's used by LLDB to define `NULL`/`Nil`/`nil`.

Getting rid of these last two keywords is possible but is a large enough change
that this will be handled in follow up patches.

Note that this only changes the keyword status of those tokens but this patch
does not remove any C++ functionality from the expression parser. The type
system still follows C++ rules and so does the rest of the expression parser.

There is another small change that gives the hardcoded macro definitions in LLDB
a higher precedence than the macros imported from the Objective-C modules. The
reason for this is that the Objective-C modules in LLDB are actually parsed in
Objective-C++ mode and they end up providing the C++ definitions of certain
system macros (like `NULL` being defined as `nullptr`). So we have to move the
LLDB definition forward and surround the definition from the module with an
`#ifdef` to make sure that we use the correct LLDB definition that doesn't
reference C++ keywords. Or to give an example, this is how the expression source
code changes:

Before:
```
 #define NULL (nullptr) // injected module definition
 #ifndef NULL
 #define NULL (__null) // hardcoded LLDB definition
 #endif
```

After:
```
 #ifndef NULL
 #define NULL (__null) // hardcoded LLDB definition
 #endif
 #ifndef NULL
 #define NULL (nullptr) // injected module definition
 #endif
```

Fixes rdar://10356912

Reviewed By: shafik

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

Added: 
    lldb/test/API/lang/c/cpp_keyword_identifiers/Makefile
    lldb/test/API/lang/c/cpp_keyword_identifiers/TestCppKeywordsAsCIdentifiers.py
    lldb/test/API/lang/c/cpp_keyword_identifiers/main.c
    lldb/test/API/lang/cpp/keywords_enabled/TestCppKeywordsEnabled.py
    lldb/test/API/lang/cpp/struct_with_keyword_name/Makefile
    lldb/test/API/lang/cpp/struct_with_keyword_name/TestStructWithKeywordName.py
    lldb/test/API/lang/cpp/struct_with_keyword_name/main.c
    lldb/test/API/lang/objc/cpp_keyword_identifiers/Makefile
    lldb/test/API/lang/objc/cpp_keyword_identifiers/TestCppKeywordsAsObjCIdentifiers.py
    lldb/test/API/lang/objc/cpp_keyword_identifiers/main.m
    lldb/test/API/lang/objcxx/cpp_keywords_enabled/TestObjCppKeywordsEnabled.py

Modified: 
    lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
    lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp
    lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp
    lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.h
    lldb/test/API/lang/objc/modules/TestObjCModules.py

Removed: 
    


################################################################################
diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
index bd0d831f0ca7..9b6e6e0f01f0 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
@@ -302,6 +302,41 @@ static void SetupModuleHeaderPaths(CompilerInstance *compiler,
   search_opts.ImplicitModuleMaps = true;
 }
 
+/// Iff the given identifier is a C++ keyword, remove it from the
+/// identifier table (i.e., make the token a normal identifier).
+static void RemoveCppKeyword(IdentifierTable &idents, llvm::StringRef token) {
+  // FIXME: 'using' is used by LLDB for local variables, so we can't remove
+  // this keyword without breaking this functionality.
+  if (token == "using")
+    return;
+  // GCC's '__null' is used by LLDB to define NULL/Nil/nil.
+  if (token == "__null")
+    return;
+
+  LangOptions cpp_lang_opts;
+  cpp_lang_opts.CPlusPlus = true;
+  cpp_lang_opts.CPlusPlus11 = true;
+  cpp_lang_opts.CPlusPlus20 = true;
+
+  clang::IdentifierInfo &ii = idents.get(token);
+  // The identifier has to be a C++-exclusive keyword. if not, then there is
+  // nothing to do.
+  if (!ii.isCPlusPlusKeyword(cpp_lang_opts))
+    return;
+  // If the token is already an identifier, then there is nothing to do.
+  if (ii.getTokenID() == clang::tok::identifier)
+    return;
+  // Otherwise the token is a C++ keyword, so turn it back into a normal
+  // identifier.
+  ii.revertTokenIDToIdentifier();
+}
+
+/// Remove all C++ keywords from the given identifier table.
+static void RemoveAllCppKeywords(IdentifierTable &idents) {
+#define KEYWORD(NAME, FLAGS) RemoveCppKeyword(idents, llvm::StringRef(#NAME));
+#include "clang/Basic/TokenKinds.def"
+}
+
 //===----------------------------------------------------------------------===//
 // Implementation of ClangExpressionParser
 //===----------------------------------------------------------------------===//
@@ -627,6 +662,21 @@ ClangExpressionParser::ClangExpressionParser(
     m_compiler->createSourceManager(m_compiler->getFileManager());
   m_compiler->createPreprocessor(TU_Complete);
 
+  switch (language) {
+  case lldb::eLanguageTypeC:
+  case lldb::eLanguageTypeC89:
+  case lldb::eLanguageTypeC99:
+  case lldb::eLanguageTypeC11:
+  case lldb::eLanguageTypeObjC:
+    // This is not a C++ expression but we enabled C++ as explained above.
+    // Remove all C++ keywords from the PP so that the user can still use
+    // variables that have C++ keywords as names (e.g. 'int template;').
+    RemoveAllCppKeywords(m_compiler->getPreprocessor().getIdentifierTable());
+    break;
+  default:
+    break;
+  }
+
   if (ClangModulesDeclVendor *decl_vendor =
           target_sp->GetClangModulesDeclVendor()) {
     if (auto *clang_persistent_vars = llvm::cast<ClangPersistentVariables>(

diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp
index 91fd246eb456..180e08b03c93 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp
@@ -297,6 +297,7 @@ bool ClangExpressionSourceCode::GetText(
     bool force_add_all_locals, llvm::ArrayRef<std::string> modules) const {
   const char *target_specific_defines = "typedef signed char BOOL;\n";
   std::string module_macros;
+  llvm::raw_string_ostream module_macros_stream(module_macros);
 
   Target *target = exe_ctx.GetTargetPtr();
   if (target) {
@@ -344,9 +345,13 @@ bool ClangExpressionSourceCode::GetText(
 
       decl_vendor->ForEachMacro(
           modules_for_macros,
-          [&module_macros](const std::string &expansion) -> bool {
-            module_macros.append(expansion);
-            module_macros.append("\n");
+          [&module_macros_stream](llvm::StringRef token,
+                                  llvm::StringRef expansion) -> bool {
+            // Check if the macro hasn't already been defined in the
+            // g_expression_prefix (which defines a few builtin macros).
+            module_macros_stream << "#ifndef " << token << "\n";
+            module_macros_stream << expansion << "\n";
+            module_macros_stream << "#endif\n";
             return false;
           });
     }
@@ -387,8 +392,8 @@ bool ClangExpressionSourceCode::GetText(
 
     StreamString wrap_stream;
 
-    wrap_stream.Printf("%s\n%s\n%s\n%s\n%s\n", module_macros.c_str(),
-                       debug_macros_stream.GetData(), g_expression_prefix,
+    wrap_stream.Printf("%s\n%s\n%s\n%s\n%s\n", g_expression_prefix,
+                       module_macros.c_str(), debug_macros_stream.GetData(),
                        target_specific_defines, m_prefix.c_str());
 
     // First construct a tagged form of the user expression so we can find it

diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp
index 6bd2d4425b7b..c014ad504d37 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp
@@ -95,8 +95,10 @@ class ClangModulesDeclVendorImpl : public ClangModulesDeclVendor {
   uint32_t FindDecls(ConstString name, bool append, uint32_t max_matches,
                      std::vector<CompilerDecl> &decls) override;
 
-  void ForEachMacro(const ModuleVector &modules,
-                    std::function<bool(const std::string &)> handler) override;
+  void ForEachMacro(
+      const ModuleVector &modules,
+      std::function<bool(llvm::StringRef, llvm::StringRef)> handler) override;
+
 private:
   void
   ReportModuleExportsHelper(std::set<ClangModulesDeclVendor::ModuleID> &exports,
@@ -420,7 +422,7 @@ ClangModulesDeclVendorImpl::FindDecls(ConstString name, bool append,
 
 void ClangModulesDeclVendorImpl::ForEachMacro(
     const ClangModulesDeclVendor::ModuleVector &modules,
-    std::function<bool(const std::string &)> handler) {
+    std::function<bool(llvm::StringRef, llvm::StringRef)> handler) {
   if (!m_enabled) {
     return;
   }
@@ -490,7 +492,8 @@ void ClangModulesDeclVendorImpl::ForEachMacro(
 
     if (macro_info) {
       std::string macro_expansion = "#define ";
-      macro_expansion.append(mi->first->getName().str());
+      llvm::StringRef macro_identifier = mi->first->getName();
+      macro_expansion.append(macro_identifier.str());
 
       {
         if (macro_info->isFunctionLike()) {
@@ -575,7 +578,7 @@ void ClangModulesDeclVendorImpl::ForEachMacro(
           }
         }
 
-        if (handler(macro_expansion)) {
+        if (handler(macro_identifier, macro_expansion)) {
           return;
         }
       }

diff  --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.h
index f04d1b07f03d..d820552a2912 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.h
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.h
@@ -90,13 +90,13 @@ class ClangModulesDeclVendor : public ClangDeclVendor {
   ///     if module A #defines a macro and module B #undefs it.
   ///
   /// \param[in] handler
-  ///     A function to call with the text of each #define (including the
-  ///     #define directive).  #undef directives are not included; we simply
-  ///     elide any corresponding #define.  If this function returns true,
-  ///     we stop the iteration immediately.
-  virtual void
-  ForEachMacro(const ModuleVector &modules,
-               std::function<bool(const std::string &)> handler) = 0;
+  ///     A function to call with the identifier of this macro and the text of
+  ///     each #define (including the #define directive). #undef directives are
+  ///     not included; we simply elide any corresponding #define. If this
+  ///     function returns true, we stop the iteration immediately.
+  virtual void ForEachMacro(
+      const ModuleVector &modules,
+      std::function<bool(llvm::StringRef, llvm::StringRef)> handler) = 0;
 
   /// Query whether Clang supports modules for a particular language.
   /// LLDB uses this to decide whether to try to find the modules loaded

diff  --git a/lldb/test/API/lang/c/cpp_keyword_identifiers/Makefile b/lldb/test/API/lang/c/cpp_keyword_identifiers/Makefile
new file mode 100644
index 000000000000..10495940055b
--- /dev/null
+++ b/lldb/test/API/lang/c/cpp_keyword_identifiers/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules

diff  --git a/lldb/test/API/lang/c/cpp_keyword_identifiers/TestCppKeywordsAsCIdentifiers.py b/lldb/test/API/lang/c/cpp_keyword_identifiers/TestCppKeywordsAsCIdentifiers.py
new file mode 100644
index 000000000000..67cfdc0f0d39
--- /dev/null
+++ b/lldb/test/API/lang/c/cpp_keyword_identifiers/TestCppKeywordsAsCIdentifiers.py
@@ -0,0 +1,87 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+class TestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    @no_debug_info_test
+    def test(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(self, "// break here", lldb.SBFileSpec("main.c"))
+
+        # Test several variables with C++ keyword names and make sure they
+        # work as intended in the expression parser.
+        self.expect_expr("alignas", result_type="int", result_value="1")
+        self.expect_expr("alignof", result_type="int", result_value="1")
+        self.expect_expr("and", result_type="int", result_value="1")
+        self.expect_expr("and_eq", result_type="int", result_value="1")
+        self.expect_expr("atomic_cancel", result_type="int", result_value="1")
+        self.expect_expr("atomic_commit", result_type="int", result_value="1")
+        self.expect_expr("atomic_noexcept", result_type="int", result_value="1")
+        self.expect_expr("bitand", result_type="int", result_value="1")
+        self.expect_expr("bitor", result_type="int", result_value="1")
+        self.expect_expr("catch", result_type="int", result_value="1")
+        self.expect_expr("char8_t", result_type="int", result_value="1")
+        self.expect_expr("char16_t", result_type="int", result_value="1")
+        self.expect_expr("char32_t", result_type="int", result_value="1")
+        self.expect_expr("class", result_type="int", result_value="1")
+        self.expect_expr("compl", result_type="int", result_value="1")
+        self.expect_expr("concept", result_type="int", result_value="1")
+        self.expect_expr("consteval", result_type="int", result_value="1")
+        self.expect_expr("constexpr", result_type="int", result_value="1")
+        self.expect_expr("constinit", result_type="int", result_value="1")
+        self.expect_expr("const_cast", result_type="int", result_value="1")
+        self.expect_expr("co_await", result_type="int", result_value="1")
+        self.expect_expr("co_return", result_type="int", result_value="1")
+        self.expect_expr("co_yield", result_type="int", result_value="1")
+        self.expect_expr("decltype", result_type="int", result_value="1")
+        self.expect_expr("delete", result_type="int", result_value="1")
+        self.expect_expr("dynamic_cast", result_type="int", result_value="1")
+        self.expect_expr("explicit", result_type="int", result_value="1")
+        self.expect_expr("export", result_type="int", result_value="1")
+        self.expect_expr("friend", result_type="int", result_value="1")
+        self.expect_expr("mutable", result_type="int", result_value="1")
+        self.expect_expr("namespace", result_type="int", result_value="1")
+        self.expect_expr("new", result_type="int", result_value="1")
+        self.expect_expr("noexcept", result_type="int", result_value="1")
+        self.expect_expr("not", result_type="int", result_value="1")
+        self.expect_expr("not_eq", result_type="int", result_value="1")
+        self.expect_expr("operator", result_type="int", result_value="1")
+        self.expect_expr("or", result_type="int", result_value="1")
+        self.expect_expr("or_eq", result_type="int", result_value="1")
+        self.expect_expr("private", result_type="int", result_value="1")
+        self.expect_expr("protected", result_type="int", result_value="1")
+        self.expect_expr("public", result_type="int", result_value="1")
+        self.expect_expr("reflexpr", result_type="int", result_value="1")
+        self.expect_expr("reinterpret_cast", result_type="int", result_value="1")
+        self.expect_expr("requires", result_type="int", result_value="1")
+        self.expect_expr("static_assert", result_type="int", result_value="1")
+        self.expect_expr("static_cast", result_type="int", result_value="1")
+        self.expect_expr("synchronized", result_type="int", result_value="1")
+        self.expect_expr("template", result_type="int", result_value="1")
+        self.expect_expr("this", result_type="int", result_value="1")
+        self.expect_expr("thread_local", result_type="int", result_value="1")
+        self.expect_expr("throw", result_type="int", result_value="1")
+        self.expect_expr("try", result_type="int", result_value="1")
+        self.expect_expr("typeid", result_type="int", result_value="1")
+        self.expect_expr("typename", result_type="int", result_value="1")
+        self.expect_expr("virtual", result_type="int", result_value="1")
+        self.expect_expr("xor", result_type="int", result_value="1")
+        self.expect_expr("xor_eq", result_type="int", result_value="1")
+
+        # Some keywords are not available in LLDB as their language feature
+        # is enabled by default.
+
+        # 'using' is used by LLDB for local variables.
+        self.expect("expr using", error=True, substrs=["expected unqualified-id"])
+
+        # 'wchar_t' supported is enabled in LLDB.
+        self.expect("expr wchar_t", error=True, substrs=["expected unqualified-id"])
+
+        # LLDB enables 'bool' support by default.
+        self.expect("expr bool", error=True, substrs=["expected unqualified-id"])
+        self.expect("expr false", error=True, substrs=["expected unqualified-id"])
+        self.expect("expr true", error=True, substrs=["expected unqualified-id"])

diff  --git a/lldb/test/API/lang/c/cpp_keyword_identifiers/main.c b/lldb/test/API/lang/c/cpp_keyword_identifiers/main.c
new file mode 100644
index 000000000000..668ebab25b36
--- /dev/null
+++ b/lldb/test/API/lang/c/cpp_keyword_identifiers/main.c
@@ -0,0 +1,68 @@
+int main() {
+  // Disable clang-format as it gets confused by the keyword identifiers.
+  // clang-format off
+  int alignas = 1;
+  int alignof = 1;
+  int and = 1;
+  int and_eq = 1;
+  int atomic_cancel = 1;
+  int atomic_commit = 1;
+  int atomic_noexcept = 1;
+  int bitand = 1;
+  int bitor = 1;
+  int bool = 1;
+  int catch = 1;
+  int char8_t = 1;
+  int char16_t = 1;
+  int char32_t = 1;
+  int class = 1;
+  int compl = 1;
+  int concept = 1;
+  int consteval = 1;
+  int constexpr = 1;
+  int constinit = 1;
+  int const_cast = 1;
+  int co_await = 1;
+  int co_return = 1;
+  int co_yield = 1;
+  int decltype = 1;
+  int delete = 1;
+  int dynamic_cast = 1;
+  int explicit = 1;
+  int export = 1;
+  int false = 1;
+  int friend = 1;
+  int mutable = 1;
+  int namespace = 1;
+  int new = 1;
+  int noexcept = 1;
+  int not = 1;
+  int not_eq = 1;
+  int operator= 1;
+  int or = 1;
+  int or_eq = 1;
+  int private = 1;
+  int protected = 1;
+  int public = 1;
+  int reflexpr = 1;
+  int reinterpret_cast = 1;
+  int requires = 1;
+  int static_assert = 1;
+  int static_cast = 1;
+  int synchronized = 1;
+  int template = 1;
+  int this = 1;
+  int thread_local = 1;
+  int throw = 1;
+  int true = 1;
+  int try = 1;
+  int typeid = 1;
+  int typename = 1;
+  int using = 1;
+  int virtual = 1;
+  int wchar_t = 1;
+  int xor = 1;
+  int xor_eq = 1;
+  // clang-format on
+  return 0; // break here
+}

diff  --git a/lldb/test/API/lang/cpp/keywords_enabled/TestCppKeywordsEnabled.py b/lldb/test/API/lang/cpp/keywords_enabled/TestCppKeywordsEnabled.py
new file mode 100644
index 000000000000..c7c15ecc8dd3
--- /dev/null
+++ b/lldb/test/API/lang/cpp/keywords_enabled/TestCppKeywordsEnabled.py
@@ -0,0 +1,14 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    @no_debug_info_test
+    def test_keyword(self):
+      # Make sure that C++ keywords work in the expression parser.
+      self.expect("expr -l c++ -- constexpr int i = 3 + 3; i", substrs=["= 6"])

diff  --git a/lldb/test/API/lang/cpp/struct_with_keyword_name/Makefile b/lldb/test/API/lang/cpp/struct_with_keyword_name/Makefile
new file mode 100644
index 000000000000..10495940055b
--- /dev/null
+++ b/lldb/test/API/lang/cpp/struct_with_keyword_name/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules

diff  --git a/lldb/test/API/lang/cpp/struct_with_keyword_name/TestStructWithKeywordName.py b/lldb/test/API/lang/cpp/struct_with_keyword_name/TestStructWithKeywordName.py
new file mode 100644
index 000000000000..af3b5a16cf5d
--- /dev/null
+++ b/lldb/test/API/lang/cpp/struct_with_keyword_name/TestStructWithKeywordName.py
@@ -0,0 +1,21 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+class TestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    @no_debug_info_test
+    def test(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(self, "// break here", lldb.SBFileSpec("main.c"))
+
+        # First run this in C which should work.
+        self.expect_expr("constexpr.class", result_type="int", result_value="3")
+
+        # Now try running this in a language that explicitly enables C++.
+        # This isn't expected to work, but at least it shouldn't crash LLDB.
+        self.expect("expr -l c++ -- constexpr.class", error=True, substrs=["expected unqualified-id"])
+        self.expect("expr -l objective-c++ -- constexpr.class", error=True, substrs=["expected unqualified-id"])

diff  --git a/lldb/test/API/lang/cpp/struct_with_keyword_name/main.c b/lldb/test/API/lang/cpp/struct_with_keyword_name/main.c
new file mode 100644
index 000000000000..36e875a89df9
--- /dev/null
+++ b/lldb/test/API/lang/cpp/struct_with_keyword_name/main.c
@@ -0,0 +1,9 @@
+struct class {
+  int class;
+};
+
+int main() {
+  struct class constexpr;
+  constexpr.class = 3;
+  return constexpr.class; // break here
+}

diff  --git a/lldb/test/API/lang/objc/cpp_keyword_identifiers/Makefile b/lldb/test/API/lang/objc/cpp_keyword_identifiers/Makefile
new file mode 100644
index 000000000000..845553d5e3f2
--- /dev/null
+++ b/lldb/test/API/lang/objc/cpp_keyword_identifiers/Makefile
@@ -0,0 +1,3 @@
+OBJC_SOURCES := main.m
+
+include Makefile.rules

diff  --git a/lldb/test/API/lang/objc/cpp_keyword_identifiers/TestCppKeywordsAsObjCIdentifiers.py b/lldb/test/API/lang/objc/cpp_keyword_identifiers/TestCppKeywordsAsObjCIdentifiers.py
new file mode 100644
index 000000000000..feae4f0d86af
--- /dev/null
+++ b/lldb/test/API/lang/objc/cpp_keyword_identifiers/TestCppKeywordsAsObjCIdentifiers.py
@@ -0,0 +1,89 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+class TestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    @skipUnlessDarwin
+    @no_debug_info_test
+    def test(self):
+        self.build()
+        lldbutil.run_to_source_breakpoint(self, "// break here", lldb.SBFileSpec("main.m"))
+
+        # Test several variables with C++ keyword names and make sure they
+        # work as intended in the expression parser.
+        self.expect_expr("alignas", result_type="int", result_value="1")
+        self.expect_expr("alignof", result_type="int", result_value="1")
+        self.expect_expr("and", result_type="int", result_value="1")
+        self.expect_expr("and_eq", result_type="int", result_value="1")
+        self.expect_expr("atomic_cancel", result_type="int", result_value="1")
+        self.expect_expr("atomic_commit", result_type="int", result_value="1")
+        self.expect_expr("atomic_noexcept", result_type="int", result_value="1")
+        self.expect_expr("bitand", result_type="int", result_value="1")
+        self.expect_expr("bitor", result_type="int", result_value="1")
+        self.expect_expr("catch", result_type="int", result_value="1")
+        self.expect_expr("char8_t", result_type="int", result_value="1")
+        self.expect_expr("char16_t", result_type="int", result_value="1")
+        self.expect_expr("char32_t", result_type="int", result_value="1")
+        self.expect_expr("class", result_type="int", result_value="1")
+        self.expect_expr("compl", result_type="int", result_value="1")
+        self.expect_expr("concept", result_type="int", result_value="1")
+        self.expect_expr("consteval", result_type="int", result_value="1")
+        self.expect_expr("constexpr", result_type="int", result_value="1")
+        self.expect_expr("constinit", result_type="int", result_value="1")
+        self.expect_expr("const_cast", result_type="int", result_value="1")
+        self.expect_expr("co_await", result_type="int", result_value="1")
+        self.expect_expr("co_return", result_type="int", result_value="1")
+        self.expect_expr("co_yield", result_type="int", result_value="1")
+        self.expect_expr("decltype", result_type="int", result_value="1")
+        self.expect_expr("delete", result_type="int", result_value="1")
+        self.expect_expr("dynamic_cast", result_type="int", result_value="1")
+        self.expect_expr("explicit", result_type="int", result_value="1")
+        self.expect_expr("export", result_type="int", result_value="1")
+        self.expect_expr("friend", result_type="int", result_value="1")
+        self.expect_expr("mutable", result_type="int", result_value="1")
+        self.expect_expr("namespace", result_type="int", result_value="1")
+        self.expect_expr("new", result_type="int", result_value="1")
+        self.expect_expr("noexcept", result_type="int", result_value="1")
+        self.expect_expr("not", result_type="int", result_value="1")
+        self.expect_expr("not_eq", result_type="int", result_value="1")
+        self.expect_expr("operator", result_type="int", result_value="1")
+        self.expect_expr("or", result_type="int", result_value="1")
+        self.expect_expr("or_eq", result_type="int", result_value="1")
+        self.expect_expr("private", result_type="int", result_value="1")
+        self.expect_expr("protected", result_type="int", result_value="1")
+        self.expect_expr("public", result_type="int", result_value="1")
+        self.expect_expr("reflexpr", result_type="int", result_value="1")
+        self.expect_expr("reinterpret_cast", result_type="int", result_value="1")
+        self.expect_expr("requires", result_type="int", result_value="1")
+        self.expect_expr("static_assert", result_type="int", result_value="1")
+        self.expect_expr("static_cast", result_type="int", result_value="1")
+        self.expect_expr("synchronized", result_type="int", result_value="1")
+        self.expect_expr("template", result_type="int", result_value="1")
+        self.expect_expr("this", result_type="int", result_value="1")
+        self.expect_expr("thread_local", result_type="int", result_value="1")
+        self.expect_expr("throw", result_type="int", result_value="1")
+        self.expect_expr("try", result_type="int", result_value="1")
+        self.expect_expr("typeid", result_type="int", result_value="1")
+        self.expect_expr("typename", result_type="int", result_value="1")
+        self.expect_expr("virtual", result_type="int", result_value="1")
+        self.expect_expr("xor", result_type="int", result_value="1")
+        self.expect_expr("xor_eq", result_type="int", result_value="1")
+
+
+        # Some keywords are not available in LLDB as their language feature
+        # is enabled by default.
+
+        # 'using' is used by LLDB for local variables.
+        self.expect("expr using", error=True, substrs=["expected unqualified-id"])
+
+        # 'wchar_t' supported is enabled in LLDB.
+        self.expect("expr wchar_t", error=True, substrs=["expected unqualified-id"])
+
+        # LLDB enables 'bool' support by default.
+        self.expect("expr bool", error=True, substrs=["expected unqualified-id"])
+        self.expect("expr false", error=True, substrs=["expected unqualified-id"])
+        self.expect("expr true", error=True, substrs=["expected unqualified-id"])

diff  --git a/lldb/test/API/lang/objc/cpp_keyword_identifiers/main.m b/lldb/test/API/lang/objc/cpp_keyword_identifiers/main.m
new file mode 100644
index 000000000000..668ebab25b36
--- /dev/null
+++ b/lldb/test/API/lang/objc/cpp_keyword_identifiers/main.m
@@ -0,0 +1,68 @@
+int main() {
+  // Disable clang-format as it gets confused by the keyword identifiers.
+  // clang-format off
+  int alignas = 1;
+  int alignof = 1;
+  int and = 1;
+  int and_eq = 1;
+  int atomic_cancel = 1;
+  int atomic_commit = 1;
+  int atomic_noexcept = 1;
+  int bitand = 1;
+  int bitor = 1;
+  int bool = 1;
+  int catch = 1;
+  int char8_t = 1;
+  int char16_t = 1;
+  int char32_t = 1;
+  int class = 1;
+  int compl = 1;
+  int concept = 1;
+  int consteval = 1;
+  int constexpr = 1;
+  int constinit = 1;
+  int const_cast = 1;
+  int co_await = 1;
+  int co_return = 1;
+  int co_yield = 1;
+  int decltype = 1;
+  int delete = 1;
+  int dynamic_cast = 1;
+  int explicit = 1;
+  int export = 1;
+  int false = 1;
+  int friend = 1;
+  int mutable = 1;
+  int namespace = 1;
+  int new = 1;
+  int noexcept = 1;
+  int not = 1;
+  int not_eq = 1;
+  int operator= 1;
+  int or = 1;
+  int or_eq = 1;
+  int private = 1;
+  int protected = 1;
+  int public = 1;
+  int reflexpr = 1;
+  int reinterpret_cast = 1;
+  int requires = 1;
+  int static_assert = 1;
+  int static_cast = 1;
+  int synchronized = 1;
+  int template = 1;
+  int this = 1;
+  int thread_local = 1;
+  int throw = 1;
+  int true = 1;
+  int try = 1;
+  int typeid = 1;
+  int typename = 1;
+  int using = 1;
+  int virtual = 1;
+  int wchar_t = 1;
+  int xor = 1;
+  int xor_eq = 1;
+  // clang-format on
+  return 0; // break here
+}

diff  --git a/lldb/test/API/lang/objc/modules/TestObjCModules.py b/lldb/test/API/lang/objc/modules/TestObjCModules.py
index 6b1b60e36877..e80705f4ebce 100644
--- a/lldb/test/API/lang/objc/modules/TestObjCModules.py
+++ b/lldb/test/API/lang/objc/modules/TestObjCModules.py
@@ -77,3 +77,5 @@ def test_expr(self):
             "p [NSURL URLWithString:@\"http://lldb.llvm.org\"].scheme",
             VARIABLES_DISPLAYED_CORRECTLY,
             substrs=["http"])
+        # Test that the NULL macro still works with a loaded module.
+        self.expect_expr("int *i = NULL; i == NULL", result_value="true")

diff  --git a/lldb/test/API/lang/objcxx/cpp_keywords_enabled/TestObjCppKeywordsEnabled.py b/lldb/test/API/lang/objcxx/cpp_keywords_enabled/TestObjCppKeywordsEnabled.py
new file mode 100644
index 000000000000..f8743536de1e
--- /dev/null
+++ b/lldb/test/API/lang/objcxx/cpp_keywords_enabled/TestObjCppKeywordsEnabled.py
@@ -0,0 +1,16 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    @skipUnlessDarwin
+    @no_debug_info_test
+    def test_keyword(self):
+      # Make sure that C++ keywords work in the expression parser when using
+      # Objective-C++.
+      self.expect("expr -l objective-c++ -- constexpr int i = 3 + 3; i", substrs=["= 6"])


        


More information about the lldb-commits mailing list