# V1098. The 'emplace' / 'insert' function call contains potentially dangerous move operation. Moved object can be destroyed even if there is no insertion.

- Функция 'try_emplace' для 'std::map' / 'std::unordered_map'
- Функции 'lower_bound' и 'emplace_hint' для 'std::set' / 'std::map'
- Примечание N1
- Примечание N2

Анализатор обнаружил потенциально опасное перемещение объекта в ассоциативный контейнер 'std::set' / 'std::map' / 'std::unordered_map' посредством вызова функции 'emplace' / 'insert' . Если элемент с указанным ключом уже существует в контейнере, перемещение может привести к преждевременному освобождению ресурсов.

Рассмотрим следующий пример:

    using pointer_type = std::unique_ptr<void, void (*)(void *)>;

    std::unordered_map<uintmax_t, pointer_type> Cont;

    // Unique pointer should be moved only if
    // there is no element in the container by the specified key
    bool add_entry(uintmax_t key, pointer_type &&ptr)
    {
      auto [it, inserted] = Cont.emplace(key, std::move(ptr));

      if (!inserted)
      {
        // dereferencing the potentially null pointer 'ptr' here
      }

      return inserted;
    }

В примере в функцию 'add_entry' передается умный указатель на некоторый ресурс и соответствующий ему ключ. По задумке программиста, умный указатель должен перемещаться в ассоциативный контейнер лишь в том случае, если ранее не было вставки с тем же ключом. Если вставки не произошло, то далее с ресурсом будет произведена некоторая работа через умный указатель.

Однако такой код содержит две проблемы:

- Если вставки не произошло, ресурс из указателя 'ptr' всё равно может быть перемещён. Это приведет к тому, что он будет освобождён раньше времени.
- Указатель 'ptr' может стать нулевым, а его разыменование приведёт к неопределенному поведению.

Рассмотрим возможные способы исправления проблем.

## Функция 'try_emplace' для 'std::map' / 'std::unordered_map'

Начиная со стандарта С++17, для контейнеров 'std::map' и 'std::unordered_map' была добавлена функция 'try_emplace'. Она гарантирует, что если элемент с указанным ключом уже существует, то аргументы функции не будут скопированы или перемещены. Поэтому для контейнеров 'std::map' и 'std::unordered_map' рекомендуется использовать именно эту функцию вместо 'emplace' и 'insert'.

Исправленный код:

    using pointer_type = std::unique_ptr<void, void (*)(void *)>;

    std::unordered_map<uintmax_t, pointer_type> Cont;

    bool add_entry(uintmax_t key, pointer_type &&ptr)
    {
      auto [it, inserted] = Cont.try_emplace(key, std::move(ptr));

      if (!inserted)
      {
        // dereferencing the 'ptr' here
        // 'ptr' is guaranteed to be non-null
      }

      return inserted;
    }

## Функции 'lower_bound' и 'emplace_hint' для 'std::set' / 'std::map'

Если функция 'try_emplace' недоступна, то для ассоциативных упорядоченных контейнеров ('std::set', 'std::map') поиск и вставку можно разделить на две операции:

- функция 'lower_bound' позволит либо найти элемент по заданному ключу, либо позицию вставки для нового элемента;
- функция 'emplace_hint' позволит эффективно вставить элемент.

Рассмотрим предыдущий пример, заменив контейнер на 'std::map' и воспользовавшись функциями 'lower_bound' и 'emplace_hint':

    using pointer_type = std::unique_ptr<void, void (*)(void *)>;

    std::map<uintmax_t, pointer_type> Cont;

    // Unique pointer should be moved only if
    // there is no element in the container by the specified key
    bool add_entry(uintmax_t key, pointer_type &&ptr)
    {
      bool inserted;
      auto it = Cont.lower_bound(key);
      if (it != Cont.end() && key == it->first)
      {
        // key exists
        inserted = false;
      }
      else
      {
        // key doesn't exist
        it = Cont.emplace_hint(it, key, std::move(ptr));
        inserted = true;
      }

      if (!inserted)
      {
        // dereferencing the 'ptr' here
        // 'ptr' is guaranteed to be non-null
      }

      return inserted;
    }

## Примечание N1

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

    using pointer_type = std::unique_ptr<void, void (*)(void *)>;

    std::map<uintmax_t, pointer_type> Cont;

    // Unique pointer should be moved only if
    // there is no element in the container by the specified key
    bool add_entry(uintmax_t key, pointer_type &&ptr)
    {
      bool inserted;

      auto it = Cont.find(key);
      if (it == Cont.end())
      {
        std::tie(it, inserted) = Cont.emplace(key, std::move(ptr)); // <=
      }
      else
      {
        inserted = false;
      }

      if (!inserted)
      {
        // dereferencing the 'ptr' here
        // 'ptr' is guaranteed to be non-null
      }

      return inserted;
    }

В примере нет ошибки: если элемента по заданному ключу не существует, то гарантированно произойдет вставка. Однако код не оптимален: сначала ведется поиск элемента по ключу, затем поиск повторяется для нахождения позиции вставки внутри функции 'emplace'. Поэтому рекомендуется оптимизировать код одним из описанных ранее способов.

## Примечание N2

Диагностика имеет два уровня достоверности. Первый уровень выдаётся для move-only объектов, т.е. когда пользовательский тип не имеет конструкторов и операторов копирования. Это значит, при неудачной вставке ресурс может быть освобождён раньше времени. Например, это актуально для типов 'std::unique_ptr' и 'std::unique_lock'. Иначе будет выдан второй уровень достоверности.

Диагностика не работает с типами, у которых отсутствует конструктор перемещения, т.к. в таком случае объекты будут копироваться.

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

CWE-826