From db270a9a94ac061c44b76e0219ba6fbf52658503 Mon Sep 17 00:00:00 2001 From: David Nadlinger Date: Sun, 12 May 2013 13:34:07 +0200 Subject: [PATCH] Replaced response_expand with BSD-licensed reimplementation. Cleanup to follow later. GitHub: Fixes #267. --- CMakeLists.txt | 6 +- dmd2/root/response.c | 313 ------------------------------------------- driver/response.cpp | 176 ++++++++++++++++++++++++ 3 files changed, 177 insertions(+), 318 deletions(-) delete mode 100644 dmd2/root/response.c create mode 100644 driver/response.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index eb4468ae..579acc2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -389,14 +389,10 @@ if (HAVE_SC_ARG_MAX) add_definitions(-DHAVE_SC_ARG_MAX) endif() -set_source_files_properties(dmd2/root/response.c dmd2/root/man.c PROPERTIES - LANGUAGE CXX - COMPILE_FLAGS "${DMD_CXXFLAGS}" -) set_source_files_properties(driver/ldmd.cpp PROPERTIES COMPILE_FLAGS "${LDC_CXXFLAGS}" ) -add_executable(${LDMD_EXE} dmd2/root/response.c dmd2/root/man.c driver/ldmd.cpp) +add_executable(${LDMD_EXE} dmd2/root/man.c driver/ldmd.cpp driver/response.cpp) set_target_properties(${LDMD_EXE} PROPERTIES COMPILE_DEFINITIONS LDC_EXE_NAME="${LDC_EXE_NAME}" COMPILE_FLAGS "${LLVM_CXXFLAGS}" diff --git a/dmd2/root/response.c b/dmd2/root/response.c deleted file mode 100644 index 31b41b7d..00000000 --- a/dmd2/root/response.c +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright (C) 1990-1998 by Symantec -// Copyright (C) 2000-2011 by Digital Mars -// All Rights Reserved -// http://www.digitalmars.com -// Written by Walter Bright -/* - * This source file is made available for personal use - * only. The license is in backendlicense.txt - * For any other uses, please contact Digital Mars. - */ - -#include -#include -#include -#include -#include - -#if _WIN32 -#include -#include -#endif - -#if linux || __APPLE__ || __FreeBSD__ || __OpenBSD__ || __sun -#include -#include -#include -#include -#include -#include -#endif - -#if _MSC_VER || defined(__MINGW32__) -#include -#include -#include -#include -#endif - -/********************************* - * #include - * int response_expand(int *pargc,char ***pargv); - * - * Expand any response files in command line. - * Response files are arguments that look like: - * @NAME - * The name is first searched for in the environment. If it is not - * there, it is searched for as a file name. - * Arguments are separated by spaces, tabs, or newlines. These can be - * imbedded within arguments by enclosing the argument in '' or "". - * Recursively expands nested response files. - * - * To use, put the line: - * response_expand(&argc,&argv); - * as the first executable statement in main(int argc, char **argv). - * argc and argv are adjusted to be the new command line arguments - * after response file expansion. - * - * Digital Mars's MAKE program can be notified that a program can accept - * long command lines via environment variables by preceding the rule - * line for the program with a *. - * - * Returns: - * 0 success - * !=0 failure (argc, argv unchanged) - */ - -struct Narg -{ - size_t argc; // arg count - size_t argvmax; // dimension of nargv[] - char **argv; -}; - -static int addargp(struct Narg *n, char *p) -{ - /* The 2 is to always allow room for a NULL argp at the end */ - if (n->argc + 2 > n->argvmax) - { - n->argvmax = n->argc + 2; - char **ap = n->argv; - ap = (char **) realloc(ap,n->argvmax * sizeof(char *)); - if (!ap) - { if (n->argv) - free(n->argv); - memset(n, 0, sizeof(*n)); - return 1; - } - n->argv = ap; - } - n->argv[n->argc++] = p; - return 0; -} - -int response_expand(size_t *pargc, char ***pargv) -{ - struct Narg n; - char *cp; - int recurse = 0; - - n.argc = 0; - n.argvmax = 0; /* dimension of n.argv[] */ - n.argv = NULL; - for (size_t i = 0; i < *pargc; ++i) - { - cp = (*pargv)[i]; - if (*cp == '@') - { - char *buffer; - char *bufend; - char *p; - int comment = 0; - - cp++; - p = getenv(cp); - if (p) - { - buffer = strdup(p); - if (!buffer) - goto noexpand; - bufend = buffer + strlen(buffer); - } - else - { - long length; - int fd; - int nread; - size_t len; - -#if __DMC__ - length = filesize(cp); -#else - struct stat statbuf; - if (stat(cp, &statbuf)) - goto noexpand; - length = statbuf.st_size; -#endif - if (length & 0xF0000000) /* error or file too big */ - goto noexpand; - len = length; - buffer = (char *)malloc(len + 1); - if (!buffer) - goto noexpand; - bufend = &buffer[len]; - /* Read file into buffer */ -#if _WIN32 - fd = _open(cp,O_RDONLY|O_BINARY); -#else - fd = open(cp,O_RDONLY); -#endif - if (fd == -1) - goto noexpand; - nread = read(fd,buffer,len); - close(fd); - - if (nread != len) - goto noexpand; - } - - // The logic of this should match that in setargv() - - for (p = buffer; p < bufend; p++) - { - char *d; - char c,lastc; - unsigned char instring; - int num_slashes,non_slashes; - - switch (*p) - { - case 26: /* ^Z marks end of file */ - goto L2; - - case 0xD: - case '\n': - if (comment) - { - comment = 0; - } - case 0: - case ' ': - case '\t': - continue; // scan to start of argument - - case '#': - comment = 1; - continue; - - case '@': - if (comment) - { - continue; - } - recurse = 1; - default: /* start of new argument */ - if (comment) - { - continue; - } - if (addargp(&n,p)) - goto noexpand; - instring = 0; - c = 0; - num_slashes = 0; - for (d = p; 1; p++) - { - lastc = c; - if (p >= bufend) - goto Lend; - c = *p; - switch (c) - { - case '"': - /* - Yes this looks strange,but this is so that we are - MS Compatible, tests have shown that: - \\\\"foo bar" gets passed as \\foo bar - \\\\foo gets passed as \\\\foo - \\\"foo gets passed as \"foo - and \"foo gets passed as "foo in VC! - */ - non_slashes = num_slashes % 2; - num_slashes = num_slashes / 2; - for (; num_slashes > 0; num_slashes--) - { - d--; - *d = '\0'; - } - - if (non_slashes) - { - *(d-1) = c; - } - else - { - instring ^= 1; - } - break; - case 26: - Lend: - *d = 0; // terminate argument - goto L2; - - case 0xD: // CR - c = lastc; - continue; // ignore - - case '@': - recurse = 1; - goto Ladd; - - case ' ': - case '\t': - if (!instring) - { - case '\n': - case 0: - *d = 0; // terminate argument - goto Lnextarg; - } - default: - Ladd: - if (c == '\\') - num_slashes++; - else - num_slashes = 0; - *d++ = c; - break; - } -#ifdef _MBCS - if (_istlead (c)) { - *d++ = *++p; - if (*(d - 1) == '\0') { - d--; - goto Lnextarg; - } - } -#endif - } - break; - } - Lnextarg: - ; - } - L2: - ; - } - else if (addargp(&n,(*pargv)[i])) - goto noexpand; - } - if (n.argvmax == 0) - { - n.argvmax = 1; - n.argv = (char **) calloc(n.argvmax, sizeof(char *)); - if (!n.argv) - return 1; - } - else - n.argv[n.argc] = NULL; - if (recurse) - { - /* Recursively expand @filename */ - if (response_expand(&n.argc,&n.argv)) - goto noexpand; - } - *pargc = n.argc; - *pargv = n.argv; - return 0; /* success */ - -noexpand: /* error */ - free(n.argv); - /* BUG: any file buffers are not free'd */ - return 1; -} diff --git a/driver/response.cpp b/driver/response.cpp new file mode 100644 index 00000000..5839caae --- /dev/null +++ b/driver/response.cpp @@ -0,0 +1,176 @@ +//===-- target.cpp --------------------------------------------------------===// +// +// LDC – the LLVM D compiler +// +// This file is distributed under the BSD-style LDC license. See the LICENSE +// file for details. +// +//===----------------------------------------------------------------------===// +// +// This is an open-source reimplementation of the DMD response_expand function +// (Safety0ff/response_expand on GitHub, see LDC issue #267). +// +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// returns true if the quote is unescaped +bool applyBackslashRule(std::string &arg) { + std::string::reverse_iterator it; + for (it = arg.rbegin(); it != arg.rend() && *it == '\\'; ++it) {} + size_t numbs = std::distance(arg.rbegin(), it); + bool escapedquote = numbs % 2; + size_t numescaped = numbs / 2 + escapedquote; + arg.resize(arg.size() - numescaped); + if (escapedquote) + arg += '"'; + return !escapedquote; +} + +// returns true if the end of the quote is the end of the argument +bool dealWithQuote(std::istream &is, std::string &arg) { + // first go back and deal with backslashes + if (!applyBackslashRule(arg)) + return false; + + // keep appending until we find a quote terminator + while (is.good()) { + char c = is.get(); + switch (c) { + case '"': + if (applyBackslashRule(arg)) + return false; + break; + case EOF: // new line or EOF ends quote and argument + case '\n': + return true; + case '\r': // ignore carriage returns in quotes + break; + default: + arg += c; + } + } + return true; // EOF +} + +void dealWithComment(std::istream &is) { + while (is.good()) { + char c = is.get(); + if (c == '\n' || c == '\r') + return; // newline, carriage return and EOF end comment + } +} + +std::vector expand(std::istream &is) { + std::vector expanded; + std::string arg; + + is >> std::ws; + while (is.good()) { + char c = is.get(); + if (c == EOF) { + break; + } else if (std::isspace(c)) { + is >> std::ws; + if (!arg.empty()) { + expanded.push_back(arg); + arg.clear(); + } + } else if (c == '"') { + if (dealWithQuote(is, arg) && !arg.empty()) { + expanded.push_back(arg); + arg.clear(); + } + } else if (c == '#') { + dealWithComment(is); + } else { + arg += c; + while (is.good()) { + c = is.peek(); + if (std::isspace(c) || c == '"' || c == EOF) + break; // N.B. comments can't be placed in the middle of an arg + else + arg += is.get(); + } + } + } + if (!arg.empty()) + expanded.push_back(arg); + return expanded; +} + +int response_expand(size_t *pargc, char ***ppargv) { + // N.B. It is possible to create an infinite loop with response arguments + // in the original implementation, we artificially limmit re-parsing responses + const unsigned reexpand_limit = 32; + std::map response_args; + + // Compile a list of arguments, at the end convert them to the proper C string + // form + std::vector processed_args; + std::list unprocessed_args; + + // fill unprocessed with initial arguments + for (size_t i = 0; i < *pargc; ++i) { + unprocessed_args.push_back((*ppargv)[i]); + } + + processed_args.reserve(*pargc); + + while (!unprocessed_args.empty()) { + std::string arg = unprocessed_args.front(); + unprocessed_args.pop_front(); + + if (!arg.empty() && arg[0] == '@') { + arg.erase(arg.begin()); // remove leading '@' + if (arg.empty()) { + return 1; + } else { + if (response_args.find(arg) == response_args.end()) + response_args[arg] = 1; + else if (++response_args[arg] > reexpand_limit) + return 2; // We might be in an infinite loop + + std::vector expanded_args; + const char *env = getenv(arg.c_str()); + if (env) { + std::string envCppStr(env); + std::istringstream ss(envCppStr); + expanded_args = expand(ss); + } else { + std::ifstream ifs(arg.c_str()); + if (ifs.good()) + expanded_args = expand(ifs); + else + return 3; // response not found between environment and files + } + unprocessed_args.insert(unprocessed_args.begin(), expanded_args.begin(), + expanded_args.end()); + } + } else if (!arg.empty()) { + processed_args.push_back(arg); + } + } + + char **pargv = (char **)malloc(sizeof(pargv) * processed_args.size()); + + for (size_t i = 0; i < processed_args.size(); ++i) { + pargv[i] = + (char *)malloc(sizeof(pargv[i]) * (1 + processed_args[i].length())); + strcpy(pargv[i], processed_args[i].c_str()); + } + + *pargc = processed_args.size(); + *ppargv = pargv; + + return 0; +}