Index: lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp =================================================================== --- lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp +++ lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp @@ -302,6 +302,41 @@ 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 //===----------------------------------------------------------------------===// @@ -623,6 +658,21 @@ 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( Index: lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp =================================================================== --- lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp +++ lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp @@ -297,6 +297,7 @@ bool force_add_all_locals, llvm::ArrayRef 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 @@ 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 @@ 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 Index: lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.h =================================================================== --- lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.h +++ lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.h @@ -90,13 +90,13 @@ /// 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 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 handler) = 0; /// Query whether Clang supports modules for a particular language. /// LLDB uses this to decide whether to try to find the modules loaded Index: lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp =================================================================== --- lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp +++ lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp @@ -95,8 +95,10 @@ uint32_t FindDecls(ConstString name, bool append, uint32_t max_matches, std::vector &decls) override; - void ForEachMacro(const ModuleVector &modules, - std::function handler) override; + void ForEachMacro( + const ModuleVector &modules, + std::function handler) override; + private: void ReportModuleExportsHelper(std::set &exports, @@ -420,7 +422,7 @@ void ClangModulesDeclVendorImpl::ForEachMacro( const ClangModulesDeclVendor::ModuleVector &modules, - std::function handler) { + std::function handler) { if (!m_enabled) { return; } @@ -490,7 +492,8 @@ 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 @@ } } - if (handler(macro_expansion)) { + if (handler(macro_identifier, macro_expansion)) { return; } } Index: lldb/test/API/lang/c/cpp_keyword_identifiers/Makefile =================================================================== --- /dev/null +++ lldb/test/API/lang/c/cpp_keyword_identifiers/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules Index: lldb/test/API/lang/c/cpp_keyword_identifiers/TestCppKeywordsAsCIdentifiers.py =================================================================== --- /dev/null +++ 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"]) Index: lldb/test/API/lang/c/cpp_keyword_identifiers/main.c =================================================================== --- /dev/null +++ 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 +} Index: lldb/test/API/lang/cpp/keywords_enabled/TestCppKeywordsEnabled.py =================================================================== --- /dev/null +++ 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"]) Index: lldb/test/API/lang/cpp/struct_with_keyword_name/Makefile =================================================================== --- /dev/null +++ lldb/test/API/lang/cpp/struct_with_keyword_name/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules Index: lldb/test/API/lang/cpp/struct_with_keyword_name/TestStructWithKeywordName.py =================================================================== --- /dev/null +++ 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"]) Index: lldb/test/API/lang/cpp/struct_with_keyword_name/main.c =================================================================== --- /dev/null +++ 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 +} Index: lldb/test/API/lang/objc/cpp_keyword_identifiers/Makefile =================================================================== --- /dev/null +++ lldb/test/API/lang/objc/cpp_keyword_identifiers/Makefile @@ -0,0 +1,3 @@ +OBJC_SOURCES := main.m + +include Makefile.rules Index: lldb/test/API/lang/objc/cpp_keyword_identifiers/TestCppKeywordsAsObjCIdentifiers.py =================================================================== --- /dev/null +++ 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"]) Index: lldb/test/API/lang/objc/cpp_keyword_identifiers/main.m =================================================================== --- /dev/null +++ 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 +} Index: lldb/test/API/lang/objc/modules/TestObjCModules.py =================================================================== --- lldb/test/API/lang/objc/modules/TestObjCModules.py +++ lldb/test/API/lang/objc/modules/TestObjCModules.py @@ -78,3 +78,5 @@ "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") Index: lldb/test/API/lang/objcxx/cpp_keywords_enabled/TestObjCppKeywordsEnabled.py =================================================================== --- /dev/null +++ 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"])