| Index: tools/gn/escape.cc
|
| diff --git a/tools/gn/escape.cc b/tools/gn/escape.cc
|
| index 1658a8ad1edd90acc9423fb498ce0ef4b57c9dd2..7028a38b0b33f37b59d62d18b94010839872e76e 100644
|
| --- a/tools/gn/escape.cc
|
| +++ b/tools/gn/escape.cc
|
| @@ -5,10 +5,11 @@
|
| #include "tools/gn/escape.h"
|
|
|
| #include "base/containers/stack_container.h"
|
| +#include "base/logging.h"
|
|
|
| namespace {
|
|
|
| -// A "1" in this lookup table means that char is valid in the shell.
|
| +// A "1" in this lookup table means that char is valid in the Posix shell.
|
| const char kShellValid[0x80] = {
|
| // 00-1f: all are invalid
|
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
| @@ -26,70 +27,150 @@ const char kShellValid[0x80] = {
|
| // p q r s t u v w x y z { | } ~
|
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0 };
|
|
|
| +// Append one character to the given string, escaping it for Ninja.
|
| +//
|
| +// Ninja's escaping rules are very simple. We always escape colons even
|
| +// though they're OK in many places, in case the resulting string is used on
|
| +// the left-hand-side of a rule.
|
| template<typename DestString>
|
| -void EscapeStringToString(const base::StringPiece& str,
|
| - const EscapeOptions& options,
|
| - DestString* dest,
|
| - bool* needed_quoting) {
|
| - bool used_quotes = false;
|
| +inline void NinjaEscapeChar(char ch, DestString* dest) {
|
| + if (ch == '$' || ch == ' ' || ch == ':')
|
| + dest->push_back('$');
|
| + dest->push_back(ch);
|
| +}
|
|
|
| - for (size_t i = 0; i < str.size(); i++) {
|
| - if (str[i] == '$' && (options.mode & ESCAPE_NINJA)) {
|
| - // Escape dollars signs since ninja treats these specially. If we're also
|
| - // escaping for the shell, we need to backslash-escape that again.
|
| - if (options.mode & ESCAPE_SHELL)
|
| - dest->push_back('\\');
|
| - dest->push_back('$');
|
| - dest->push_back('$');
|
| - } else if (str[i] == ' ') {
|
| - if (options.mode & ESCAPE_NINJA) {
|
| - // For Ninja just escape spaces with $.
|
| - dest->push_back('$');
|
| +template<typename DestString>
|
| +void EscapeStringToString_Ninja(const base::StringPiece& str,
|
| + const EscapeOptions& options,
|
| + DestString* dest,
|
| + bool* needed_quoting) {
|
| + for (size_t i = 0; i < str.size(); i++)
|
| + NinjaEscapeChar(str[i], dest);
|
| + if (needed_quoting)
|
| + *needed_quoting = false;
|
| +}
|
| +
|
| +// Escape for CommandLineToArgvW and additionally escape Ninja characters.
|
| +//
|
| +// The basic algorithm is if the string doesn't contain any parse-affecting
|
| +// characters, don't do anything (other than the Ninja processing). If it does,
|
| +// quote the string, and backslash-escape all quotes and backslashes.
|
| +// See:
|
| +// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
|
| +// http://blogs.msdn.com/b/oldnewthing/archive/2010/09/17/10063629.aspx
|
| +template<typename DestString>
|
| +void EscapeStringToString_WindowsNinjaFork(const base::StringPiece& str,
|
| + const EscapeOptions& options,
|
| + DestString* dest,
|
| + bool* needed_quoting) {
|
| + // We assume we don't have any whitespace chars that aren't spaces.
|
| + DCHECK(str.find_first_of("\r\n\v\t") == std::string::npos);
|
| +
|
| + if (str.find_first_of(" \"") == std::string::npos) {
|
| + // Simple case, don't quote.
|
| + EscapeStringToString_Ninja(str, options, dest, needed_quoting);
|
| + } else {
|
| + if (!options.inhibit_quoting)
|
| + dest->push_back('"');
|
| +
|
| + for (size_t i = 0; i < str.size(); i++) {
|
| + // Count backslashes in case they're followed by a quote.
|
| + size_t backslash_count = 0;
|
| + while (i < str.size() && str[i] == '\\') {
|
| + i++;
|
| + backslash_count++;
|
| }
|
| - if (options.mode & ESCAPE_SHELL) {
|
| - // For the shell, quote the whole string.
|
| - if (needed_quoting)
|
| - *needed_quoting = true;
|
| - if (!options.inhibit_quoting) {
|
| - if (!used_quotes) {
|
| - used_quotes = true;
|
| - dest->insert(dest->begin(), '"');
|
| - }
|
| - }
|
| + if (i == str.size()) {
|
| + // Backslashes at end of string. Backslash-escape all of them since
|
| + // they'll be followed by a quote.
|
| + dest->append(backslash_count * 2, '\\');
|
| + } else if (str[i] == '"') {
|
| + // 0 or more backslashes followed by a quote. Backslash-escape the
|
| + // backslashes, then backslash-escape the quote.
|
| + dest->append(backslash_count * 2 + 1, '\\');
|
| + dest->push_back('"');
|
| + } else {
|
| + // Non-special Windows character, just escape for Ninja. Also, add any
|
| + // backslashes we read previously, these are literals.
|
| + dest->append(backslash_count, '\\');
|
| + NinjaEscapeChar(str[i], dest);
|
| }
|
| - dest->push_back(' ');
|
| - } else if (str[i] == '\'' && (options.mode & ESCAPE_JSON)) {
|
| - dest->push_back('\\');
|
| - dest->push_back('\'');
|
| -#if defined(OS_WIN)
|
| - } else if (str[i] == '/' && options.convert_slashes) {
|
| - // Convert slashes on Windows if requested.
|
| - dest->push_back('\\');
|
| -#else
|
| - } else if (str[i] == '\\' && (options.mode & ESCAPE_SHELL)) {
|
| - // For non-Windows shell, escape backslashes.
|
| - dest->push_back('\\');
|
| - dest->push_back('\\');
|
| -#endif
|
| - } else if (str[i] == '\\' && (options.mode & ESCAPE_JSON)) {
|
| - dest->push_back('\\');
|
| + }
|
| +
|
| + if (!options.inhibit_quoting)
|
| + dest->push_back('"');
|
| + if (needed_quoting)
|
| + *needed_quoting = true;
|
| + }
|
| +}
|
| +
|
| +template<typename DestString>
|
| +void EscapeStringToString_PosixNinjaFork(const base::StringPiece& str,
|
| + const EscapeOptions& options,
|
| + DestString* dest,
|
| + bool* needed_quoting) {
|
| + for (size_t i = 0; i < str.size(); i++) {
|
| + if (str[i] == '$' || str[i] == ' ') {
|
| + // Space and $ are special to both Ninja and the shell. '$' escape for
|
| + // Ninja, then backslash-escape for the shell.
|
| dest->push_back('\\');
|
| - } else if (str[i] == ':' && (options.mode & ESCAPE_NINJA)) {
|
| + dest->push_back('$');
|
| + dest->push_back(str[i]);
|
| + } else if (str[i] == ':') {
|
| + // Colon is the only other Ninja special char, which is not special to
|
| + // the shell.
|
| dest->push_back('$');
|
| dest->push_back(':');
|
| - } else if ((options.mode & ESCAPE_SHELL) &&
|
| - (static_cast<unsigned>(str[i]) >= 0x80 ||
|
| - !kShellValid[static_cast<int>(str[i])])) {
|
| + } else if (static_cast<unsigned>(str[i]) >= 0x80 ||
|
| + !kShellValid[static_cast<int>(str[i])]) {
|
| // All other invalid shell chars get backslash-escaped.
|
| dest->push_back('\\');
|
| dest->push_back(str[i]);
|
| } else {
|
| + // Everything else is a literal.
|
| dest->push_back(str[i]);
|
| }
|
| }
|
| +}
|
|
|
| - if (used_quotes)
|
| - dest->push_back('"');
|
| +template<typename DestString>
|
| +void EscapeStringToString(const base::StringPiece& str,
|
| + const EscapeOptions& options,
|
| + DestString* dest,
|
| + bool* needed_quoting) {
|
| + switch (options.mode) {
|
| + case ESCAPE_NONE:
|
| + dest->append(str.data(), str.size());
|
| + break;
|
| + case ESCAPE_NINJA:
|
| + EscapeStringToString_Ninja(str, options, dest, needed_quoting);
|
| + break;
|
| + case ESCAPE_NINJA_COMMAND:
|
| + switch (options.platform) {
|
| + case ESCAPE_PLATFORM_CURRENT:
|
| +#if defined(OS_WIN)
|
| + EscapeStringToString_WindowsNinjaFork(str, options, dest,
|
| + needed_quoting);
|
| +#else
|
| + EscapeStringToString_PosixNinjaFork(str, options, dest,
|
| + needed_quoting);
|
| +#endif
|
| + break;
|
| + case ESCAPE_PLATFORM_WIN:
|
| + EscapeStringToString_WindowsNinjaFork(str, options, dest,
|
| + needed_quoting);
|
| + break;
|
| + case ESCAPE_PLATFORM_POSIX:
|
| + EscapeStringToString_PosixNinjaFork(str, options, dest,
|
| + needed_quoting);
|
| + break;
|
| + default:
|
| + NOTREACHED();
|
| + }
|
| + break;
|
| + default:
|
| + NOTREACHED();
|
| + }
|
| }
|
|
|
| } // namespace
|
| @@ -106,10 +187,8 @@ std::string EscapeString(const base::StringPiece& str,
|
| void EscapeStringToStream(std::ostream& out,
|
| const base::StringPiece& str,
|
| const EscapeOptions& options) {
|
| - // Escape to a stack buffer and then write out to the stream.
|
| - base::StackVector<char, 256> result;
|
| - result->reserve(str.size() + 4); // Guess we'll add a couple of extra chars.
|
| - EscapeStringToString(str, options, &result.container(), NULL);
|
| - if (!result->empty())
|
| - out.write(&result[0], result->size());
|
| + base::StackString<256> escaped;
|
| + EscapeStringToString(str, options, &escaped.container(), NULL);
|
| + if (!escaped->empty())
|
| + out.write(escaped->data(), escaped->size());
|
| }
|
|
|