blob: 8d902d49324e65c05999d2144db37a06acacfd6d [file]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gn/escape.h"
#include <stddef.h>
#include <array>
#include "base/compiler_specific.h"
#include "base/json/string_escape.h"
#include "base/logging.h"
#include "gn/string_output_buffer.h"
#include "util/build_config.h"
namespace {
#if defined(OS_WIN)
constexpr size_t kMaxEscapedCharsPerChar = 2;
#else
constexpr size_t kMaxEscapedCharsPerChar = 3;
#endif
// A "1" in this lookup table means that char is valid in the Posix shell.
// clang-format off
constexpr char kShellValid[256] = {
// 00-1f: all are invalid
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
// ' ' ! " # $ % & ' ( ) * + , - . /
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
// 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0,
// @ A B C D E F G H I J K L M N O
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
// P Q R S T U V W X Y Z [ \ ] ^ _
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
// ` a b c d e f g h i j k l m n o
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
// p q r s t u v w x y z { | } ~
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0};
// clang-format on
constexpr std::array<char, 256> PosixEscapesTo(const char (&entries)[256]) {
std::array<char, 256> table{};
for (size_t i = 0; i < table.size(); ++i) {
if (entries[i] == 0) {
table[i] = '\\';
}
}
table[':'] = '$';
return table;
}
constexpr auto kPosixNinjaTable = PosixEscapesTo(kShellValid);
constexpr std::array<bool, 256> InitNinjaEscapeTable() {
std::array<bool, 256> table{};
table['$'] = true;
table[' '] = true;
table[':'] = true;
return table;
}
constexpr auto kNinjaEscapeTable = InitNinjaEscapeTable();
char* EscapeStringToString_Space(std::string_view str, char* out) {
size_t in_idx = 0;
for (size_t delim = str.find(' '); UNLIKELY(delim != std::string_view::npos);
delim = str.find(' ', in_idx)) {
size_t len = delim - in_idx;
memcpy(out, str.data() + in_idx, len);
out += len;
*out++ = '\\';
*out++ = ' ';
in_idx = delim + 1;
}
size_t len = str.size() - in_idx;
memcpy(out, str.data() + in_idx, len);
out += len;
return out;
}
// 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.
inline bool ShouldEscapeCharForNinja(char ch) {
return kNinjaEscapeTable[static_cast<uint8_t>(ch)];
}
// Performance could be further improved with architecture-specific SIMD
// (e.g. SSE2 on x86, NEON on ARM), but that is platform-dependent, so we
// refrain from doing so for now.
char* EscapeStringToString_Ninja(std::string_view str, char* out) {
size_t in_idx = 0;
for (size_t i = 0; i < str.size(); ++i) {
if (UNLIKELY(ShouldEscapeCharForNinja(str[i]))) {
size_t len = i - in_idx;
memcpy(out, str.data() + in_idx, len);
out += len;
*out++ = '$';
*out++ = str[i];
in_idx = i + 1;
}
}
size_t len = str.size() - in_idx;
memcpy(out, str.data() + in_idx, len);
out += len;
return out;
}
inline bool ShouldEscapeCharForCompilationDatabase(char ch) {
return ch == '\\' || ch == '"';
}
size_t EscapeStringToString_CompilationDatabase(std::string_view str,
char* dest) {
size_t i = 0;
bool quote = false;
for (const auto& elem : str) {
if (!kShellValid[static_cast<uint8_t>(elem)]) {
quote = true;
break;
}
}
if (quote)
dest[i++] = '"';
for (const auto& elem : str) {
if (ShouldEscapeCharForCompilationDatabase(elem))
dest[i++] = '\\';
dest[i++] = elem;
}
if (quote)
dest[i++] = '"';
return i;
}
size_t EscapeStringToString_Depfile(std::string_view str, char* dest) {
size_t i = 0;
for (const auto& elem : str) {
// Escape all characters that ninja depfile parser can recognize as escaped,
// even if some of them can work without escaping.
if (elem == ' ' || elem == '\\' || elem == '#' || elem == '*' ||
elem == '[' || elem == '|' || elem == ']')
dest[i++] = '\\';
else if (elem == '$') // Extra rule for $$
dest[i++] = '$';
dest[i++] = elem;
}
return i;
}
size_t EscapeStringToString_NinjaPreformatted(std::string_view str,
char* dest) {
// Only Ninja-escape $.
size_t i = 0;
for (const auto& elem : str) {
if (elem == '$')
dest[i++] = '$';
dest[i++] = elem;
}
return i;
}
// 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
size_t EscapeStringToString_WindowsNinjaFork(std::string_view str,
bool inhibit_quoting,
char* 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);
size_t i = 0;
// '$', ' ', and ':' mean we need ninja escaping. ' ' and '"' mean we need
// shell escaping.
size_t idx = str.find_first_of("$: \"");
if (LIKELY(idx == std::string::npos)) {
// Super-fast-path: No shell escaping or Ninja escaping required.
memcpy(dest, str.data(), str.size());
return str.size();
}
// If the special character found was NOT a space or quote (i.e. it was '$'
// or ':'), check if there are any spaces or quotes in the rest of the string.
if (str[idx] != ' ' && str[idx] != '"' &&
str.find_first_of(" \"", idx + 1) == std::string::npos) {
// Fast-path: Needs Ninja escaping, but no Windows command-line quoting.
// We know that the up until here doesn't require any escaping
memcpy(dest, str.data(), idx);
char* out = &dest[idx];
// Ninja always escapes with a $
*out++ = '$';
*out++ = str[idx];
// Escape the rest of the string with ninja.
out = EscapeStringToString_Ninja(str.substr(idx + 1), out);
return out - dest;
}
if (!inhibit_quoting)
dest[i++] = '"';
for (size_t j = 0; j < str.size(); j++) {
// Count backslashes in case they're followed by a quote.
size_t backslash_count = 0;
while (j < str.size() && str[j] == '\\') {
j++;
backslash_count++;
}
if (j == str.size()) {
// Backslashes at end of string. Backslash-escape all of them since
// they'll be followed by a quote.
memset(dest + i, '\\', backslash_count * 2);
i += backslash_count * 2;
} else if (str[j] == '"') {
// 0 or more backslashes followed by a quote. Backslash-escape the
// backslashes, then backslash-escape the quote.
memset(dest + i, '\\', backslash_count * 2 + 1);
i += backslash_count * 2 + 1;
dest[i++] = '"';
} else {
// Non-special Windows character, just escape for Ninja. Also, add any
// backslashes we read previously, these are literals.
memset(dest + i, '\\', backslash_count);
i += backslash_count;
if (ShouldEscapeCharForNinja(str[j]))
dest[i++] = '$';
dest[i++] = str[j];
}
}
if (!inhibit_quoting)
dest[i++] = '"';
if (needed_quoting)
*needed_quoting = true;
return i;
}
char* EscapeStringToString_PosixNinjaFork(std::string_view str, char* out) {
size_t start = 0;
for (size_t i = 0; i < str.size(); ++i) {
char esc = kPosixNinjaTable[static_cast<uint8_t>(str[i])];
if (UNLIKELY(esc != '\0')) {
size_t len = i - start;
memcpy(out, str.data() + start, len);
out += len;
char c = str[i];
*out++ = esc;
if (c == ' ' || c == '$') {
*out++ = '$';
}
*out++ = c;
start = i + 1;
}
}
size_t len = str.size() - start;
memcpy(out, str.data() + start, len);
out += len;
return out;
}
// Escapes |str| into |dest| and returns the number of characters written.
size_t EscapeStringToString(std::string_view str,
const EscapeOptions& options,
char* dest,
bool* needed_quoting) {
switch (options.mode) {
case ESCAPE_NONE:
strncpy(dest, str.data(), str.size());
return str.size();
case ESCAPE_SPACE:
return EscapeStringToString_Space(str, dest) - dest;
case ESCAPE_NINJA:
return EscapeStringToString_Ninja(str, dest) - dest;
case ESCAPE_DEPFILE:
return EscapeStringToString_Depfile(str, dest);
case ESCAPE_COMPILATION_DATABASE:
return EscapeStringToString_CompilationDatabase(str, dest);
case ESCAPE_NINJA_COMMAND:
switch (options.platform) {
case ESCAPE_PLATFORM_WIN:
return EscapeStringToString_WindowsNinjaFork(
str, options.inhibit_quoting, dest, needed_quoting);
case ESCAPE_PLATFORM_POSIX:
return EscapeStringToString_PosixNinjaFork(str, dest) - dest;
default:
NOTREACHED();
}
case ESCAPE_NINJA_PREFORMATTED_COMMAND:
return EscapeStringToString_NinjaPreformatted(str, dest);
default:
NOTREACHED();
}
return 0;
}
} // namespace
char* Escaped::Space::WriteToBuf(char* out) const {
return EscapeStringToString_Space(str, out);
}
char* Escaped::Ninja::WriteToBuf(char* out) const {
return EscapeStringToString_Ninja(str, out);
}
char* Escaped::Depfile::WriteToBuf(char* out) const {
return &out[EscapeStringToString_Depfile(str, out)];
}
char* Escaped::CompilationDatabase::WriteToBuf(char* out) const {
return &out[EscapeStringToString_CompilationDatabase(str, out)];
}
char* Escaped::NinjaPreformatted::WriteToBuf(char* out) const {
return &out[EscapeStringToString_NinjaPreformatted(str, out)];
}
char* Escaped::Command::WriteToBuf(char* out) const {
switch (platform) {
case ESCAPE_PLATFORM_WIN:
return &out[EscapeStringToString_WindowsNinjaFork(str, inhibit_quoting,
out, nullptr)];
case ESCAPE_PLATFORM_POSIX:
return EscapeStringToString_PosixNinjaFork(str, out);
default:
NOTREACHED();
}
return out;
}
std::string EscapeString(std::string_view str,
const EscapeOptions& options,
bool* needed_quoting) {
std::string result;
result.resize(str.size() * kMaxEscapedCharsPerChar + 2);
size_t len =
EscapeStringToString(str, options, result.data(), needed_quoting);
result.resize(len);
return result;
}
void EscapeStringToStream(StringOutputBuffer& out,
std::string_view str,
const EscapeOptions& options) {
switch (options.mode) {
case ESCAPE_NONE:
out << str;
break;
case ESCAPE_SPACE:
out << Escaped::Space(str);
break;
case ESCAPE_NINJA:
out << Escaped::Ninja(str);
break;
case ESCAPE_DEPFILE:
out << Escaped::Depfile(str);
break;
case ESCAPE_COMPILATION_DATABASE:
out << Escaped::CompilationDatabase(str);
break;
case ESCAPE_NINJA_COMMAND:
out << Escaped::Command(str, options.platform, options.inhibit_quoting);
break;
case ESCAPE_NINJA_PREFORMATTED_COMMAND:
out << Escaped::NinjaPreformatted(str);
break;
default:
NOTREACHED();
}
}
void EscapeJSONStringToStream(StringOutputBuffer& out,
std::string_view str,
const EscapeOptions& options) {
std::string dest;
bool needed_quoting = !options.inhibit_quoting;
base::EscapeJSONString(str, needed_quoting, &dest);
EscapeStringToStream(out, dest, options);
}