#include "CxxParser.h" #include #include #include #include #include #include #include #include #include "ApplicationSettings.h" #include "ASTActionFactory.h" #include "CanonicalFilePathCache.h" #include "CxxCompilationDatabaseSingle.h" #include "CxxDiagnosticConsumer.h" #include "FilePath.h" #include "FileRegister.h" #include "IndexerCommandCxx.h" #include "logging.h" #include "ParserClient.h" #include "ResourcePaths.h" #include "TextAccess.h" #include "utilityString.h" #include "utility.h" namespace { struct ClangInvocationInfo { std::string invocation; std::string errors; }; // copied from clang codebase clang::driver::Driver *newDriver( clang::DiagnosticsEngine *Diagnostics, const char *BinaryName, clang::IntrusiveRefCntPtr VFS) { clang::driver::Driver *CompilerDriver = new clang::driver::Driver(BinaryName, llvm::sys::getDefaultTargetTriple(), *Diagnostics, std::move(VFS)); CompilerDriver->setTitle("clang_based_tool"); return CompilerDriver; } // copied and stitched together from clang codebase ClangInvocationInfo getClangInvocationString(const clang::tooling::CompilationDatabase* compilationDatabase) { ClangInvocationInfo invocationInfo; if (!compilationDatabase->getAllCompileCommands().empty()) { std::vector CommandLine = compilationDatabase->getAllCompileCommands().front().CommandLine; std::vector Argv; for (const std::string &Str : CommandLine) Argv.push_back(Str.c_str()); const char *const BinaryName = Argv[0]; clang::IntrusiveRefCntPtr DiagOpts = new clang::DiagnosticOptions(); unsigned MissingArgIndex, MissingArgCount; std::unique_ptr Opts = clang::driver::createDriverOptTable(); llvm::opt::InputArgList ParsedArgs = Opts->ParseArgs( clang::ArrayRef(Argv).slice(1), MissingArgIndex, MissingArgCount); clang::ParseDiagnosticArgs(*DiagOpts, ParsedArgs); llvm::raw_string_ostream diagnosticsStream(invocationInfo.errors); clang::TextDiagnosticPrinter DiagnosticPrinter( diagnosticsStream, &*DiagOpts); clang::DiagnosticsEngine Diagnostics( clang::IntrusiveRefCntPtr(new clang::DiagnosticIDs()), &*DiagOpts, &DiagnosticPrinter, false); llvm::IntrusiveRefCntPtr Files(new clang::FileManager(clang::FileSystemOptions())); const std::unique_ptr Driver( newDriver(&Diagnostics, BinaryName, Files->getVirtualFileSystem())); // Since the input might only be virtual, don't check whether it exists. Driver->setCheckInputsExist(false); const std::unique_ptr Compilation( Driver->BuildCompilation(llvm::makeArrayRef(Argv))); if (Compilation) { llvm::raw_string_ostream ss(invocationInfo.invocation); Compilation->getJobs().Print(ss, "", true); ss.flush(); } diagnosticsStream.flush(); invocationInfo.invocation = utility::trim(invocationInfo.invocation); invocationInfo.errors = utility::trim(invocationInfo.errors); } return invocationInfo; } std::vector prependSyntaxOnlyToolArgs(const std::vector& args) { return utility::concat(std::vector({ "clang-tool", "-fsyntax-only" }), args); } std::vector appendFilePath(const std::vector& args, llvm::StringRef filePath) { return utility::concat(args, { filePath.str() }); } // custom implementation of clang::runToolOnCodeWithArgs which also sets our custon DiagnosticConsumer bool runToolOnCodeWithArgs( clang::DiagnosticConsumer* DiagConsumer, clang::FrontendAction *ToolAction, const llvm::Twine &Code, const std::vector &Args, const llvm::Twine &FileName = "input.cc", const clang::tooling::FileContentMappings &VirtualMappedFiles = clang::tooling::FileContentMappings() ) { llvm::SmallString<16> FileNameStorage; llvm::StringRef FileNameRef = FileName.toNullTerminatedStringRef(FileNameStorage); llvm::IntrusiveRefCntPtr OverlayFileSystem(new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); llvm::IntrusiveRefCntPtr InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem); OverlayFileSystem->pushOverlay(InMemoryFileSystem); llvm::IntrusiveRefCntPtr Files(new clang::FileManager(clang::FileSystemOptions(), OverlayFileSystem)); clang::tooling::ToolInvocation Invocation(prependSyntaxOnlyToolArgs(appendFilePath(Args, FileNameRef)), ToolAction, Files.get()); llvm::SmallString<1024> CodeStorage; llvm::StringRef CodeRef = Code.toNullTerminatedStringRef(CodeStorage); InMemoryFileSystem->addFile(FileNameRef, 0, llvm::MemoryBuffer::getMemBufferCopy(CodeRef)); Invocation.setDiagnosticConsumer(DiagConsumer); return Invocation.run(); } } CxxParser::CxxParser( std::shared_ptr client, std::shared_ptr fileRegister, std::shared_ptr indexerStateInfo ) : Parser(client) , m_fileRegister(fileRegister) , m_indexerStateInfo(indexerStateInfo) { llvm::InitializeNativeTarget(); llvm::InitializeNativeTargetAsmParser(); } void CxxParser::buildIndex(std::shared_ptr indexerCommand) { clang::tooling::CompileCommand compileCommand; compileCommand.Filename = utility::encodeToUtf8(indexerCommand->getSourceFilePath().wstr()); compileCommand.Directory = utility::encodeToUtf8(indexerCommand->getWorkingDirectory().wstr()); std::vector args = indexerCommand->getCompilerFlags(); if (!args.empty() && !utility::isPrefix(L"-", args.front())) { args.erase(args.begin()); } compileCommand.CommandLine = getCommandlineArgumentsEssential(args); compileCommand.CommandLine = prependSyntaxOnlyToolArgs(compileCommand.CommandLine); CxxCompilationDatabaseSingle compilationDatabase(compileCommand); runTool(&compilationDatabase, indexerCommand->getSourceFilePath()); } void CxxParser::buildIndex(const std::wstring& fileName, std::shared_ptr fileContent, std::vector compilerFlags) { std::shared_ptr canonicalFilePathCache = std::make_shared(m_fileRegister); std::shared_ptr diagnostics = getDiagnostics(FilePath(), canonicalFilePathCache, false); ASTActionFactory actionFactory(m_client, canonicalFilePathCache, m_indexerStateInfo); std::vector args = getCommandlineArgumentsEssential(compilerFlags); runToolOnCodeWithArgs( diagnostics.get(), actionFactory.create(), fileContent->getText(), args, utility::encodeToUtf8(fileName) ); } void CxxParser::runTool(clang::tooling::CompilationDatabase* compilationDatabase, const FilePath& sourceFilePath) { clang::tooling::ClangTool tool(*compilationDatabase, std::vector(1, utility::encodeToUtf8(sourceFilePath.wstr()))); std::shared_ptr canonicalFilePathCache = std::make_shared(m_fileRegister); std::shared_ptr diagnostics = getDiagnostics(sourceFilePath, canonicalFilePathCache, true); tool.setDiagnosticConsumer(diagnostics.get()); ClangInvocationInfo info; if (LogManager::getInstance()->getLoggingEnabled()) { info = getClangInvocationString(compilationDatabase); LOG_INFO("Clang Invocation: " + info.invocation.substr(0, ApplicationSettings::getInstance()->getVerboseIndexerLoggingEnabled() ? std::string::npos : 20000)); if (!info.errors.empty()) { LOG_INFO("Clang Invocation errors: " + info.errors); } } ASTActionFactory actionFactory(m_client, canonicalFilePathCache, m_indexerStateInfo); tool.run(&actionFactory); if (!m_client->hasContent()) { if (info.invocation.empty()) { info = getClangInvocationString(compilationDatabase); } if (!info.errors.empty()) { Id fileId = m_client->recordFile(sourceFilePath, true); m_client->recordError( L"Clang Invocation errors: " + utility::decodeFromUtf8(info.errors), true, true, sourceFilePath, ParseLocation(fileId, 1, 1) ); } } } std::vector CxxParser::getCommandlineArgumentsEssential(const std::vector& compilerFlags) const { std::vector args; // The option -fno-delayed-template-parsing signals that templates that there should // be AST elements for unused template functions as well. args.push_back("-fno-delayed-template-parsing"); // The option -fexceptions signals that clang should watch out for exception-related code during indexing. args.push_back("-fexceptions"); // The option -c signals that no executable is built. args.push_back("-c"); // The option -w disables all warnings. args.push_back("-w"); // This option tells clang just to continue parsing no matter how manny errors have been thrown. args.push_back("-ferror-limit=0"); for (const std::wstring& compilerFlag: compilerFlags) { args.push_back(utility::encodeToUtf8(compilerFlag)); } return args; } std::shared_ptr CxxParser::getDiagnostics(const FilePath& sourceFilePath, std::shared_ptr canonicalFilePathCache, bool logErrors) const { llvm::IntrusiveRefCntPtr options = new clang::DiagnosticOptions(); return std::make_shared( llvm::errs(), &*options, m_client, canonicalFilePathCache, sourceFilePath, logErrors ); }