llvm-project/clang/lib/Tooling/ExpandResponseFilesCompilationDatabase.cpp
Dmitry Polukhin fceaf86211 [clang] Fix UB when string.front() is used for the empty string
Compilation database might have empty string as a command line argument.
But ExpandResponseFilesDatabase::expand doesn't expect this and assumes
that string.front() can be used for any argument. It is undefined behaviour if
string is empty. With debug build mode it causes crash in clangd.

Test Plan: check-clang

Differential Revision: https://reviews.llvm.org/D105120
2021-06-30 01:07:47 -07:00

93 lines
3.1 KiB
C++

//===- ExpandResponseFileCompilationDataBase.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 "clang/Tooling/CompilationDatabase.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Triple.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/StringSaver.h"
namespace clang {
namespace tooling {
namespace {
class ExpandResponseFilesDatabase : public CompilationDatabase {
public:
ExpandResponseFilesDatabase(
std::unique_ptr<CompilationDatabase> Base,
llvm::cl::TokenizerCallback Tokenizer,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)
: Base(std::move(Base)), Tokenizer(Tokenizer), FS(std::move(FS)) {
assert(this->Base != nullptr);
assert(this->Tokenizer != nullptr);
assert(this->FS != nullptr);
}
std::vector<std::string> getAllFiles() const override {
return Base->getAllFiles();
}
std::vector<CompileCommand>
getCompileCommands(StringRef FilePath) const override {
return expand(Base->getCompileCommands(FilePath));
}
std::vector<CompileCommand> getAllCompileCommands() const override {
return expand(Base->getAllCompileCommands());
}
private:
std::vector<CompileCommand> expand(std::vector<CompileCommand> Cmds) const {
for (auto &Cmd : Cmds) {
bool SeenRSPFile = false;
llvm::SmallVector<const char *, 20> Argv;
Argv.reserve(Cmd.CommandLine.size());
for (auto &Arg : Cmd.CommandLine) {
Argv.push_back(Arg.c_str());
if (!Arg.empty())
SeenRSPFile |= Arg.front() == '@';
}
if (!SeenRSPFile)
continue;
llvm::BumpPtrAllocator Alloc;
llvm::StringSaver Saver(Alloc);
llvm::cl::ExpandResponseFiles(Saver, Tokenizer, Argv, false, false,
llvm::StringRef(Cmd.Directory), *FS);
// Don't assign directly, Argv aliases CommandLine.
std::vector<std::string> ExpandedArgv(Argv.begin(), Argv.end());
Cmd.CommandLine = std::move(ExpandedArgv);
}
return Cmds;
}
private:
std::unique_ptr<CompilationDatabase> Base;
llvm::cl::TokenizerCallback Tokenizer;
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS;
};
} // namespace
std::unique_ptr<CompilationDatabase>
expandResponseFiles(std::unique_ptr<CompilationDatabase> Base,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS) {
auto Tokenizer = llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows()
? llvm::cl::TokenizeWindowsCommandLine
: llvm::cl::TokenizeGNUCommandLine;
return std::make_unique<ExpandResponseFilesDatabase>(
std::move(Base), Tokenizer, std::move(FS));
}
} // namespace tooling
} // namespace clang