152 changes: 87 additions & 65 deletions llvm/lib/Support/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1153,14 +1153,14 @@ static void ExpandBasePaths(StringRef BasePath, StringSaver &Saver,
}

// FName must be an absolute path.
llvm::Error
ExpansionContext::expandResponseFile(StringRef FName,
SmallVectorImpl<const char *> &NewArgv) {
Error ExpansionContext::expandResponseFile(
StringRef FName, SmallVectorImpl<const char *> &NewArgv) {
assert(sys::path::is_absolute(FName));
llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> MemBufOrErr =
FS->getBufferForFile(FName);
if (!MemBufOrErr)
return llvm::errorCodeToError(MemBufOrErr.getError());
return llvm::createStringError(
MemBufOrErr.getError(), Twine("cannot not open file '") + FName + "'");
MemoryBuffer &MemBuf = *MemBufOrErr.get();
StringRef Str(MemBuf.getBufferStart(), MemBuf.getBufferSize());

Expand All @@ -1182,29 +1182,30 @@ ExpansionContext::expandResponseFile(StringRef FName,
// Tokenize the contents into NewArgv.
Tokenizer(Str, Saver, NewArgv, MarkEOLs);

if (!RelativeNames)
// Expanded file content may require additional transformations, like using
// absolute paths instead of relative in '@file' constructs or expanding
// macros.
if (!RelativeNames && !InConfigFile)
return Error::success();
llvm::StringRef BasePath = llvm::sys::path::parent_path(FName);
// If names of nested response files should be resolved relative to including
// file, replace the included response file names with their full paths
// obtained by required resolution.
for (auto &Arg : NewArgv) {
if (!Arg)

StringRef BasePath = llvm::sys::path::parent_path(FName);
for (auto I = NewArgv.begin(), E = NewArgv.end(); I != E; ++I) {
const char *&Arg = *I;
if (Arg == nullptr)
continue;

// Substitute <CFGDIR> with the file's base path.
if (InConfigFile)
ExpandBasePaths(BasePath, Saver, Arg);

// Skip non-rsp file arguments.
if (Arg[0] != '@')
// Get expanded file name.
StringRef FileName(Arg);
if (!FileName.consume_front("@"))
continue;

StringRef FileName(Arg + 1);
// Skip if non-relative.
if (!llvm::sys::path::is_relative(FileName))
continue;

// Update expansion construct.
SmallString<128> ResponseFile;
ResponseFile.push_back('@');
ResponseFile.append(BasePath);
Expand All @@ -1216,9 +1217,8 @@ ExpansionContext::expandResponseFile(StringRef FName,

/// Expand response files on a command line recursively using the given
/// StringSaver and tokenization strategy.
bool ExpansionContext::expandResponseFiles(
Error ExpansionContext::expandResponseFiles(
SmallVectorImpl<const char *> &Argv) {
bool AllExpanded = true;
struct ResponseFileRecord {
std::string File;
size_t End;
Expand Down Expand Up @@ -1262,53 +1262,60 @@ bool ExpansionContext::expandResponseFiles(
if (auto CWD = FS->getCurrentWorkingDirectory()) {
CurrDir = *CWD;
} else {
// TODO: The error should be propagated up the stack.
llvm::consumeError(llvm::errorCodeToError(CWD.getError()));
return false;
return make_error<StringError>(
CWD.getError(), Twine("cannot get absolute path for: ") + FName);
}
} else {
CurrDir = CurrentDir;
}
llvm::sys::path::append(CurrDir, FName);
FName = CurrDir.c_str();
}
auto IsEquivalent = [FName, this](const ResponseFileRecord &RFile) {
llvm::ErrorOr<llvm::vfs::Status> LHS = FS->status(FName);
if (!LHS) {
// TODO: The error should be propagated up the stack.
llvm::consumeError(llvm::errorCodeToError(LHS.getError()));
return false;
}
llvm::ErrorOr<llvm::vfs::Status> RHS = FS->status(RFile.File);
if (!RHS) {
// TODO: The error should be propagated up the stack.
llvm::consumeError(llvm::errorCodeToError(RHS.getError()));
return false;
}
auto IsEquivalent =
[FName, this](const ResponseFileRecord &RFile) -> ErrorOr<bool> {
ErrorOr<llvm::vfs::Status> LHS = FS->status(FName);
if (!LHS)
return LHS.getError();
ErrorOr<llvm::vfs::Status> RHS = FS->status(RFile.File);
if (!RHS)
return RHS.getError();
return LHS->equivalent(*RHS);
};

// Check for recursive response files.
if (any_of(drop_begin(FileStack), IsEquivalent)) {
// This file is recursive, so we leave it in the argument stream and
// move on.
AllExpanded = false;
++I;
continue;
for (const auto &F : drop_begin(FileStack)) {
if (ErrorOr<bool> R = IsEquivalent(F)) {
if (R.get())
return make_error<StringError>(
Twine("recursive expansion of: '") + F.File + "'", R.getError());
} else {
return make_error<StringError>(Twine("cannot open file: ") + F.File,
R.getError());
}
}

// Replace this response file argument with the tokenization of its
// contents. Nested response files are expanded in subsequent iterations.
SmallVector<const char *, 0> ExpandedArgv;
if (llvm::Error Err = expandResponseFile(FName, ExpandedArgv)) {
// We couldn't read this file, so we leave it in the argument stream and
// move on.
// TODO: The error should be propagated up the stack.
llvm::consumeError(std::move(Err));
AllExpanded = false;
++I;
continue;
if (!InConfigFile) {
// If the specified file does not exist, leave '@file' unexpanded, as
// libiberty does.
ErrorOr<llvm::vfs::Status> Res = FS->status(FName);
if (!Res) {
std::error_code EC = Res.getError();
if (EC == llvm::errc::no_such_file_or_directory) {
++I;
continue;
}
} else {
if (!Res->exists()) {
++I;
continue;
}
}
}
if (Error Err = expandResponseFile(FName, ExpandedArgv))
return Err;

for (ResponseFileRecord &Record : FileStack) {
// Increase the end of all active records by the number of newly expanded
Expand All @@ -1327,7 +1334,7 @@ bool ExpansionContext::expandResponseFiles(
// don't have a chance to pop the stack when encountering recursive files at
// the end of the stream, so seeing that doesn't indicate a bug.
assert(FileStack.size() > 0 && Argv.size() == FileStack.back().End);
return AllExpanded;
return Error::success();
}

bool cl::expandResponseFiles(int Argc, const char *const *Argv,
Expand All @@ -1344,7 +1351,21 @@ bool cl::expandResponseFiles(int Argc, const char *const *Argv,
// Command line options can override the environment variable.
NewArgv.append(Argv + 1, Argv + Argc);
ExpansionContext ECtx(Saver.getAllocator(), Tokenize);
return ECtx.expandResponseFiles(NewArgv);
if (Error Err = ECtx.expandResponseFiles(NewArgv)) {
errs() << toString(std::move(Err)) << '\n';
return false;
}
return true;
}

bool cl::ExpandResponseFiles(StringSaver &Saver, TokenizerCallback Tokenizer,
SmallVectorImpl<const char *> &Argv) {
ExpansionContext ECtx(Saver.getAllocator(), Tokenizer);
if (Error Err = ECtx.expandResponseFiles(Argv)) {
errs() << toString(std::move(Err)) << '\n';
return false;
}
return true;
}

ExpansionContext::ExpansionContext(BumpPtrAllocator &A, TokenizerCallback T)
Expand Down Expand Up @@ -1387,22 +1408,20 @@ bool ExpansionContext::findConfigFile(StringRef FileName,
return false;
}

bool ExpansionContext::readConfigFile(StringRef CfgFile,
SmallVectorImpl<const char *> &Argv) {
Error ExpansionContext::readConfigFile(StringRef CfgFile,
SmallVectorImpl<const char *> &Argv) {
SmallString<128> AbsPath;
if (sys::path::is_relative(CfgFile)) {
AbsPath.assign(CfgFile);
if (std::error_code EC = FS->makeAbsolute(AbsPath))
return false;
return make_error<StringError>(
EC, Twine("cannot get absolute path for " + CfgFile));
CfgFile = AbsPath.str();
}
InConfigFile = true;
RelativeNames = true;
if (llvm::Error Err = expandResponseFile(CfgFile, Argv)) {
// TODO: The error should be propagated up the stack.
llvm::consumeError(std::move(Err));
return false;
}
if (Error Err = expandResponseFile(CfgFile, Argv))
return Err;
return expandResponseFiles(Argv);
}

Expand Down Expand Up @@ -1458,25 +1477,28 @@ bool CommandLineParser::ParseCommandLineOptions(int argc,
bool LongOptionsUseDoubleDash) {
assert(hasOptions() && "No options specified!");

ProgramOverview = Overview;
bool IgnoreErrors = Errs;
if (!Errs)
Errs = &errs();
bool ErrorParsing = false;

// Expand response files.
SmallVector<const char *, 20> newArgv(argv, argv + argc);
BumpPtrAllocator A;
ExpansionContext ECtx(A, Triple(sys::getProcessTriple()).isOSWindows()
? cl::TokenizeWindowsCommandLine
: cl::TokenizeGNUCommandLine);
ECtx.expandResponseFiles(newArgv);
if (Error Err = ECtx.expandResponseFiles(newArgv)) {
*Errs << toString(std::move(Err)) << '\n';
return false;
}
argv = &newArgv[0];
argc = static_cast<int>(newArgv.size());

// Copy the program name into ProgName, making sure not to overflow it.
ProgramName = std::string(sys::path::filename(StringRef(argv[0])));

ProgramOverview = Overview;
bool IgnoreErrors = Errs;
if (!Errs)
Errs = &errs();
bool ErrorParsing = false;

// Check out the positional arguments to collect information about them.
unsigned NumPositionalRequired = 0;

Expand Down
45 changes: 38 additions & 7 deletions llvm/unittests/Support/CommandLineTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,7 @@ TEST(CommandLineTest, ResponseFiles) {
llvm::BumpPtrAllocator A;
llvm::cl::ExpansionContext ECtx(A, llvm::cl::TokenizeGNUCommandLine);
ECtx.setVFS(&FS).setCurrentDir(TestRoot).setRelativeNames(true);
ASSERT_TRUE(ECtx.expandResponseFiles(Argv));
ASSERT_FALSE((bool)ECtx.expandResponseFiles(Argv));
EXPECT_THAT(Argv, testing::Pointwise(
StringEquality(),
{"test/test", "-flag_1", "-option_1", "-option_2",
Expand Down Expand Up @@ -933,7 +933,14 @@ TEST(CommandLineTest, RecursiveResponseFiles) {
#endif
llvm::cl::ExpansionContext ECtx(A, Tokenizer);
ECtx.setVFS(&FS).setCurrentDir(TestRoot);
ASSERT_FALSE(ECtx.expandResponseFiles(Argv));
llvm::Error Err = ECtx.expandResponseFiles(Argv);
ASSERT_TRUE((bool)Err);
SmallString<128> FilePath = SelfFilePath;
std::error_code EC = FS.makeAbsolute(FilePath);
ASSERT_FALSE((bool)EC);
std::string ExpectedMessage =
std::string("recursive expansion of: '") + std::string(FilePath) + "'";
ASSERT_TRUE(toString(std::move(Err)) == ExpectedMessage);

EXPECT_THAT(Argv,
testing::Pointwise(StringEquality(),
Expand Down Expand Up @@ -971,7 +978,7 @@ TEST(CommandLineTest, ResponseFilesAtArguments) {
BumpPtrAllocator A;
llvm::cl::ExpansionContext ECtx(A, cl::TokenizeGNUCommandLine);
ECtx.setVFS(&FS).setCurrentDir(TestRoot);
ASSERT_FALSE(ECtx.expandResponseFiles(Argv));
ASSERT_FALSE((bool)ECtx.expandResponseFiles(Argv));

// ASSERT instead of EXPECT to prevent potential out-of-bounds access.
ASSERT_EQ(Argv.size(), 1 + NON_RSP_AT_ARGS + 2);
Expand Down Expand Up @@ -1005,7 +1012,7 @@ TEST(CommandLineTest, ResponseFileRelativePath) {
BumpPtrAllocator A;
llvm::cl::ExpansionContext ECtx(A, cl::TokenizeGNUCommandLine);
ECtx.setVFS(&FS).setCurrentDir(TestRoot).setRelativeNames(true);
ASSERT_TRUE(ECtx.expandResponseFiles(Argv));
ASSERT_FALSE((bool)ECtx.expandResponseFiles(Argv));
EXPECT_THAT(Argv,
testing::Pointwise(StringEquality(), {"test/test", "-flag"}));
}
Expand All @@ -1025,7 +1032,7 @@ TEST(CommandLineTest, ResponseFileEOLs) {
llvm::cl::ExpansionContext ECtx(A, cl::TokenizeWindowsCommandLine);
ECtx.setVFS(&FS).setCurrentDir(TestRoot).setMarkEOLs(true).setRelativeNames(
true);
ASSERT_TRUE(ECtx.expandResponseFiles(Argv));
ASSERT_FALSE((bool)ECtx.expandResponseFiles(Argv));
const char *Expected[] = {"clang", "-Xclang", "-Wno-whatever", nullptr,
"input.cpp"};
ASSERT_EQ(std::size(Expected), Argv.size());
Expand All @@ -1038,6 +1045,30 @@ TEST(CommandLineTest, ResponseFileEOLs) {
}
}

TEST(CommandLineTest, BadResponseFile) {
BumpPtrAllocator A;
StringSaver Saver(A);
TempDir ADir("dir", /*Unique*/ true);
SmallString<128> AFilePath = ADir.path();
llvm::sys::path::append(AFilePath, "file.rsp");
std::string AFileExp = std::string("@") + std::string(AFilePath.str());
SmallVector<const char *, 2> Argv = {"clang", AFileExp.c_str()};

bool Res = cl::ExpandResponseFiles(Saver, cl::TokenizeGNUCommandLine, Argv);
ASSERT_TRUE(Res);
ASSERT_EQ(2U, Argv.size());
ASSERT_STREQ(Argv[0], "clang");
ASSERT_STREQ(Argv[1], AFileExp.c_str());

std::string ADirExp = std::string("@") + std::string(ADir.path());
Argv = {"clang", ADirExp.c_str()};
Res = cl::ExpandResponseFiles(Saver, cl::TokenizeGNUCommandLine, Argv);
ASSERT_FALSE(Res);
ASSERT_EQ(2U, Argv.size());
ASSERT_STREQ(Argv[0], "clang");
ASSERT_STREQ(Argv[1], ADirExp.c_str());
}

TEST(CommandLineTest, SetDefaultValue) {
cl::ResetCommandLineParser();

Expand Down Expand Up @@ -1145,9 +1176,9 @@ TEST(CommandLineTest, ReadConfigFile) {

llvm::BumpPtrAllocator A;
llvm::cl::ExpansionContext ECtx(A, cl::tokenizeConfigFile);
bool Result = ECtx.readConfigFile(ConfigFile.path(), Argv);
llvm::Error Result = ECtx.readConfigFile(ConfigFile.path(), Argv);

EXPECT_TRUE(Result);
EXPECT_FALSE((bool)Result);
EXPECT_EQ(Argv.size(), 13U);
EXPECT_STREQ(Argv[0], "-option_1");
EXPECT_STREQ(Argv[1],
Expand Down