# V1076. Code contains invisible characters that may alter its logic. Consider enabling the display of invisible characters in the code editor.

Анализатор обнаружил в тексте программы символы, которые могут ввести программиста в заблуждение. Эти символы могут не отображаться и изменять видимое представление кода в среде разработки. Комбинации таких символов могут привести к тому, что человек и компилятор будут интерпретировать код по-разному.

Это может быть сделано специально. Такой вид атаки называется Trojan Source. Подробнее:

- Атака Trojan Source для внедрения в код изменений, незаметных для разработчика;
- Атака Trojan Source: скрытые уязвимости.

Анализатор выдаст предупреждение, если найдет один из следующих символов:

Обозначение | Код | Название | Описание
--- | --- | --- | ---
LRE | U+202A | LEFT-TO-RIGHT EMBEDDING | Текст после символа LRE интерпретируется как вставленный и отображается слева направо. Действие LRE прерывается символом PDF или символом перевода строки.
RLE | U+202B | RIGHT-TO-LEFT EMBEDDING | Текст после символа RLE интерпретируется как вставленный и отображается справа налево. Действие RLE прерывается символом PDF или символом перевода строки.
LRO | U+202D | LEFT-TO-RIGHT OVERRIDE | Текст после символа LRO принудительно отображается слева направо. Действие LRO прерывается символом PDF или символом перевода строки.
RLO | U+202E | RIGHT-TO-LEFT OVERRIDE | Текст после символа RLO принудительно отображается справа налево. Действие RLO прерывается символом PDF или символом перевода строки.
PDF | U+202C | POP DIRECTIONAL FORMATTING | Символ PDF прерывает действие одного из символов LRE, RLE, LRO или RLO, встреченного ранее. Прерывает ровно один, последний из встреченных, символ.
LRI | U+2066 | LEFT‑TO‑RIGHT ISOLATE | Текст после символа LRI отображается слева направо и интерпретируется как изолированный. Это означает, что другие управляющие символы не влияют на отображение этого фрагмента текста. Действие LRI прерывается символом PDI или символом перевода строки.
RLI | U+2067 | RIGHT‑TO‑LEFT ISOLATE | Текст после символа RLI отображается справа налево и интерпретируется как изолированный. Это означает, что другие управляющие символы не влияют на отображение этого фрагмента текста. Действие RLI прерывается символом PDI или символом перевода строки.
FSI | U+2068 | FIRST STRONG ISOLATE | Направление текста после символа FSI задается первым управляющим символом, не входящим в этот фрагмент текста. Другие управляющие символы не влияют на отображение этого текста. Действие FSI прерывается символом PDI или символом перевода строки.
PDI | U+2069 | POP DIRECTIONAL ISOLATE | Символ PDI прерывает действие одного из символов LRI, RLI или FSI, встреченного ранее. Прерывает ровно один, последний из встреченных, символ.
LRM | U+200E | LEFT-TO-RIGHT MARK | Текст после символа LRM отображается слева направо. Действие LRM прерывается символом перевода строки.
RLM | U+200F | RIGHT-TO-LEFT MARK | Текст после символа RLM отображается справа налево. Действие RLM прерывается символом перевода строки.
ALM | U+061C | ARABIC LETTER MARK | Текст после символа ALM отображается справа налево. Действие ALM прерывается символом перевода строки.
ZWSP | U+200B | ZERO WIDTH SPACE | Неотображаемый пробельный символ. Использование символа ZWSP привести к тому, что разные строки будут отображаться одинаково. Например, 'str[ZWSP]ing' отображается как 'string'.

Рассмотрим следующий фрагмент кода:

    #include <iostream>

    int main()
    {
      bool isAdmin = false;
      /*[RLO] } [LRI] if (isAdmin)[PDI] [LRI] begin admins only */ // (1)
          std::cout << "You are an admin.\n";
      /* end admins only [RLO]{ [LRI]*/                            // (2)
      return 0;
    }

Изучим детально строку (1).

    [LRI] if (isAdmin)[PDI]

Здесь символ [LRI] действует до символа [PDI]. Строка 'if (isAdmin)' будет отображаться слева направо и считается изолированной, получаем 'if (isAdmin)'.

    [LRI] begin admins only */

Здесь символ [LRI] действует до конца строки. Получаем изолированную строку: 'begin admins only */'

    [RLO] {пробел1}, '}', {пробел2}, 'if (isAdmin)', 'begin admins only */'

Здесь символ [RLO] действует до конца строки и отображает текст справа налево. Каждая из полученных в предыдущих пунктах изолированных строк рассматривается как отдельный неделимый символ. Получаем такую последовательность:

    'begin admins only */', 'if (isAdmin)', {пробел2}, '{', {пробел1}

Обратите внимание, что символ закрывающей фигурной скобки теперь отображается как '{' вместо '}'.

Итоговый вид строки (1), который может быть отображен в редакторе:

    /* begin admins only */ if (isAdmin) {

Похожие преобразования затронут и строку (2), которая отобразится так:

    /* end admins only */ }

Финальный вид кода, который может отобразиться в редакторе:

    #include <iostream>

    int main()
    {
      bool isAdmin = false;
      /* begin admins only */ if (isAdmin) {
          std::cout << "You are an admin.\n";
      /* end admins only */ }
      return 0;
    }

Ревьювер может посчитать, что в коде выполняется некоторая проверка перед выводом сообщения. Он проигнорирует комментарии и подумает, что код должен выполняться так:

    #include <iostream>

    int main()
    {
      bool isAdmin = false;
      if (isAdmin) {
        std::cout << "You are an admin.\n";
      }
      return 0;
    }

Однако, на самом деле, проверки нет. Для компилятора рассмотренный код выглядит так:

    #include <iostream>

    int main()
    {
      bool isAdmin = false;
      std::cout << "You are an admin.\n";
      return 0;
    }

Теперь рассмотрим более простой и в то же время более опасный пример использования неотображаемых символов:

    #include <string>
    #include <string_view>

    enum class BlockCipherType { DES, TripleDES, AES, /*....*/ };

    constexpr BlockCipherType
    StringToBlockCipherType(std::string_view str) noexcept
    {
      if (str == "AES[ZWSP]")
        return BlockCipherType::AES;
      else if (str == "TripleDES[ZWSP]")
        return BlockCipherType::TripleDES;
      else
        return BlockCipherType::DES;
    }

Функция 'StringToBlockCipherType' производит конвертацию строки в одно из значений перечисления 'BlockCipherType'. По коду можно сделать вывод, что функция возвращает три разных значения, однако это не так. Из-за того, что в конце каждого строкового литерала дописан неотображаемый пробельный символ [ZWSP], проверки на равенство со строками 'AES' и 'TriplesDES' будут ложными. В итоге из трех ожидаемых возвращаемых значений функция будет возвращать лишь 'BlockCipherType::DES'. В то же время код в редакторе может отображаться следующим образом:

    #include <string>
    #include <string_view>

    enum class BlockCipherType { DES, TripleDES, AES, /*....*/ };

    constexpr BlockCipherType
    StringToBlockCipherType(std::string_view str) noexcept
    {
      if (str == "AES")
        return BlockCipherType::AES;
      else if (str == "TripleDES")
        return BlockCipherType::TripleDES;
      else
        return BlockCipherType::DES;
    }

Если анализатор выдал предупреждение о неотображаемых символах на вашем коде, включите отображение невидимых символов в вашем редакторе и убедитесь, что они не изменяют логику выполнения программы.

Данная диагностика классифицируется как:

CWE-506

Взгляните на примеры ошибок, обнаруженных с помощью диагностики V1076.