Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix linker cmdline length limitation via response files #3536

Merged
merged 2 commits into from
Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions driver/args.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

#include "args.h"

#include "llvm/ADT/SmallString.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/StringSaver.h"

#include <cstdlib>
Expand Down Expand Up @@ -82,8 +84,106 @@ int forwardToDruntime(int argc, const CArgChar **argv) {
bool isRunArg(const char *arg) {
return strcmp(arg, "-run") == 0 || strcmp(arg, "--run") == 0;
}

////////////////////////////////////////////////////////////////////////////////

namespace {
#if LDC_LLVM_VER >= 700
std::vector<llvm::StringRef> toRefsVector(llvm::ArrayRef<const char *> args) {
std::vector<llvm::StringRef> refs;
refs.reserve(args.size());
for (const char *arg : args)
refs.emplace_back(arg);
return refs;
}
#endif

struct ResponseFile {
llvm::SmallString<128> path;

// Deletes the file (if path is non-empty).
~ResponseFile() {
if (!path.empty())
llvm::sys::fs::remove(path);
}

// Creates an appropriate response file if needed (=> path non-empty).
// Returns false on error.
bool setup(llvm::ArrayRef<const char *> fullArgs,
llvm::sys::WindowsEncodingMethod encoding) {
assert(path.empty());

const auto args = fullArgs.slice(1);
if (llvm::sys::commandLineFitsWithinSystemLimits(fullArgs[0], args))
return true; // nothing to do

#if defined(_WIN32) && LDC_LLVM_VER >= 700
const std::string content =
llvm::sys::flattenWindowsCommandLine(toRefsVector(args));
#else
std::string content;
content.reserve(65536);
for (llvm::StringRef arg : args) {
content += '"';
for (char c : arg) {
#ifdef _WIN32
if (c == '"')
content += '"'; // " => ""
#else
if (c == '\\' || c == '"')
content += '\\'; // \ => \\, " => \"
#endif
content += c;
}
content += "\"\n";
}
#endif

if (llvm::sys::fs::createTemporaryFile("ldc", "rsp", path))
return false;

if (llvm::sys::writeFileWithEncoding(path, content, encoding))
return false;

return true;
}
};
} // anonymous namespace

int executeAndWait(
std::vector<const char *> fullArgs,
llvm::Optional<llvm::sys::WindowsEncodingMethod> responseFileEncoding,
std::string *errorMsg) {
args::ResponseFile rspFile;
if (responseFileEncoding.hasValue() &&
!rspFile.setup(fullArgs, responseFileEncoding.getValue())) {
if (errorMsg)
*errorMsg = "could not write temporary response file";
return -1;
}

std::string rspArg;
if (!rspFile.path.empty()) {
rspArg = ("@" + rspFile.path).str();
fullArgs.resize(1); // executable only
fullArgs.push_back(rspArg.c_str());
}

#if LDC_LLVM_VER >= 700
const std::vector<llvm::StringRef> argv = toRefsVector(fullArgs);
auto envVars = llvm::None;
#else
fullArgs.push_back(nullptr); // terminate with null
auto argv = fullArgs.data();
auto envVars = nullptr;
#endif

return llvm::sys::ExecuteAndWait(argv[0], argv, envVars, {}, 0, 0, errorMsg);
}
} // namespace args

////////////////////////////////////////////////////////////////////////////////

namespace env {
#ifdef _WIN32
static wchar_t *wget(const char *name) {
Expand Down
14 changes: 12 additions & 2 deletions driver/args.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#pragma once

#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Program.h"

namespace args {

Expand All @@ -35,7 +36,16 @@ int forwardToDruntime(int argc, const CArgChar **argv);

// Returns true if the specified arg is either `-run` or `--run`.
bool isRunArg(const char *arg);
}

// Executes a command line and returns its exit code.
// Optionally uses a response file to overcome cmdline length limitations.
int executeAndWait(std::vector<const char *> fullArgs,
llvm::Optional<llvm::sys::WindowsEncodingMethod>
responseFileEncoding = {llvm::None},
std::string *errorMsg = nullptr);
} // namespace args

////////////////////////////////////////////////////////////////////////////////

namespace env {

Expand All @@ -47,4 +57,4 @@ bool has(const wchar_t *wname);

// Returns the value of the specified environment variable (in UTF-8).
std::string get(const char *name);
}
} // namespace env
96 changes: 13 additions & 83 deletions driver/ldmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/SystemUtils.h"
#include "llvm/Support/raw_ostream.h"
#include <cassert>
Expand All @@ -37,7 +36,6 @@
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <numeric>
#include <vector>

#if _WIN32
Expand Down Expand Up @@ -109,22 +107,13 @@ bool startsWith(const char *str, const char (&prefix)[N]) {
/**
* Runs the given executable, returning its error code.
*/
int execute(const std::string &exePath, const char **args) {
#if LDC_LLVM_VER >= 700
std::vector<llvm::StringRef> argv;
for (auto pArg = args; *pArg; ++pArg) {
argv.push_back(*pArg);
}
auto envVars = llvm::None;
#else
auto argv = args;
auto envVars = nullptr;
#endif

int execute(std::vector<const char *> fullArgs) {
std::string errorMsg;
int rc = ls::ExecuteAndWait(exePath, argv, envVars, {}, 0, 0, &errorMsg);
if (!errorMsg.empty()) {
error("Error executing %s: %s", exePath.c_str(), errorMsg.c_str());
const char *executable = fullArgs[0];
const int rc =
args::executeAndWait(std::move(fullArgs), llvm::sys::WEM_UTF8, &errorMsg);
if (rc && !errorMsg.empty()) {
error("Error executing %s: %s", executable, errorMsg.c_str());
}
return rc;
}
Expand All @@ -134,8 +123,7 @@ int execute(const std::string &exePath, const char **args) {
*/
void printUsage(const char *argv0, const std::string &ldcPath) {
// Print version information by actually invoking ldc -version.
const char *args[] = {ldcPath.c_str(), "-version", nullptr};
execute(ldcPath, args);
execute({ldcPath.c_str(), "-version"});

printf(
"\n\
Expand Down Expand Up @@ -538,8 +526,7 @@ void translateArgs(const llvm::SmallVectorImpl<const char *> &ldmdArgs,
const char *c = p + 6;
if (strcmp(c, "?") == 0 || strcmp(c, "h") == 0 ||
strcmp(c, "help") == 0) {
const char *mcpuargs[] = {ldcPath.c_str(), "-mcpu=help", nullptr};
execute(ldcPath, mcpuargs);
execute({ldcPath.c_str(), "-mcpu=help"});
exit(EXIT_SUCCESS);
} else if (strcmp(c, "baseline") == 0) {
// ignore
Expand Down Expand Up @@ -667,8 +654,7 @@ void translateArgs(const llvm::SmallVectorImpl<const char *> &ldmdArgs,
exit(EXIT_SUCCESS);
} else if (strcmp(p + 1, "-version") == 0) {
// Print version information by actually invoking ldc -version.
const char *versionargs[] = {ldcPath.c_str(), "-version", nullptr};
execute(ldcPath, versionargs);
execute({ldcPath.c_str(), "-version"});
exit(EXIT_SUCCESS);
}
/* -L
Expand Down Expand Up @@ -722,23 +708,6 @@ void translateArgs(const llvm::SmallVectorImpl<const char *> &ldmdArgs,
}
}

/**
* Returns the OS-dependent length limit for the command line when invoking
* subprocesses.
*/
size_t maxCommandLineLen() {
#if defined(HAVE_SC_ARG_MAX)
// http://www.in-ulm.de/~mascheck/various/argmax – the factor 2 is just
// a wild guess to account for the enviroment.
return sysconf(_SC_ARG_MAX) / 2;
#elif defined(_WIN32)
// http://blogs.msdn.com/b/oldnewthing/archive/2003/12/10/56028.aspx
return 32767;
#else
#error "Do not know how to determine maximum command line length."
#endif
}

/**
* Tries to locate an executable with the given name, or an invalid path if
* nothing was found. Search paths: 1. Directory where this binary resides.
Expand Down Expand Up @@ -794,49 +763,10 @@ int cppmain() {
exit(EXIT_FAILURE);
}

// We need to manually set up argv[0] and the terminating NULL.
std::vector<const char *> args;
args.push_back(ldcPath.c_str());

translateArgs(ldmdArguments, args);

args.push_back(nullptr);

// Check if we can get away without a response file.
const size_t totalLen = std::accumulate(
args.begin(), args.end() - 1,
args.size() * 3, // quotes + space
[](size_t acc, const char *arg) { return acc + strlen(arg); });
if (totalLen <= maxCommandLineLen()) {
return execute(ldcPath, args.data());
}

int rspFd;
llvm::SmallString<128> rspPath;
if (ls::fs::createUniqueFile("ldmd-%%-%%-%%-%%.rsp", rspFd, rspPath)) {
error("Could not open temporary response file.");
}

{
llvm::raw_fd_ostream rspOut(rspFd, /*shouldClose=*/true);
// skip argv[0] and terminating NULL
for (auto it = args.begin() + 1, end = args.end() - 1; it != end; ++it) {
rspOut << *it << '\n';
}
}

std::string rspArg = "@";
rspArg += rspPath.str();

args.resize(1);
args.push_back(rspArg.c_str());
args.push_back(nullptr);
std::vector<const char *> fullArgs;
fullArgs.push_back(ldcPath.c_str());

int rc = execute(ldcPath, args.data());
translateArgs(ldmdArguments, fullArgs);

if (ls::fs::remove(rspPath.str())) {
warning("Could not remove response file.");
}

return rc;
return execute(std::move(fullArgs));
}
41 changes: 20 additions & 21 deletions driver/tool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"

#ifdef _WIN32
#include <Windows.h>
Expand Down Expand Up @@ -150,7 +149,7 @@ std::vector<const char *> getFullArgs(const char *tool,
bool printVerbose) {
std::vector<const char *> fullArgs;
fullArgs.reserve(args.size() +
2); // executeToolAndWait() appends an additional null
2); // args::executeAndWait() may append an additional null

fullArgs.push_back(tool);
for (const auto &arg : args)
Expand All @@ -172,38 +171,38 @@ std::vector<const char *> getFullArgs(const char *tool,
////////////////////////////////////////////////////////////////////////////////

int executeToolAndWait(const std::string &tool_,
std::vector<std::string> const &args, bool verbose) {
const std::vector<std::string> &args, bool verbose) {
const auto tool = findProgramByName(tool_);
if (tool.empty()) {
error(Loc(), "failed to locate %s", tool_.c_str());
return -1;
}

// Construct real argument list; first entry is the tool itself.
auto realargs = getFullArgs(tool.c_str(), args, verbose);
#if LDC_LLVM_VER >= 700
std::vector<llvm::StringRef> argv;
argv.reserve(realargs.size());
for (auto &&arg : realargs)
argv.push_back(arg);
auto envVars = llvm::None;
#else
realargs.push_back(nullptr); // terminate with null
auto argv = &realargs[0];
auto envVars = nullptr;
auto fullArgs = getFullArgs(tool.c_str(), args, verbose);

// We may need a response file to overcome cmdline limits, especially on Windows.
auto rspEncoding = llvm::sys::WEM_UTF8;
#ifdef _WIN32
// MSVC tools (link.exe etc.) apparently require UTF-16 encoded response files
auto triple = global.params.targetTriple;
if (triple && triple->isWindowsMSVCEnvironment())
rspEncoding = llvm::sys::WEM_UTF16;
#endif

// Execute tool.
std::string errstr;
if (int status =
llvm::sys::ExecuteAndWait(tool, argv, envVars, {}, 0, 0, &errstr)) {
std::string errorMsg;
const int status =
args::executeAndWait(std::move(fullArgs), rspEncoding, &errorMsg);

if (status) {
error(Loc(), "%s failed with status: %d", tool.c_str(), status);
if (!errstr.empty()) {
error(Loc(), "message: %s", errstr.c_str());
if (!errorMsg.empty()) {
errorSupplemental(Loc(), "message: %s", errorMsg.c_str());
}
return status;
}
return 0;

return status;
}

////////////////////////////////////////////////////////////////////////////////
Expand Down
2 changes: 1 addition & 1 deletion driver/tool.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ std::vector<const char *> getFullArgs(const char *tool,
bool printVerbose);

int executeToolAndWait(const std::string &tool,
std::vector<std::string> const &args,
const std::vector<std::string> &args,
bool verbose = false);

#ifdef _WIN32
Expand Down