Author: Chelsea Cassanova Date: 2025-07-09T09:19:42-07:00 New Revision: afc82ce3aa56670101495d7f328d938f55ccaf8b
URL: https://github.com/llvm/llvm-project/commit/afc82ce3aa56670101495d7f328d938f55ccaf8b DIFF: https://github.com/llvm/llvm-project/commit/afc82ce3aa56670101495d7f328d938f55ccaf8b.diff LOG: Reland "[lldb][RPC] Upstream lldb-rpc-gen tool" (#146969) (#147417) Relands the commit to upstream the lldb-rpc-gen tool in order to fix a build failure on the linux remote bots. The reland adds the Clang resource dir unconditionally to the invocation for the tool instead of only adding it in the event that we're using a standalone build. Original PR description: This commit upstreams the lldb-rpc-gen tool, a ClangTool that generates the LLDB RPC client and server interfaces. This tool, as well as LLDB RPC itself is built by default. If it needs to be disabled, put -DLLDB_BUILD_LLDBRPC=OFF in your CMake invocation. https://discourse.llvm.org/t/rfc-upstreaming-lldb-rpc/85804 Original PR Link: https://github.com/llvm/llvm-project/pull/138031 Added: lldb/test/Shell/RPC/Generator/Inputs/SBDummy.h lldb/test/Shell/RPC/Generator/Tests/CheckRPCGenToolByproducts.test lldb/test/Shell/RPC/Generator/lit.local.cfg lldb/tools/lldb-rpc/CMakeLists.txt lldb/tools/lldb-rpc/LLDBRPCGeneration.cmake lldb/tools/lldb-rpc/LLDBRPCHeaders.cmake lldb/tools/lldb-rpc/lldb-rpc-gen/CMakeLists.txt lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.cpp lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.h Modified: lldb/cmake/modules/LLDBConfig.cmake lldb/test/CMakeLists.txt lldb/test/Shell/helper/toolchain.py lldb/test/Shell/lit.site.cfg.py.in lldb/tools/CMakeLists.txt lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp Removed: ################################################################################ diff --git a/lldb/cmake/modules/LLDBConfig.cmake b/lldb/cmake/modules/LLDBConfig.cmake index 8c30b6e09d2c7..f674c29682160 100644 --- a/lldb/cmake/modules/LLDBConfig.cmake +++ b/lldb/cmake/modules/LLDBConfig.cmake @@ -323,4 +323,6 @@ else() set(LLDB_CAN_USE_DEBUGSERVER OFF) endif() +set(LLDB_BUILD_LLDBRPC ON CACHE BOOL "") + include(LLDBGenerateConfig) diff --git a/lldb/test/CMakeLists.txt b/lldb/test/CMakeLists.txt index 6449ac5a9247f..7cf239c7f95ab 100644 --- a/lldb/test/CMakeLists.txt +++ b/lldb/test/CMakeLists.txt @@ -132,6 +132,10 @@ if(TARGET lldb-framework) add_lldb_test_dependency(lldb-framework) endif() +if (LLDB_BUILD_LLDBRPC) + add_lldb_test_dependency(lldb-rpc-generate-sources) +endif() + # Add dependencies that are not exported targets when building standalone. if(NOT LLDB_BUILT_STANDALONE) add_lldb_test_dependency( @@ -249,7 +253,8 @@ llvm_canonicalize_cmake_booleans( LLDB_TEST_SHELL_DISABLE_REMOTE LLDB_TOOL_LLDB_SERVER_BUILD LLDB_USE_SYSTEM_DEBUGSERVER - LLDB_IS_64_BITS) + LLDB_IS_64_BITS + LLDB_BUILD_LLDBRPC) # Configure the individual test suites. add_subdirectory(API) diff --git a/lldb/test/Shell/RPC/Generator/Inputs/SBDummy.h b/lldb/test/Shell/RPC/Generator/Inputs/SBDummy.h new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/lldb/test/Shell/RPC/Generator/Tests/CheckRPCGenToolByproducts.test b/lldb/test/Shell/RPC/Generator/Tests/CheckRPCGenToolByproducts.test new file mode 100644 index 0000000000000..15fcf8fb39c7d --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/Tests/CheckRPCGenToolByproducts.test @@ -0,0 +1,9 @@ +RUN: %lldb-rpc-gen --output-dir=%t %S/../Inputs/SBDummy.h + +RUN: ls %t | FileCheck %s + +# We're just making sure that the tool emits the class names, +# methods and skipped methods file in the output directory. +CHECK: SBAPI.def +CHECK: SBClasses.def +CHECK: SkippedMethods.txt diff --git a/lldb/test/Shell/RPC/Generator/lit.local.cfg b/lldb/test/Shell/RPC/Generator/lit.local.cfg new file mode 100644 index 0000000000000..db9494781c00c --- /dev/null +++ b/lldb/test/Shell/RPC/Generator/lit.local.cfg @@ -0,0 +1,3 @@ +# All tests for the tool need lldb-rpc-gen to be built. +if not config.lldb_has_lldbrpc: + config.unsupported = True diff --git a/lldb/test/Shell/helper/toolchain.py b/lldb/test/Shell/helper/toolchain.py index 42968128f2702..728f6347242f1 100644 --- a/lldb/test/Shell/helper/toolchain.py +++ b/lldb/test/Shell/helper/toolchain.py @@ -156,6 +156,16 @@ def use_lldb_substitutions(config): extra_args=["platform"], unresolved="ignore", ), + ToolSubst( + "%lldb-rpc-gen", + command=FindTool("lldb-rpc-gen"), + # We need the LLDB build directory root to pass into the tool, not the test build root. + extra_args=[ + "-p " + config.lldb_build_directory + "/..", + '--extra-arg="-resource-dir=' + config.clang_resource_dir + '"', + ], + unresolved="ignore", + ), "lldb-test", "lldb-dap", ToolSubst( diff --git a/lldb/test/Shell/lit.site.cfg.py.in b/lldb/test/Shell/lit.site.cfg.py.in index 5be5359217769..beaa41e6fd379 100644 --- a/lldb/test/Shell/lit.site.cfg.py.in +++ b/lldb/test/Shell/lit.site.cfg.py.in @@ -33,6 +33,7 @@ config.lldb_build_directory = "@LLDB_TEST_BUILD_DIRECTORY@" config.have_lldb_server = @LLDB_TOOL_LLDB_SERVER_BUILD@ config.lldb_system_debugserver = @LLDB_USE_SYSTEM_DEBUGSERVER@ config.llvm_use_sanitizer = "@LLVM_USE_SANITIZER@" +config.lldb_has_lldbrpc = @LLDB_BUILD_LLDBRPC@ # The shell tests use their own module caches. config.lldb_module_cache = os.path.join("@LLDB_TEST_MODULE_CACHE_LLDB@", "lldb-shell") config.clang_module_cache = os.path.join("@LLDB_TEST_MODULE_CACHE_CLANG@", "lldb-shell") diff --git a/lldb/tools/CMakeLists.txt b/lldb/tools/CMakeLists.txt index 6804dc234555b..73ffbbbee3056 100644 --- a/lldb/tools/CMakeLists.txt +++ b/lldb/tools/CMakeLists.txt @@ -10,6 +10,9 @@ add_subdirectory(lldb-fuzzer EXCLUDE_FROM_ALL) add_lldb_tool_subdirectory(lldb-instr) add_lldb_tool_subdirectory(lldb-dap) +if (LLDB_BUILD_LLDBRPC) + add_lldb_tool_subdirectory(lldb-rpc) +endif() if (CMAKE_SYSTEM_NAME MATCHES "Darwin") add_lldb_tool_subdirectory(darwin-debug) diff --git a/lldb/tools/lldb-rpc/CMakeLists.txt b/lldb/tools/lldb-rpc/CMakeLists.txt new file mode 100644 index 0000000000000..fdd6cf9163e96 --- /dev/null +++ b/lldb/tools/lldb-rpc/CMakeLists.txt @@ -0,0 +1,22 @@ +include(CheckCXXCompilerFlag) +# Umbrella target for the entire framework is a default target. +add_custom_target(lldb-rpc ALL) + +if(LLDB_CODESIGN_IDENTITY) + # Use explicit LLDB identity + set(LLVM_CODESIGNING_IDENTITY ${LLDB_CODESIGN_IDENTITY}) +else() + # Use explicit LLVM identity or default to ad-hoc signing if empty + if(NOT LLVM_CODESIGNING_IDENTITY) + set(LLVM_CODESIGNING_IDENTITY -) + endif() +endif() + +# LLDBRPCGeneration.cmake needs the LLDB_RPC_GEN_EXE variable +# which gets defined in the lldb-rpc-gen folder, so we're adding +# this folder before we add that file. +add_lldb_tool_subdirectory(lldb-rpc-gen) +include(${CMAKE_CURRENT_SOURCE_DIR}/LLDBRPCGeneration.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/LLDBRPCHeaders.cmake) + +add_dependencies(lldb-rpc lldb-rpc-generate-sources liblldbrpc-headers) diff --git a/lldb/tools/lldb-rpc/LLDBRPCGeneration.cmake b/lldb/tools/lldb-rpc/LLDBRPCGeneration.cmake new file mode 100644 index 0000000000000..ca73c546d781f --- /dev/null +++ b/lldb/tools/lldb-rpc/LLDBRPCGeneration.cmake @@ -0,0 +1,75 @@ +if (NOT DEFINED LLDB_RPC_GEN_EXE) + message(FATAL_ERROR + "Unable to generate lldb-rpc sources because LLDB_RPC_GEN_EXE is not + defined. If you are cross-compiling, please build lldb-rpc-gen for your host + platform.") +endif() +set(lldb_rpc_generated_dir "${CMAKE_CURRENT_BINARY_DIR}/generated") +set(lldb_rpc_server_generated_source_dir "${lldb_rpc_generated_dir}/server") + +file(GLOB api_headers ${LLDB_SOURCE_DIR}/include/lldb/API/SB*.h) +# We don't generate SBCommunication +list(REMOVE_ITEM api_headers ${LLDB_SOURCE_DIR}/include/lldb/API/SBCommunication.h) +# SBDefines.h is mostly definitions and forward declarations, nothing to +# generate. +list(REMOVE_ITEM api_headers ${LLDB_SOURCE_DIR}/include/lldb/API/SBDefines.h) + +# Generate the list of byproducts. Note that we cannot just glob the files in +# the directory with the generated sources because BYPRODUCTS needs to be known +# at configure time but the files are generated at build time. +set(lldb_rpc_gen_byproducts + ${lldb_rpc_generated_dir}/SBClasses.def + ${lldb_rpc_generated_dir}/SBAPI.def + ${lldb_rpc_generated_dir}/lldb.py + ${lldb_rpc_server_generated_source_dir}/SBAPI.h +) + +set(lldb_rpc_gen_server_impl_files) +foreach(path ${api_headers}) + get_filename_component(filename_no_ext ${path} NAME_WLE) + + set(server_header_file "Server_${filename_no_ext}.h") + list(APPEND lldb_rpc_gen_byproducts "${lldb_rpc_server_generated_source_dir}/${server_header_file}") + + set(server_impl_file "Server_${filename_no_ext}.cpp") + list(APPEND lldb_rpc_gen_byproducts "${lldb_rpc_server_generated_source_dir}/${server_impl_file}") + list(APPEND lldb_rpc_gen_server_impl_files "${lldb_rpc_server_generated_source_dir}/${server_impl_file}") + +endforeach() + +# Make sure that the clang-resource-dir is set correctly or else the tool will +# fail to run. This is only needed when we do a standalone build. +set(clang_resource_dir_arg) +if (TARGET clang-resource-headers) + set(clang_resource_headers_dir + $<TARGET_PROPERTY:clang-resource-headers,INTERFACE_INCLUDE_DIRECTORIES>) + set(clang_resource_dir_arg --extra-arg="-resource-dir=${clang_resource_headers_dir}/..") +else() + set(clang_resource_dir_arg --extra-arg="-resource-dir=${LLDB_EXTERNAL_CLANG_RESOURCE_DIR}") +endif() + +add_custom_command(OUTPUT ${lldb_rpc_gen_byproducts} + COMMAND ${CMAKE_COMMAND} -E make_directory + ${lldb_rpc_generated_dir} + + COMMAND ${CMAKE_COMMAND} -E make_directory + ${lldb_rpc_server_generated_source_dir} + + COMMAND ${LLDB_RPC_GEN_EXE} + -p ${CMAKE_BINARY_DIR} + --output-dir=${lldb_rpc_generated_dir} + ${clang_resource_dir_arg} + --extra-arg="-USWIG" + ${api_headers} + + DEPENDS ${LLDB_RPC_GEN_EXE} ${api_headers} + COMMENT "Generating sources for lldb-rpc-server..." + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) + +add_custom_target(lldb-rpc-generate-sources + DEPENDS + ${lldb_rpc_gen_byproducts} + lldb-sbapi-dwarf-enums) + +add_dependencies(lldb-rpc-generate-sources clang-resource-headers) diff --git a/lldb/tools/lldb-rpc/LLDBRPCHeaders.cmake b/lldb/tools/lldb-rpc/LLDBRPCHeaders.cmake new file mode 100644 index 0000000000000..97ad481140248 --- /dev/null +++ b/lldb/tools/lldb-rpc/LLDBRPCHeaders.cmake @@ -0,0 +1,101 @@ +set(derived_headers_location "${CMAKE_CURRENT_BINARY_DIR}/DerivedHeaders") + +# Obtain the original headers from their staged location in the build directory. +set(original_headers_location "${CMAKE_BINARY_DIR}/include/lldb") +set(headers_to_process + SBDefines.h + lldb-defines.h + lldb-enumerations.h + lldb-types.h +) + +file(MAKE_DIRECTORY ${derived_headers_location}) + +# Take the original headers and convert them RPC as necessary using the conversion script. +set(original_headers) +set(derived_headers) +foreach(header ${headers_to_process}) + set(original_header "${original_headers_location}/${header}") + + get_filename_component(header_filename ${header} NAME) + string(REPLACE "lldb-" "lldb-rpc-" rpc_header_filename "${header_filename}") + set(derived_header "${derived_headers_location}/${rpc_header_filename}") + + list(APPEND original_headers "${original_header}") + list(APPEND derived_headers "${derived_header}") + add_custom_command(OUTPUT ${derived_header} + COMMAND ${Python3_EXECUTABLE} ${LLDB_SOURCE_DIR}/scripts/convert-lldb-header-to-rpc-header.py + ${original_header} ${derived_header} + DEPENDS ${original_header} + + COMMENT "Creating ${derived_header}" + ) +endforeach() + +# Do the same thing for any header files that were autogenerated. +set(generated_headers_to_process + API/SBLanguages.h +) +foreach(header ${generated_headers_to_process}) + set(original_header "${LLDB_OBJ_DIR}/include/lldb/${header}") + + get_filename_component(header_filename ${header} NAME) + string(REPLACE "lldb-" "lldb-rpc-" rpc_header_filename "${header_filename}") + set(derived_header "${derived_headers_location}/${rpc_header_filename}") + + list(APPEND original_headers "${original_header}") + list(APPEND derived_headers "${derived_header}") + add_custom_command(OUTPUT ${derived_header} + COMMAND ${CMAKE_COMMAND} -E copy ${original_header} ${derived_header} + COMMAND ${Python3_EXECUTABLE} ${LLDB_SOURCE_DIR}/scripts/convert-lldb-header-to-rpc-header.py + ${original_header} ${derived_header} + DEPENDS lldb-sbapi-dwarf-enums + + COMMENT "Creating ${derived_header}" + ) +endforeach() + +add_custom_target(copy-aux-rpc-headers DEPENDS ${derived_headers}) +add_dependencies(copy-aux-rpc-headers liblldb-header-staging) + +list(APPEND public_headers + ${derived_headers_location}/SBDefines.h + ${derived_headers_location}/SBLanguages.h + ${derived_headers_location}/lldb-rpc-enumerations.h + ${derived_headers_location}/lldb-rpc-types.h + ${derived_headers_location}/lldb-rpc-defines.h +) + +# Collect and preprocess headers for the framework bundle +set(version_header + ${derived_headers_location}/lldb-rpc-defines.h +) + +function(FixIncludePaths in subfolder out) + get_filename_component(base_name ${in} NAME) + set(parked_header ${CMAKE_CURRENT_BINARY_DIR}/ParkedHeaders/${subfolder}/${base_name}) + set(${out} ${parked_header} PARENT_SCOPE) + find_program(unifdef_EXECUTABLE unifdef) + + add_custom_command(OUTPUT ${parked_header} + COMMAND ${LLDB_SOURCE_DIR}/scripts/framework-header-fix.py + -f lldb_rpc -i ${in} -o ${parked_header} -p ${unifdef_EXECUTABLE} USWIG + DEPENDS ${in} + COMMENT "Fixing includes in ${in}" + ) +endfunction() + +set(preprocessed_headers) + +# Apply include-paths fix and any version fix on all headers and park them. +foreach(source_header ${public_headers}) + FixIncludePaths(${source_header} Headers parked_header) + list(APPEND preprocessed_headers ${parked_header}) +endforeach() + +# Wrap header preprocessing in a target, so liblldbrpc can depend on. +add_custom_target(liblldbrpc-headers DEPENDS ${preprocessed_headers}) +add_dependencies(liblldbrpc-headers copy-aux-rpc-headers liblldb-header-staging) +set_target_properties(liblldbrpc-headers PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ParkedHeaders +) diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/CMakeLists.txt b/lldb/tools/lldb-rpc/lldb-rpc-gen/CMakeLists.txt new file mode 100644 index 0000000000000..65b76431d1bea --- /dev/null +++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/CMakeLists.txt @@ -0,0 +1,23 @@ +add_lldb_tool(lldb-rpc-gen + RPCCommon.cpp + server/RPCServerHeaderEmitter.cpp + server/RPCServerSourceEmitter.cpp + lldb-rpc-gen.cpp + + CLANG_LIBS + clangAST + clangBasic + clangCodeGen + clangFrontend + clangLex + clangRewrite + clangSerialization + clangTooling + + LINK_COMPONENTS + Support + ) + +if (NOT DEFINED LLDB_RPC_GEN_EXE) + set(LLDB_RPC_GEN_EXE $<TARGET_FILE:lldb-rpc-gen> CACHE STRING "Executable that generates lldb-rpc-server") +endif() diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.cpp b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.cpp new file mode 100644 index 0000000000000..37831a0cf195c --- /dev/null +++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.cpp @@ -0,0 +1,501 @@ +//===-- RPCCommon.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 "RPCCommon.h" + +#include "clang/AST/AST.h" +#include "clang/AST/Attr.h" +#include "clang/AST/DeclBase.h" +#include "clang/AST/Mangle.h" +#include "clang/Lex/Lexer.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" + +#include <cstring> + +using namespace clang; + +// We intentionally do not generate some classes because they are currently +// inconvenient, they aren't really used by most consumers, or we're not sure +// why they exist. +static constexpr llvm::StringRef DisallowedClasses[] = { + "SBCommunication", // This class is pretty much unused by consumers, so we + // skip it. + "SBInputReader", // This class is pretty much unused by consumers, so we + // skip it. + "SBCommandPluginInterface", // This class uses virtual functions, and the SB + // API should not have those, so we skip this + // class. + "SBCommand", // There's nothing too diff icult about this one, but many of + // its methods take a SBCommandPluginInterface pointer so + // there's no reason to support this. +}; + +// NOTE: In lldb-rpc-gen, we use mangled names when we need to work with +// functions. We do this because we support many functions that have overloads, +// and mangled names have no ambiguity which makes it easier to keep track of. +// This is also possible since the LLDB SB API is stable. + +// We intentionally avoid generating certain methods either because they are +// diff icult to support correctly or they aren't really used much from C++. +// NOTE: These methods are marked as deprecated using LLDB_DEPRECATED. +// Normally this macro defines to the deprecated annotation, but this +// functionality is removed in SBDefines.h when generating SWIG bindings which +// we use for testing. Because of this, there is no annotation for the tool to +// pick up on so this list will be used while we have this restriction in +// SBDefines.h. +static constexpr llvm::StringRef DisallowedMethods[] = { + // The threading functionality in SBHostOS is deprecated and thus we do not + // generate them. It would be ideal to add the annotations to the methods + // and then support not generating deprecated methods. However, without + // annotations the generator generates most things correctly. This one is + // problematic because it returns a pointer to an "opaque" structure + // (thread_t) that is not `void *`, so special casing it is more effort than + // it's worth. + "_ZN4lldb8SBHostOS10ThreadJoinEP17_opaque_pthread_tPPvPNS_7SBErrorE", + "_ZN4lldb8SBHostOS12ThreadCancelEP17_opaque_pthread_tPNS_7SBErrorE", + "_ZN4lldb8SBHostOS12ThreadCreateEPKcPFPvS3_ES3_PNS_7SBErrorE", + "_ZN4lldb8SBHostOS12ThreadDetachEP17_opaque_pthread_tPNS_7SBErrorE", + "_ZN4lldb8SBHostOS13ThreadCreatedEPKc", +}; + +static constexpr llvm::StringRef ClassesWithoutDefaultCtor[] = { + "SBHostOS", + "SBReproducer", +}; + +static constexpr llvm::StringRef ClassesWithoutCopyOperations[] = { + "SBHostOS", + "SBReproducer", + "SBStream", + "SBProgress", +}; + +static constexpr llvm::StringRef MethodsWithPointerPlusLen[] = { + "_ZN4lldb6SBData11ReadRawDataERNS_7SBErrorEyPvm", + "_ZN4lldb6SBData7SetDataERNS_7SBErrorEPKvmNS_9ByteOrderEh", + "_ZN4lldb6SBData20SetDataWithOwnershipERNS_7SBErrorEPKvmNS_9ByteOrderEh", + "_ZN4lldb6SBData25CreateDataFromUInt64ArrayENS_9ByteOrderEjPym", + "_ZN4lldb6SBData25CreateDataFromUInt32ArrayENS_9ByteOrderEjPjm", + "_ZN4lldb6SBData25CreateDataFromSInt64ArrayENS_9ByteOrderEjPxm", + "_ZN4lldb6SBData25CreateDataFromSInt32ArrayENS_9ByteOrderEjPim", + "_ZN4lldb6SBData25CreateDataFromDoubleArrayENS_9ByteOrderEjPdm", + "_ZN4lldb6SBData22SetDataFromUInt64ArrayEPym", + "_ZN4lldb6SBData22SetDataFromUInt32ArrayEPjm", + "_ZN4lldb6SBData22SetDataFromSInt64ArrayEPxm", + "_ZN4lldb6SBData22SetDataFromSInt32ArrayEPim", + "_ZN4lldb6SBData22SetDataFromDoubleArrayEPdm", + "_ZN4lldb10SBDebugger22GetDefaultArchitectureEPcm", + "_ZN4lldb10SBDebugger13DispatchInputEPvPKvm", + "_ZN4lldb10SBDebugger13DispatchInputEPKvm", + "_ZN4lldb6SBFile4ReadEPhmPm", + "_ZN4lldb6SBFile5WriteEPKhmPm", + "_ZNK4lldb10SBFileSpec7GetPathEPcm", + "_ZN4lldb10SBFileSpec11ResolvePathEPKcPcm", + "_ZN4lldb8SBModule10GetVersionEPjj", + "_ZN4lldb12SBModuleSpec12SetUUIDBytesEPKhm", + "_ZNK4lldb9SBProcess9GetSTDOUTEPcm", + "_ZNK4lldb9SBProcess9GetSTDERREPcm", + "_ZNK4lldb9SBProcess19GetAsyncProfileDataEPcm", + "_ZN4lldb9SBProcess10ReadMemoryEyPvmRNS_7SBErrorE", + "_ZN4lldb9SBProcess11WriteMemoryEyPKvmRNS_7SBErrorE", + "_ZN4lldb9SBProcess21ReadCStringFromMemoryEyPvmRNS_7SBErrorE", + "_ZNK4lldb16SBStructuredData14GetStringValueEPcm", + "_ZN4lldb8SBTarget23BreakpointCreateByNamesEPPKcjjRKNS_" + "14SBFileSpecListES6_", + "_ZN4lldb8SBTarget10ReadMemoryENS_9SBAddressEPvmRNS_7SBErrorE", + "_ZN4lldb8SBTarget15GetInstructionsENS_9SBAddressEPKvm", + "_ZN4lldb8SBTarget25GetInstructionsWithFlavorENS_9SBAddressEPKcPKvm", + "_ZN4lldb8SBTarget15GetInstructionsEyPKvm", + "_ZN4lldb8SBTarget25GetInstructionsWithFlavorEyPKcPKvm", + "_ZN4lldb8SBThread18GetStopDescriptionEPcm", + // The below mangled names are used for dummy methods in shell tests + // that test the emitters' output. If you're adding any new mangled names + // from the actual SB API to this list please add them above. + "_ZN4lldb33SBRPC_" + "CHECKCONSTCHARPTRPTRWITHLEN27CheckConstCharPtrPtrWithLenEPPKcm", + "_ZN4lldb19SBRPC_CHECKARRAYPTR13CheckArrayPtrEPPKcm", + "_ZN4lldb18SBRPC_CHECKVOIDPTR12CheckVoidPtrEPvm", +}; + +// These classes inherit from rpc::ObjectRef directly (as opposed to +// rpc::LocalObjectRef). Changing them from ObjectRef to LocalObjectRef is ABI +// breaking, so we preserve that compatibility here. +// +// lldb-rpc-gen emits classes as LocalObjectRefs by default. +// +// FIXME: Does it matter which one it emits by default? +static constexpr llvm::StringRef ClassesThatInheritFromObjectRef[] = { + "SBAddress", + "SBBreakpointName", + "SBCommandInterpreter", + "SBCommandReturnObject", + "SBError", + "SBExecutionContext", + "SBExpressionOptions", + "SBFileSpec", + "SBFileSpecList", + "SBFormat", + "SBFunction", + "SBHistoricalFrame", + "SBHistoricalLineEntry", + "SBHistoricalLineEntryList", + "SBLineEntry", + "SBStream", + "SBStringList", + "SBStructuredData", + "SBSymbolContext", + "SBSymbolContextList", + "SBTypeMember", + "SBTypeSummaryOptions", + "SBValueList", +}; + +QualType lldb_rpc_gen::GetUnderlyingType(QualType T) { + QualType UnderlyingType; + if (T->isPointerType()) + UnderlyingType = T->getPointeeType(); + else if (T->isReferenceType()) + UnderlyingType = T.getNonReferenceType(); + else + UnderlyingType = T; + + return UnderlyingType; +} + +QualType lldb_rpc_gen::GetUnqualifiedUnderlyingType(QualType T) { + return GetUnderlyingType(T).getUnqualifiedType(); +} + +std::string lldb_rpc_gen::GetMangledName(ASTContext &Context, + CXXMethodDecl *MDecl) { + std::string Mangled; + llvm::raw_string_ostream MangledStream(Mangled); + + GlobalDecl GDecl; + if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(MDecl)) + GDecl = GlobalDecl(CtorDecl, Ctor_Complete); + else if (const auto *DtorDecl = dyn_cast<CXXDestructorDecl>(MDecl)) + GDecl = GlobalDecl(DtorDecl, Dtor_Deleting); + else + GDecl = GlobalDecl(MDecl); + + MangleContext *MC = Context.createMangleContext(); + MC->mangleName(GDecl, MangledStream); + return Mangled; +} + +static auto CheckTypeForLLDBPrivate = [](const Type *Ty) {}; +bool lldb_rpc_gen::TypeIsFromLLDBPrivate(QualType T) { + auto CheckTypeForLLDBPrivate = [](const Type *Ty) { + if (!Ty) + return false; + const auto *CXXRDecl = Ty->getAsCXXRecordDecl(); + if (!CXXRDecl) + return false; + const auto *NSDecl = + llvm::dyn_cast<NamespaceDecl>(CXXRDecl->getDeclContext()); + if (!NSDecl) + return false; + return NSDecl->getName() == "lldb_private"; + }; + + // First, get the underlying type (remove qualifications and strip off any + // pointers/references). Then we'll need to desugar this type. This will + // remove things like typedefs, so instead of seeing "lldb::DebuggerSP" we'll + // actually see something like "std::shared_ptr<lldb_private::Debugger>". + QualType UnqualifiedUnderlyingType = GetUnqualifiedUnderlyingType(T); + const Type *DesugaredType = + UnqualifiedUnderlyingType->getUnqualifiedDesugaredType(); + assert(DesugaredType && "DesugaredType from a valid Type is nullptr!"); + + // Check the type itself. + if (CheckTypeForLLDBPrivate(DesugaredType)) + return true; + + // If that didn't work, it's possible that the type has a template argument + // that is an lldb_private type. + if (const auto *TemplateSDecl = + llvm::dyn_cast_or_null<ClassTemplateSpecializationDecl>( + DesugaredType->getAsCXXRecordDecl())) { + for (const TemplateArgument &TA : + TemplateSDecl->getTemplateArgs().asArray()) { + if (TA.getKind() != TemplateArgument::Type) + continue; + if (CheckTypeForLLDBPrivate(TA.getAsType().getTypePtr())) + return true; + } + } + return false; +} + +bool lldb_rpc_gen::TypeIsSBClass(QualType T) { + QualType UnqualifiedUnderlyingType = GetUnqualifiedUnderlyingType(T); + const auto *CXXRDecl = UnqualifiedUnderlyingType->getAsCXXRecordDecl(); + if (!CXXRDecl) + return false; // SB Classes are always C++ classes + + return CXXRDecl->getName().starts_with("SB"); +} + +bool lldb_rpc_gen::TypeIsConstCharPtr(QualType T) { + if (!T->isPointerType()) + return false; + + QualType UnderlyingType = T->getPointeeType(); + if (!UnderlyingType.isConstQualified()) + return false; + + // NOTE: We should be able to do `UnderlyingType->isCharType` but that will + // return true for `const uint8_t *` since that is effectively an unsigned + // char pointer. We currently do not support pointers other than `const char + // *` and `const char **`. + return UnderlyingType->isSpecificBuiltinType(BuiltinType::Char_S) || + UnderlyingType->isSpecificBuiltinType(BuiltinType::SChar); +} + +bool lldb_rpc_gen::TypeIsConstCharPtrPtr(QualType T) { + if (!T->isPointerType()) + return false; + + return TypeIsConstCharPtr(T->getPointeeType()); +} + +bool lldb_rpc_gen::TypeIsDisallowedClass(QualType T) { + QualType UUT = GetUnqualifiedUnderlyingType(T); + const auto *CXXRDecl = UUT->getAsCXXRecordDecl(); + if (!CXXRDecl) + return false; + + llvm::StringRef DeclName = CXXRDecl->getName(); + for (const llvm::StringRef DisallowedClass : DisallowedClasses) + if (DeclName == DisallowedClass) + return true; + return false; +} + +bool lldb_rpc_gen::TypeIsCallbackFunctionPointer(QualType T) { + return T->isFunctionPointerType(); +} + +bool lldb_rpc_gen::MethodIsDisallowed(ASTContext &Context, + CXXMethodDecl *MDecl) { + bool isDisallowed = false; + std::string MangledName = lldb_rpc_gen::GetMangledName(Context, MDecl); + if (llvm::is_contained(DisallowedMethods, MangledName)) + isDisallowed = true; + + if (MDecl->hasAttrs()) { + for (auto *attr : MDecl->getAttrs()) { + if (strcmp(attr->getAttrName()->getNameStart(), "deprecated") == 0) + isDisallowed = true; + } + } + return isDisallowed; +} + +bool lldb_rpc_gen::HasCallbackParameter(CXXMethodDecl *MDecl) { + bool HasCallbackParameter = false; + bool HasBatonParameter = false; + auto End = MDecl->parameters().end(); + for (auto Iter = MDecl->parameters().begin(); Iter != End; Iter++) { + if ((*Iter)->getType()->isFunctionPointerType()) + HasCallbackParameter = true; + else if ((*Iter)->getType()->isVoidPointerType()) + HasBatonParameter = true; + } + + return HasCallbackParameter && HasBatonParameter; +} + +// NOTE: There's possibly a more clever way to do this, but we're keeping +// the string replacement way here. Here is why it is written this way: +// By the time we have already created a `Method` object, we have extracted the +// `QualifiedName` and the relevant QualTypes for parameters/return types, many +// of which contains "lldb::" in them. To change it in a way that would be +// friendly to liblldbrpc, we would need to have a way of replacing that +// namespace at the time of creating a Method, and only for liblldbrpc methods. +// IMO this would complicate Method more than what I'm doing here, and not +// necessarily for any more benefit. +// In clang-tools-extra, there is a ChangeNamespaces tool which tries to do +// something similar to this. It also operates primarily on string replacement, +// but uses more sophisticated clang tooling to do so. +// For now, this will do what we need it to do. +std::string +lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace(std::string Name) { + const char *lldb_namespace = "lldb::"; + auto Pos = Name.find(lldb_namespace); + while (Pos != std::string::npos) { + constexpr size_t SizeOfLLDBNamespace = 6; + Name.replace(Pos, SizeOfLLDBNamespace, "lldb_rpc::"); + Pos = Name.find(lldb_namespace); + } + return Name; +} + +std::string lldb_rpc_gen::StripLLDBNamespace(std::string Name) { + const char *lldb_namespace = "lldb::"; + auto Pos = Name.find(lldb_namespace); + if (Pos != std::string::npos) { + constexpr size_t SizeOfLLDBNamespace = 6; + Name = Name.substr(Pos + SizeOfLLDBNamespace); + } + return Name; +} + +bool lldb_rpc_gen::SBClassRequiresDefaultCtor(const std::string &ClassName) { + return !llvm::is_contained(ClassesWithoutDefaultCtor, ClassName); +} + +bool lldb_rpc_gen::SBClassRequiresCopyCtorAssign(const std::string &ClassName) { + return !llvm::is_contained(ClassesWithoutCopyOperations, ClassName); +} + +bool lldb_rpc_gen::SBClassInheritsFromObjectRef(const std::string &ClassName) { + return llvm::is_contained(ClassesThatInheritFromObjectRef, ClassName); +} + +std::string lldb_rpc_gen::GetSBClassNameFromType(QualType T) { + assert(lldb_rpc_gen::TypeIsSBClass(T) && + "Cannot get SBClass name from non-SB class type!"); + + QualType UnqualifiedUnderlyingType = GetUnqualifiedUnderlyingType(T); + const auto *CXXRDecl = UnqualifiedUnderlyingType->getAsCXXRecordDecl(); + assert(CXXRDecl && "SB class was not CXXRecordDecl!"); + if (!CXXRDecl) + return std::string(); + + return CXXRDecl->getName().str(); +} +lldb_rpc_gen::Method::Method(CXXMethodDecl *MDecl, const PrintingPolicy &Policy, + ASTContext &Context) + : Policy(Policy), Context(Context), + QualifiedName(MDecl->getQualifiedNameAsString()), + BaseName(MDecl->getNameAsString()), + MangledName(lldb_rpc_gen::GetMangledName(Context, MDecl)), + ReturnType(MDecl->getReturnType()), IsConst(MDecl->isConst()), + IsInstance(MDecl->isInstance()), IsCtor(isa<CXXConstructorDecl>(MDecl)), + IsCopyAssign(MDecl->isCopyAssignmentOperator()), + IsMoveAssign(MDecl->isMoveAssignmentOperator()), + IsDtor(isa<CXXDestructorDecl>(MDecl)), + IsConversionMethod(isa<CXXConversionDecl>(MDecl)) { + uint8_t UnnamedArgIdx = 0; + bool PrevParamWasPointer = false; + for (const auto *ParamDecl : MDecl->parameters()) { + Param param; + if (ParamDecl->hasDefaultArg()) + param.DefaultValueText = + Lexer::getSourceText( + CharSourceRange::getTokenRange( + ParamDecl->getDefaultArg()->getSourceRange()), + Context.getSourceManager(), Context.getLangOpts()) + .str(); + + param.IsFollowedByLen = false; + param.Name = ParamDecl->getNameAsString(); + // If the parameter has no name, we'll generate one + if (param.Name.empty()) { + param.Name = "arg" + std::to_string(UnnamedArgIdx); + UnnamedArgIdx++; + } + param.Type = ParamDecl->getType(); + + // FIXME: Instead of using this heuristic, the ideal thing would be to add + // annotations to the SBAPI methods themselves. For now, we have a list of + // methods that we know will need this. + if (PrevParamWasPointer) { + PrevParamWasPointer = false; + const bool IsIntegerType = param.Type->isIntegerType() && + !param.Type->isBooleanType() && + !param.Type->isEnumeralType(); + if (IsIntegerType && llvm::is_contained(MethodsWithPointerPlusLen, + llvm::StringRef(MangledName))) + Params.back().IsFollowedByLen = true; + } + + if (param.Type->isPointerType() && + !lldb_rpc_gen::TypeIsConstCharPtr(param.Type) && + !param.Type->isFunctionPointerType()) + PrevParamWasPointer = true; + + if (param.Type->isFunctionPointerType()) + ContainsFunctionPointerParameter = true; + + Params.push_back(param); + } + + if (IsInstance) + ThisType = MDecl->getThisType(); + + if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(MDecl)) { + IsExplicitCtorOrConversionMethod = CtorDecl->isExplicit(); + IsCopyCtor = CtorDecl->isCopyConstructor(); + IsMoveCtor = CtorDecl->isMoveConstructor(); + } else if (const auto *ConversionDecl = dyn_cast<CXXConversionDecl>(MDecl)) + IsExplicitCtorOrConversionMethod = ConversionDecl->isExplicit(); +} + +// Adding a '<' allows us to use Methods in ordered containers. +// The ordering is on memory addresses. +bool lldb_rpc_gen::Method::operator<(const lldb_rpc_gen::Method &rhs) const { + return this < &rhs; +} + +std::string +lldb_rpc_gen::Method::CreateParamListAsString(GenerationKind Generation, + bool IncludeDefaultValue) const { + assert((!IncludeDefaultValue || Generation == eLibrary) && + "Default values should only be emitted on the library side!"); + + std::vector<std::string> ParamList; + + if (Generation == eLibrary && RequiresConnectionParameter()) + ParamList.push_back("const rpc::Connection &connection"); + + for (const auto &Param : Params) { + std::string ParamString; + llvm::raw_string_ostream ParamStringStream(ParamString); + + if (Generation == eLibrary) + ParamStringStream << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace( + Param.Type.getAsString(Policy)); + else + ParamStringStream << Param.Type.getAsString(Policy); + + ParamStringStream << " " << Param.Name; + if (IncludeDefaultValue && Generation == eLibrary && + !Param.DefaultValueText.empty()) + ParamStringStream << " = " + << lldb_rpc_gen::ReplaceLLDBNamespaceWithRPCNamespace( + Param.DefaultValueText); + + ParamList.push_back(ParamString); + } + + return llvm::join(ParamList, ", "); +} + +bool lldb_rpc_gen::Method::RequiresConnectionParameter() const { + if (!IsCtor && IsInstance) + return false; + if (IsCopyCtor || IsMoveCtor) + return false; + for (const auto &Param : Params) + // We can re-use the connection from our parameter if possible. + // Const-qualified parameters are input parameters and already + // have a valid connection to provide to the current method. + if (TypeIsSBClass(Param.Type) && + GetUnderlyingType(Param.Type).isConstQualified()) + return false; + + return true; +} diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.h b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.h new file mode 100644 index 0000000000000..edc03b4f81a3d --- /dev/null +++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/RPCCommon.h @@ -0,0 +1,108 @@ +//===-- RPCCommon.h -------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_RPC_GEN_RPCCOMMON_H +#define LLDB_RPC_GEN_RPCCOMMON_H + +#include "clang/AST/AST.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/raw_ostream.h" + +#include <string> + +using namespace clang; + +namespace lldb_rpc_gen { +QualType GetUnderlyingType(QualType T); +QualType GetUnqualifiedUnderlyingType(QualType T); +std::string GetMangledName(ASTContext &Context, CXXMethodDecl *MDecl); + +bool TypeIsFromLLDBPrivate(QualType T); +bool TypeIsSBClass(QualType T); +bool TypeIsConstCharPtr(QualType T); +bool TypeIsConstCharPtrPtr(QualType T); +bool TypeIsDisallowedClass(QualType T); +bool TypeIsCallbackFunctionPointer(QualType T); + +bool MethodIsDisallowed(ASTContext &Context, CXXMethodDecl *MDecl); +bool HasCallbackParameter(CXXMethodDecl *MDecl); + +std::string ReplaceLLDBNamespaceWithRPCNamespace(std::string Name); +std::string StripLLDBNamespace(std::string Name); +bool SBClassRequiresDefaultCtor(const std::string &ClassName); +bool SBClassRequiresCopyCtorAssign(const std::string &ClassName); +bool SBClassInheritsFromObjectRef(const std::string &ClassName); +std::string GetSBClassNameFromType(QualType T); +struct Param { + std::string Name; + QualType Type; + std::string DefaultValueText; + bool IsFollowedByLen; +}; + +enum GenerationKind : bool { eServer, eLibrary }; + +struct Method { + enum Type { eOther, eConstructor, eDestructor }; + + Method(CXXMethodDecl *MDecl, const PrintingPolicy &Policy, + ASTContext &Context); + + // Adding a '<' allows us to use Methods in ordered containers. + // The ordering is on memory addresses. + bool operator<(const lldb_rpc_gen::Method &rhs) const; + const PrintingPolicy &Policy; + const ASTContext &Context; + std::string QualifiedName; + std::string BaseName; + std::string MangledName; + QualType ReturnType; + QualType ThisType; + std::vector<Param> Params; + bool IsConst = false; + bool IsInstance = false; + bool IsCtor = false; + bool IsCopyCtor = false; + bool IsCopyAssign = false; + bool IsMoveCtor = false; + bool IsMoveAssign = false; + bool IsDtor = false; + bool IsConversionMethod = false; + bool IsExplicitCtorOrConversionMethod = false; + bool ContainsFunctionPointerParameter = false; + + std::string CreateParamListAsString(GenerationKind Generation, + bool IncludeDefaultValue = false) const; + + bool RequiresConnectionParameter() const; +}; + +std::string +GetDefaultArgumentsForConstructor(std::string ClassName, + const lldb_rpc_gen::Method &method); + +class FileEmitter { +protected: + FileEmitter(std::unique_ptr<llvm::ToolOutputFile> &&OutputFile) + : OutputFile(std::move(OutputFile)), IndentLevel(0) {} + void EmitLine(const std::string &line) { + for (auto i = 0; i < IndentLevel; i++) + OutputFile->os() << " "; + + OutputFile->os() << line << "\n"; + } + + void EmitNewLine() { OutputFile->os() << "\n"; } + + std::unique_ptr<llvm::ToolOutputFile> OutputFile; + uint8_t IndentLevel; +}; +} // namespace lldb_rpc_gen +#endif // LLDB_RPC_GEN_RPCCOMMON_H diff --git a/lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp b/lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp index e6b601ea13012..fdcfee96a387e 100644 --- a/lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp +++ b/lldb/tools/lldb-rpc/lldb-rpc-gen/lldb-rpc-gen.cpp @@ -7,8 +7,8 @@ //===----------------------------------------------------------------------===// #include "RPCCommon.h" -#include "RPCServerHeaderEmitter.h" -#include "RPCServerSourceEmitter.h" +#include "server/RPCServerHeaderEmitter.h" +#include "server/RPCServerSourceEmitter.h" #include "clang/AST/AST.h" #include "clang/AST/ASTConsumer.h" @@ -40,15 +40,15 @@ static llvm::cl::opt<std::string> llvm::cl::desc("Directory to output generated files to"), llvm::cl::init(""), llvm::cl::cat(RPCGenCategory)); -static std::string GetLibraryOutputDirectory() { +static std::string GetServerOutputDirectory() { llvm::SmallString<128> Path(OutputDir.getValue()); - llvm::sys::path::append(Path, "lib"); + llvm::sys::path::append(Path, "server"); return std::string(Path); } static std::unique_ptr<llvm::ToolOutputFile> CreateOutputFile(llvm::StringRef OutputDir, llvm::StringRef Filename) { - llvm::SmallString<128> Path(OutputDir); + llvm::SmallString<256> Path(OutputDir); llvm::sys::path::append(Path, Filename); std::error_code EC; @@ -100,7 +100,8 @@ class SBVisitor : public RecursiveASTVisitor<SBVisitor> { for (CXXMethodDecl *MDecl : RDecl->methods()) { const std::string MangledName = lldb_rpc_gen::GetMangledName(Context, MDecl); - const bool IsDisallowed = lldb_rpc_gen::MethodIsDisallowed(MangledName); + const bool IsDisallowed = + lldb_rpc_gen::MethodIsDisallowed(Context, MDecl); const bool HasCallbackParameter = lldb_rpc_gen::HasCallbackParameter(MDecl); SupportLevel MethodSupportLevel = GetMethodSupportLevel(MDecl); @@ -314,9 +315,9 @@ bool EmitClassNamesFile(std::set<std::string> &ClassNames) { if (!ClassNamesFile) return false; - ClassNamesFile->os() << "#ifndef SBCLASS\n"; - ClassNamesFile->os() << "#error \"SBClass must be defined\"\n"; - ClassNamesFile->os() << "#endif\n"; + ClassNamesFile->os() << "#ifndef SBCLASS\n" + << "#error \"SBClass must be defined\"\n" + << "#endif\n"; for (const auto &ClassName : ClassNames) { if (ClassName == "SBStream" || ClassName == "SBProgress") @@ -340,9 +341,9 @@ bool EmitMethodNamesFile(std::set<std::string> &MangledMethodNames) { if (!MethodNamesFile) return false; - MethodNamesFile->os() << "#ifndef GENERATE_SBAPI\n"; - MethodNamesFile->os() << "#error \"GENERATE_SBAPI must be defined\"\n"; - MethodNamesFile->os() << "#endif\n"; + MethodNamesFile->os() << "#ifndef GENERATE_SBAPI\n" + << "#error \"GENERATE_SBAPI must be defined\"\n" + << "#endif\n"; for (const auto &MangledName : MangledMethodNames) { MethodNamesFile->os() << "GENERATE_SBAPI(" << MangledName << ")\n"; @@ -358,9 +359,8 @@ bool EmitSkippedMethodsFile(std::set<std::string> &SkippedMethodNames) { if (!File) return false; - for (const auto &Skipped : SkippedMethodNames) { + for (const auto &Skipped : SkippedMethodNames) File->os() << Skipped << "\n"; - } File->keep(); return true; } @@ -381,6 +381,14 @@ int main(int argc, const char *argv[]) { return 1; } + // Create the output directory if the user specified one does not exist. + if (!llvm::sys::fs::exists(OutputDir.getValue())) { + llvm::sys::fs::create_directory(OutputDir.getValue()); + } + + if (!llvm::sys::fs::exists(GetServerOutputDirectory())) { + llvm::sys::fs::create_directory(GetServerOutputDirectory()); + } CommonOptionsParser &OP = ExpectedParser.get(); auto PCHOpts = std::make_shared<PCHContainerOperations>(); PCHOpts->registerWriter(std::make_unique<ObjectFilePCHContainerWriter>()); _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits