| //===-- ClangExpressionSourceCode.cpp -------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "ClangExpressionSourceCode.h" | |
| #include "ClangExpressionUtil.h" | |
| #include "clang/AST/TypeBase.h" | |
| #include "clang/Basic/CharInfo.h" | |
| #include "clang/Basic/FileManager.h" | |
| #include "clang/Basic/SourceManager.h" | |
| #include "clang/Lex/Lexer.h" | |
| #include "llvm/ADT/ScopeExit.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.h" | |
| #include "Plugins/ExpressionParser/Clang/ClangPersistentVariables.h" | |
| #include "lldb/Symbol/Block.h" | |
| #include "lldb/Symbol/CompileUnit.h" | |
| #include "lldb/Symbol/DebugMacros.h" | |
| #include "lldb/Symbol/TypeSystem.h" | |
| #include "lldb/Symbol/VariableList.h" | |
| #include "lldb/Target/ExecutionContext.h" | |
| #include "lldb/Target/Language.h" | |
| #include "lldb/Target/Platform.h" | |
| #include "lldb/Target/StackFrame.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Utility/StreamString.h" | |
| #include "lldb/lldb-forward.h" | |
| using namespace lldb_private; | |
| #define PREFIX_NAME "<lldb wrapper prefix>" | |
| #define SUFFIX_NAME "<lldb wrapper suffix>" | |
| const llvm::StringRef ClangExpressionSourceCode::g_prefix_file_name = PREFIX_NAME; | |
| const char *ClangExpressionSourceCode::g_expression_prefix = | |
| "#line 1 \"" PREFIX_NAME R"(" | |
| #ifndef offsetof | |
| #define offsetof(t, d) __builtin_offsetof(t, d) | |
| #endif | |
| #ifndef NULL | |
| #define NULL (__null) | |
| #endif | |
| #ifndef Nil | |
| #define Nil (__null) | |
| #endif | |
| #ifndef nil | |
| #define nil (__null) | |
| #endif | |
| #ifndef YES | |
| #define YES ((BOOL)1) | |
| #endif | |
| #ifndef NO | |
| #define NO ((BOOL)0) | |
| #endif | |
| typedef __INT8_TYPE__ int8_t; | |
| typedef __UINT8_TYPE__ uint8_t; | |
| typedef __INT16_TYPE__ int16_t; | |
| typedef __UINT16_TYPE__ uint16_t; | |
| typedef __INT32_TYPE__ int32_t; | |
| typedef __UINT32_TYPE__ uint32_t; | |
| typedef __INT64_TYPE__ int64_t; | |
| typedef __UINT64_TYPE__ uint64_t; | |
| typedef __INTPTR_TYPE__ intptr_t; | |
| typedef __UINTPTR_TYPE__ uintptr_t; | |
| typedef __SIZE_TYPE__ size_t; | |
| typedef __PTRDIFF_TYPE__ ptrdiff_t; | |
| typedef unsigned short unichar; | |
| extern "C" | |
| { | |
| int printf(const char * __restrict, ...); | |
| } | |
| )"; | |
| const char *ClangExpressionSourceCode::g_expression_suffix = | |
| "\n;\n#line 1 \"" SUFFIX_NAME "\"\n"; | |
| namespace { | |
| class AddMacroState { | |
| enum State { | |
| CURRENT_FILE_NOT_YET_PUSHED, | |
| CURRENT_FILE_PUSHED, | |
| CURRENT_FILE_POPPED | |
| }; | |
| public: | |
| AddMacroState(const FileSpec ¤t_file, const uint32_t current_file_line) | |
| : m_current_file(current_file), m_current_file_line(current_file_line) {} | |
| void StartFile(const FileSpec &file) { | |
| m_file_stack.push_back(file); | |
| if (file == m_current_file) | |
| m_state = CURRENT_FILE_PUSHED; | |
| } | |
| void EndFile() { | |
| if (m_file_stack.size() == 0) | |
| return; | |
| FileSpec old_top = m_file_stack.back(); | |
| m_file_stack.pop_back(); | |
| if (old_top == m_current_file) | |
| m_state = CURRENT_FILE_POPPED; | |
| } | |
| // An entry is valid if it occurs before the current line in the current | |
| // file. | |
| bool IsValidEntry(uint32_t line) { | |
| switch (m_state) { | |
| case CURRENT_FILE_NOT_YET_PUSHED: | |
| return true; | |
| case CURRENT_FILE_PUSHED: | |
| // If we are in file included in the current file, the entry should be | |
| // added. | |
| if (m_file_stack.back() != m_current_file) | |
| return true; | |
| return line < m_current_file_line; | |
| default: | |
| return false; | |
| } | |
| } | |
| private: | |
| std::vector<FileSpec> m_file_stack; | |
| State m_state = CURRENT_FILE_NOT_YET_PUSHED; | |
| FileSpec m_current_file; | |
| uint32_t m_current_file_line; | |
| }; | |
| } // anonymous namespace | |
| static void AddMacros(const DebugMacros *dm, CompileUnit *comp_unit, | |
| AddMacroState &state, StreamString &stream) { | |
| if (dm == nullptr) | |
| return; | |
| // The macros directives below can potentially redefine builtin macros of the | |
| // Clang instance which parses the user expression. The Clang diagnostics | |
| // caused by this are not useful for the user as the source code here is | |
| // generated by LLDB. | |
| stream << "#pragma clang diagnostic push\n"; | |
| stream << "#pragma clang diagnostic ignored \"-Wmacro-redefined\"\n"; | |
| stream << "#pragma clang diagnostic ignored \"-Wbuiltin-macro-redefined\"\n"; | |
| llvm::scope_exit pop_warning( | |
| [&stream]() { stream << "#pragma clang diagnostic pop\n"; }); | |
| for (size_t i = 0; i < dm->GetNumMacroEntries(); i++) { | |
| const DebugMacroEntry &entry = dm->GetMacroEntryAtIndex(i); | |
| uint32_t line; | |
| switch (entry.GetType()) { | |
| case DebugMacroEntry::DEFINE: | |
| if (state.IsValidEntry(entry.GetLineNumber())) | |
| stream.Format("#define {0}\n", entry.GetMacroString()); | |
| else | |
| return; | |
| break; | |
| case DebugMacroEntry::UNDEF: | |
| if (state.IsValidEntry(entry.GetLineNumber())) | |
| stream.Format("#undef {0}\n", entry.GetMacroString()); | |
| else | |
| return; | |
| break; | |
| case DebugMacroEntry::START_FILE: | |
| line = entry.GetLineNumber(); | |
| if (state.IsValidEntry(line)) | |
| state.StartFile(entry.GetFileSpec(comp_unit)); | |
| else | |
| return; | |
| break; | |
| case DebugMacroEntry::END_FILE: | |
| state.EndFile(); | |
| break; | |
| case DebugMacroEntry::INDIRECT: | |
| AddMacros(entry.GetIndirectDebugMacros(), comp_unit, state, stream); | |
| break; | |
| default: | |
| // This is an unknown/invalid entry. Ignore. | |
| break; | |
| } | |
| } | |
| } | |
| /// Return qualifers of the current C++ method. | |
| static clang::Qualifiers GetFrameCVQualifiers(StackFrame *frame) { | |
| if (!frame) | |
| return {}; | |
| auto this_sp = frame->FindVariable(ConstString("this")); | |
| if (!this_sp) | |
| return {}; | |
| // Lambdas that capture 'this' have a member variable called 'this'. The class | |
| // context of __lldb_expr for a lambda is the class type of the 'this' capture | |
| // (not the anonymous lambda structure). So use the qualifiers of the captured | |
| // 'this'. | |
| if (auto this_this_sp = this_sp->GetChildMemberWithName("this")) | |
| return clang::Qualifiers::fromCVRMask( | |
| this_this_sp->GetCompilerType().GetPointeeType().GetTypeQualifiers()); | |
| // Not in a lambda. Return 'this' qualifiers. | |
| return clang::Qualifiers::fromCVRMask( | |
| this_sp->GetCompilerType().GetPointeeType().GetTypeQualifiers()); | |
| } | |
| lldb_private::ClangExpressionSourceCode::ClangExpressionSourceCode( | |
| llvm::StringRef filename, llvm::StringRef name, llvm::StringRef prefix, | |
| llvm::StringRef body, Wrapping wrap, WrapKind wrap_kind) | |
| : ExpressionSourceCode(name, prefix, body, wrap), m_wrap_kind(wrap_kind) { | |
| // Use #line markers to pretend that we have a single-line source file | |
| // containing only the user expression. This will hide our wrapper code | |
| // from the user when we render diagnostics with Clang. | |
| m_start_marker = "#line 1 \"" + filename.str() + "\"\n"; | |
| m_end_marker = g_expression_suffix; | |
| } | |
| namespace { | |
| /// Allows checking if a token is contained in a given expression. | |
| class TokenVerifier { | |
| /// The tokens we found in the expression. | |
| llvm::StringSet<> m_tokens; | |
| public: | |
| TokenVerifier(std::string body); | |
| /// Returns true iff the given expression body contained a token with the | |
| /// given content. | |
| bool hasToken(llvm::StringRef token) const { | |
| return m_tokens.contains(token); | |
| } | |
| }; | |
| // If we're evaluating from inside a lambda that captures a 'this' pointer, | |
| // add a "using" declaration to 'stream' for each capture used in the | |
| // expression (tokenized by 'verifier'). | |
| // | |
| // If no 'this' capture exists, generate no using declarations. Instead | |
| // capture lookups will get resolved by the same mechanism as class member | |
| // variable lookup. That's because Clang generates an unnamed structure | |
| // representing the lambda closure whose members are the captured variables. | |
| void AddLambdaCaptureDecls(StreamString &stream, StackFrame *frame, | |
| TokenVerifier const &verifier) { | |
| assert(frame); | |
| if (auto thisValSP = ClangExpressionUtil::GetLambdaValueObject(frame)) { | |
| uint32_t numChildren = thisValSP->GetNumChildrenIgnoringErrors(); | |
| for (uint32_t i = 0; i < numChildren; ++i) { | |
| auto childVal = thisValSP->GetChildAtIndex(i); | |
| ConstString childName(childVal ? childVal->GetName() : ConstString("")); | |
| if (!childName.IsEmpty() && verifier.hasToken(childName.GetStringRef()) && | |
| childName != "this") { | |
| stream.Printf("using $__lldb_local_vars::%s;\n", | |
| childName.GetCString()); | |
| } | |
| } | |
| } | |
| } | |
| } // namespace | |
| TokenVerifier::TokenVerifier(std::string body) { | |
| using namespace clang; | |
| // We only care about tokens and not their original source locations. If we | |
| // move the whole expression to only be in one line we can simplify the | |
| // following code that extracts the token contents. | |
| llvm::replace(body, '\n', ' '); | |
| llvm::replace(body, '\r', ' '); | |
| FileSystemOptions file_opts; | |
| FileManager file_mgr(file_opts, | |
| FileSystem::Instance().GetVirtualFileSystem()); | |
| // Let's build the actual source code Clang needs and setup some utility | |
| // objects. | |
| DiagnosticOptions diags_opts; | |
| DiagnosticsEngine diags(DiagnosticIDs::create(), diags_opts); | |
| clang::SourceManager SM(diags, file_mgr); | |
| auto buf = llvm::MemoryBuffer::getMemBuffer(body); | |
| FileID FID = SM.createFileID(buf->getMemBufferRef()); | |
| // Let's just enable the latest ObjC and C++ which should get most tokens | |
| // right. | |
| LangOptions Opts; | |
| Opts.ObjC = true; | |
| Opts.DollarIdents = true; | |
| Opts.CPlusPlus20 = true; | |
| Opts.LineComment = true; | |
| Lexer lex(FID, buf->getMemBufferRef(), SM, Opts); | |
| Token token; | |
| bool exit = false; | |
| while (!exit) { | |
| // Returns true if this is the last token we get from the lexer. | |
| exit = lex.LexFromRawLexer(token); | |
| // Extract the column number which we need to extract the token content. | |
| // Our expression is just one line, so we don't need to handle any line | |
| // numbers here. | |
| bool invalid = false; | |
| unsigned start = SM.getSpellingColumnNumber(token.getLocation(), &invalid); | |
| if (invalid) | |
| continue; | |
| // Column numbers start at 1, but indexes in our string start at 0. | |
| --start; | |
| // Annotations don't have a length, so let's skip them. | |
| if (token.isAnnotation()) | |
| continue; | |
| // Extract the token string from our source code and store it. | |
| std::string token_str = body.substr(start, token.getLength()); | |
| if (token_str.empty()) | |
| continue; | |
| m_tokens.insert(token_str); | |
| } | |
| } | |
| void ClangExpressionSourceCode::AddLocalVariableDecls(StreamString &stream, | |
| const std::string &expr, | |
| StackFrame *frame) const { | |
| assert(frame); | |
| TokenVerifier tokens(expr); | |
| lldb::VariableListSP var_list_sp = frame->GetInScopeVariableList(false, true); | |
| for (size_t i = 0; i < var_list_sp->GetSize(); i++) { | |
| lldb::VariableSP var_sp = var_list_sp->GetVariableAtIndex(i); | |
| ConstString var_name = var_sp->GetName(); | |
| if (var_name == "this" && m_wrap_kind == WrapKind::CppMemberFunction) { | |
| AddLambdaCaptureDecls(stream, frame, tokens); | |
| continue; | |
| } | |
| // We can check for .block_descriptor w/o checking for language since this | |
| // is not a valid identifier in either C or C++. | |
| if (!var_name || var_name == ".block_descriptor") | |
| continue; | |
| if (!expr.empty() && !tokens.hasToken(var_name.GetStringRef())) | |
| continue; | |
| const bool is_objc = m_wrap_kind == WrapKind::ObjCInstanceMethod || | |
| m_wrap_kind == WrapKind::ObjCStaticMethod; | |
| if ((var_name == "self" || var_name == "_cmd") && is_objc) | |
| continue; | |
| stream.Format("using $__lldb_local_vars::{0};\n", var_name); | |
| } | |
| } | |
| bool ClangExpressionSourceCode::GetText(std::string &text, | |
| ExecutionContext &exe_ctx, | |
| bool add_locals, | |
| bool force_add_all_locals, | |
| llvm::ArrayRef<std::string> modules, | |
| bool ignore_context_qualifiers) 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) { | |
| if (target->GetArchitecture().GetMachine() == llvm::Triple::aarch64 || | |
| target->GetArchitecture().GetMachine() == llvm::Triple::aarch64_32) { | |
| target_specific_defines = "typedef bool BOOL;\n"; | |
| } | |
| if (target->GetArchitecture().GetMachine() == llvm::Triple::x86_64) { | |
| if (lldb::PlatformSP platform_sp = target->GetPlatform()) { | |
| if (platform_sp->GetPluginName() == "ios-simulator") { | |
| target_specific_defines = "typedef bool BOOL;\n"; | |
| } | |
| } | |
| } | |
| auto *persistent_vars = llvm::cast<ClangPersistentVariables>( | |
| target->GetPersistentExpressionStateForLanguage(lldb::eLanguageTypeC)); | |
| std::shared_ptr<ClangModulesDeclVendor> decl_vendor = | |
| persistent_vars->GetClangModulesDeclVendor(); | |
| if (decl_vendor) { | |
| const ClangModulesDeclVendor::ModuleVector &hand_imported_modules = | |
| persistent_vars->GetHandLoadedClangModules(); | |
| ClangModulesDeclVendor::ModuleVector modules_for_macros; | |
| for (ClangModulesDeclVendor::ModuleID module : hand_imported_modules) { | |
| modules_for_macros.push_back(module); | |
| } | |
| if (target->GetEnableAutoImportClangModules()) { | |
| if (StackFrame *frame = exe_ctx.GetFramePtr()) { | |
| if (Block *block = frame->GetFrameBlock()) { | |
| SymbolContext sc; | |
| block->CalculateSymbolContext(&sc); | |
| if (sc.comp_unit) { | |
| if (auto err = decl_vendor->AddModulesForCompileUnit( | |
| *sc.comp_unit, modules_for_macros)) | |
| LLDB_LOG_ERROR( | |
| GetLog(LLDBLog::Expressions), std::move(err), | |
| "Error while loading hand-imported modules:\n{0}"); | |
| } | |
| } | |
| } | |
| } | |
| decl_vendor->ForEachMacro( | |
| modules_for_macros, | |
| [&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; | |
| }); | |
| } | |
| } | |
| StreamString debug_macros_stream; | |
| StreamString lldb_local_var_decls; | |
| if (StackFrame *frame = exe_ctx.GetFramePtr()) { | |
| const SymbolContext &sc = frame->GetSymbolContext( | |
| lldb::eSymbolContextCompUnit | lldb::eSymbolContextLineEntry); | |
| if (sc.comp_unit && sc.line_entry.IsValid()) { | |
| DebugMacros *dm = sc.comp_unit->GetDebugMacros(); | |
| if (dm) { | |
| AddMacroState state(sc.line_entry.GetFile(), sc.line_entry.line); | |
| AddMacros(dm, sc.comp_unit, state, debug_macros_stream); | |
| } | |
| } | |
| if (add_locals) | |
| if (target->GetInjectLocalVariables(&exe_ctx)) { | |
| AddLocalVariableDecls(lldb_local_var_decls, | |
| force_add_all_locals ? "" : m_body, frame); | |
| } | |
| } | |
| if (m_wrap) { | |
| // Generate a list of @import statements that will import the specified | |
| // module into our expression. | |
| std::string module_imports; | |
| for (const std::string &module : modules) { | |
| module_imports.append("@import "); | |
| module_imports.append(module); | |
| module_imports.append(";\n"); | |
| } | |
| StreamString wrap_stream; | |
| 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 | |
| // later: | |
| std::string tagged_body; | |
| tagged_body.append(m_start_marker); | |
| tagged_body.append(m_body); | |
| tagged_body.append(m_end_marker); | |
| switch (m_wrap_kind) { | |
| case WrapKind::Function: | |
| wrap_stream.Printf("%s" | |
| "void \n" | |
| "%s(void *$__lldb_arg) \n" | |
| "{ \n" | |
| " %s; \n" | |
| "%s" | |
| "} \n", | |
| module_imports.c_str(), m_name.c_str(), | |
| lldb_local_var_decls.GetData(), tagged_body.c_str()); | |
| break; | |
| case WrapKind::CppMemberFunction: | |
| wrap_stream.Printf("%s" | |
| "void \n" | |
| "$__lldb_class::%s(void *$__lldb_arg) %s \n" | |
| "{ \n" | |
| " %s; \n" | |
| "%s" | |
| "} \n", | |
| module_imports.c_str(), m_name.c_str(), | |
| ignore_context_qualifiers | |
| ? "" | |
| : GetFrameCVQualifiers(exe_ctx.GetFramePtr()) | |
| .getAsString() | |
| .c_str(), | |
| lldb_local_var_decls.GetData(), tagged_body.c_str()); | |
| break; | |
| case WrapKind::ObjCInstanceMethod: | |
| wrap_stream.Printf( | |
| "%s" | |
| "@interface $__lldb_objc_class ($__lldb_category) \n" | |
| "-(void)%s:(void *)$__lldb_arg; \n" | |
| "@end \n" | |
| "@implementation $__lldb_objc_class ($__lldb_category) \n" | |
| "-(void)%s:(void *)$__lldb_arg \n" | |
| "{ \n" | |
| " %s; \n" | |
| "%s" | |
| "} \n" | |
| "@end \n", | |
| module_imports.c_str(), m_name.c_str(), m_name.c_str(), | |
| lldb_local_var_decls.GetData(), tagged_body.c_str()); | |
| break; | |
| case WrapKind::ObjCStaticMethod: | |
| wrap_stream.Printf( | |
| "%s" | |
| "@interface $__lldb_objc_class ($__lldb_category) \n" | |
| "+(void)%s:(void *)$__lldb_arg; \n" | |
| "@end \n" | |
| "@implementation $__lldb_objc_class ($__lldb_category) \n" | |
| "+(void)%s:(void *)$__lldb_arg \n" | |
| "{ \n" | |
| " %s; \n" | |
| "%s" | |
| "} \n" | |
| "@end \n", | |
| module_imports.c_str(), m_name.c_str(), m_name.c_str(), | |
| lldb_local_var_decls.GetData(), tagged_body.c_str()); | |
| break; | |
| } | |
| text = std::string(wrap_stream.GetString()); | |
| } else { | |
| text.append(m_body); | |
| } | |
| return true; | |
| } | |
| bool ClangExpressionSourceCode::GetOriginalBodyBounds( | |
| std::string transformed_text, size_t &start_loc, size_t &end_loc) { | |
| start_loc = transformed_text.find(m_start_marker); | |
| if (start_loc == std::string::npos) | |
| return false; | |
| start_loc += m_start_marker.size(); | |
| end_loc = transformed_text.find(m_end_marker); | |
| return end_loc != std::string::npos; | |
| } |