CERT C++: EXP54-CPP

Не получайте доступ к объекту за пределами его жизни

Описание

Определение правила

Не получайте доступ к объекту за пределами его жизни.[1]

Реализация Polyspace

Эта проверка проверяет на наличие следующих проблем:

  • Неинициализированный указатель.

  • Неинициализированная переменная.

  • Использование ранее освобожденного указателя.

  • Указатель или ссылка на переменную стека, покидающую возможности.

  • Доступ к объекту с временным сроком службы.

Примеры

расширить все

Проблема

Неинициализированный указатель возникает, когда указателю не назначается адрес перед отсрочкой.

Риск

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

Зафиксировать

Исправление зависит от первопричины дефекта. Например, вы назначили адрес указателю, но назначение недоступно.

Часто детали результата показывают последовательность событий, которые привели к дефекту. Вы можете реализовать исправление на любом событии в последовательности. Если сведения о результате не отображают историю событий, можно отследить их с помощью опций правого щелчка в исходном коде и просмотреть предыдущие связанные события. Смотрите также Результаты интерпретации Bug Finder в интерфейсе пользователя Polyspace Desktop.

См. примеры исправлений ниже. Рекомендуется инициализировать указатель на NULL при объявлении указателя.

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

Пример - Неинициализированная ошибка указателя
#include <stdlib.h>

int* assign_pointer(int* prev)
{
    int j = 42;
    int* pi;

    if (prev == nullptr) 
      {
        pi = new int;
        if (pi == nullptr) return NULL;
      }
    *pi = j;                    
    /* Defect: Writing to uninitialized pointer */

    return pi;
}

Если prev не nullptr, указатель pi не присваивается адрес. Однако pi определяется на каждом пути выполнения независимо от того, prev является nullptr или нет.

Коррекция - Инициализация указателя на каждом пути выполнения

Одной из возможных коррекций является назначение адреса pi когда prev не nullptr. Также инициализируйте pi как nullptr во время его объявления.

#include <cstdlib>

int* assign_pointer(int* prev)
{
    int j = 42;
/*Fix: Initialize pointers by using nullptr during declaration*/
    int* pi = nullptr;

    if (prev == NULL) 
       {
        pi = new int;
        if (pi == nullptr) return NULL;
       } 
    /* Fix: Initialize pi in branches of if statement  */
    else 
        pi = prev;              
    *pi = j;
    return pi;
}
Проблема

Неинициализированная переменная возникает, когда переменная не инициализируется до чтения ее значения.

Риск

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

Зафиксировать

Исправление зависит от первопричины дефекта. Например, вы присвоили значение переменной, но присвоение недоступно, или вы присвоили значение переменной в одной из двух ветвей условного оператора. Исправьте недостижимый код или отсутствующее назначение.

Часто детали результата показывают последовательность событий, которые привели к дефекту. Вы можете реализовать исправление на любом событии в последовательности. Если сведения о результате не отображают историю событий, можно отследить их с помощью опций правого щелчка в исходном коде и просмотреть предыдущие связанные события. Смотрите также Результаты интерпретации Bug Finder в интерфейсе пользователя Polyspace Desktop.

См. примеры исправлений ниже. Рекомендуется инициализировать переменную при объявлении.

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

Пример - Неинициализированная ошибка переменной
int get_sensor_value(void)
{
    extern int getsensor(void);
    int command;
    int val;

    command = getsensor();
    if (command == 2) 
      {
        val = getsensor();
      }

    return val;              
    /* Defect: val does not have a value if command is not 2 */
}

Если command не 2, переменная val не назначен. В этом случае возврат значение функции get_sensor_value не определено.

Коррекция - Инициализация во время объявления

Одной из возможных коррекций является инициализация val во время объявления так, чтобы инициализация не обходилась по некоторым путям выполнения.

int get_sensor_value(void)
{
    extern int getsensor(void);
    int command;
    /* Fix: Initialize val */
    int val=0;

    command = getsensor();
    if (command == 2) 
      {
        val = getsensor();
      }

    return val;              
 }

val присваивается начальное значение 0. Когда command не равно 2, функция get_sensor_value возвращает это значение.

Проблема

Использование ранее освобожденного указателя происходит, когда вы получаете доступ к блоку памяти после удаления блока, например, при помощи free функцию или delete оператор.

Риск

Когда указателю выделяется динамическая память при помощи функций malloc, calloc, realloc или оператор new, это указывает на место памяти на куче. Когда вы используете free функцию или delete оператор на этом указателе, связанный блок памяти отменяется. Попытка получить доступ к этому блоку памяти может привести к непредсказуемому поведению или даже отказу сегментации.

Зафиксировать

Исправление зависит от первопричины дефекта. Посмотрите, намеревались ли вы позже отменить выделение памяти или выделить указателю другой блок памяти перед доступом.

Как хорошая практика, после удаления блока памяти назначьте соответствующий указатель на nullptr. Перед снятием указателей проверьте, nullptr ли они и обработайте ошибку. Таким образом, вы защищены от доступа к освобожденному блоку.

Пример - Использование ранее освобожденной ошибки указателя
#include <cstdlib>
int increment_content_of_address(int base_val, int shift)
   { 
    int j;
    int* pi = new int;
    if (pi == NULL) return 0;

    *pi = base_val;
    delete pi;

    j = *pi + shift;
    /* Defect: Reading a deallocated pointer */
 
    return j;
   }

The delete оператор освобождает блок памяти, который pi относится к. Поэтому разыменование pi после delete pi; недопустимый оператор.

Коррекция - Удаление указателя после использования

Одной из возможных коррекций является отмена выделения указателя pi только после последнего образца, к которому осуществляется доступ.

#include <cstdlib>

int increment_content_of_address(int base_val, int shift)
{
    int j;
    int* pi = new int;
    if (pi == NULL) return 0;

    *pi = base_val;

    j = *pi + shift;
    *pi = 0;

    /* Fix: The pointer is deallocated after its last use */
    delete pi;               
    return j;
}
Коррекция - Использование std::unique_ptr

Другой возможной коррекцией является использование std::unique_ptr вместо необработанного указателя. Умные указатели, такие как std::unique_ptr управляет собственными ресурсами. поскольку вы не должны явным образом отменять выделение смарт-указателей, доступ к ним не осуществляется непреднамеренно после удаления.

#include <cstdlib>
#include <memory>

int increment_content_of_address(int base_val, int shift)
{
    int j;
    /* Fix: A smart pointer is used*/
    std::unique_ptr<int>   pi(new int(3));
    if (pi == nullptr) return 0;

    *pi = base_val;

    j = *pi + shift;
    *pi = 0;
    return j;
}
Проблема

Указатель или ссылка на переменную стека, покидающую возможности, происходит, когда указатель или ссылка на локальную переменную покидает возможности переменной. Для образца:

  • Функция возвращает указатель на локальную переменную.

  • Функция выполняет назначение globPtr = &locVar. globPtr является переменной глобального указателя и locVar является локальной переменной.

  • Функция выполняет назначение *paramPtr = &locVar. paramPtr является параметром функции, который, для образца, является int** указатель и locVar является локальным int переменная.

  • Метод C++ выполняет назначение memPtr = &locVar. memPtr является представителем данных указателя класса, которому принадлежит метод. locVar является локальной переменной метода.

Дефект также относится к памяти, выделенной с помощью alloca функция. Дефект не применяется к статическим локальным переменным.

Риск

Локальным переменным выделяется адрес в стеке. Когда возможности локальной переменной заканчиваются, этот адрес доступен для повторного использования. Использование этого адреса для доступа к локальному значению переменных вне переменных возможностей может вызвать неожиданное поведение.

Если указатель на локальную переменную покидает возможности переменной, Polyspace® Bug Finder™ подсвечивает дефект. Дефект появляется, даже если вы не используете адрес, сохраненный в указателе. Для поддерживаемого кода рекомендуется не позволять указателю покидать возможности переменной. Даже если вы не используете адрес в указателе сейчас, кто-то другой, использующий вашу функцию, может использовать адрес, вызывая неопределенное поведение.

Зафиксировать

Не разрешайте указателю или ссылке на локальную переменную покидать возможности переменной.

Пример - Указатель на локальную переменную, возвращенную из функции
void func2(int *ptr) {
    *ptr = 0;
}

int* func1(void) {
    int ret = 0;
    return &ret ;
}
void main(void) {
    int* ptr = func1() ;
    func2(ptr) ;
}

В этом примере func1 возвращает указатель на локальную переменную ret.

В main, ptr указывает на адрес локальной переменной. Когда ptr доступ осуществляется в func2, доступ запрещен, потому что возможности ret ограничивается func1,

Пример - Указатель на локальную переменную избегает через выражение Лямбды
auto createAdder(int amountToAdd) {
  int addThis = amountToAdd;
  auto adder = [&] (int initialAmount) {
      return (initialAmount + addThis);
  };
  return adder;
}
 
void func() {
  auto AddByTwo = createAdder(2);
  int res = AddByTwo(10);
}

В этом примере createAdder функция задает лямбда-выражение adder который захватывает локальную переменную addThis по ссылке. Область возможностей addThis ограничивается createAdder функция. Когда объект вернулся createAdder вызывается, ссылка на переменную addThis доступ к нему за пределами его возможностей. При доступе таким способом значение addThis не определено.

Коррекция - Захватывайте локальные переменные путем копирования в выражении Лямбда вместо ссылки

Если функция возвращает объект выражения лямбды, избегайте захвата локальных переменных путем ссылки в объекте лямбды. Вместо этого соберите переменные путем копирования.

Переменные, захваченные копией, имеют то же время жизни, что и лямбда- объект, но переменные, захваченные ссылкой, часто имеют меньшее время жизни, чем сама лямбда объекта. Когда используется объект lambda, эти переменные, доступные вне возможностей, имеют неопределенные значения.

auto createAdder(int amountToAdd) {
  int addThis = amountToAdd;
  auto adder = [=] (int initialAmount) {
      return (initialAmount + addThis);
  };
  return adder;
}
 
void func() {
  auto AddByTwo = createAdder(2);
  int res = AddByTwo(10);
}
Проблема

Доступ к объекту с временным временем жизни происходит при попытке считать или записать в объект с временным временем жизни, который возвращается вызовом функции. В структуре или объединении, возвращенном функцией и содержащем массив, представители массива являются временными объектами. Срок жизни временных объектов заканчивается:

  • Когда полное выражение или полный декларатор, содержащий вызов, заканчивается, как определено в C11 Standard.

  • После следующей точки последовательности, как определено в C90 и C99 стандартах. Точка последовательности является точкой выполнения программы, где все предыдущие вычисления завершены, и последующая оценка еще не началась.

Для кода С++ объект Accessing с временным временем жизни поднимает дефект только тогда, когда вы записываете в объект с временным временем жизни.

Если временный объект жизни возвращается по адресу, дефект не возникает.

Риск

Изменение объектов с временным временем жизни является неопределенным поведением и может вызвать ненормальное завершение программы и проблемы переносимости.

Зафиксировать

Присвойте объект, возвращенный из вызова функции, локальной переменной. Содержимое объекта временного срока службы копируется в переменную. Теперь вы можете изменять его безопасно.

Пример - Изменение временного объекта жизни, возвращенного вызовом функции
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#define SIZE6 6

struct S_Array
{
    int t;
    int a[SIZE6];
};

struct S_Array func_temp(void);

/* func_temp() returns a struct value containing
* an array with a temporary lifetime.
*/
int func(void) {
 
/*Writing to temporary lifetime object is
 undefined behavior
 */
    return ++(func_temp().a[0]); 
}

void main(void) {
    (void)func();
}

В этом примере func_temp() возвращает по значению структуру с представителем массива a. У этот представитель есть временное время жизни. Его увеличение является неопределенным поведением.

Коррекция - Присвоение возвращенного значения локальной переменной перед записью

Одной из возможных коррекций является назначение возврата вызова func_temp() в локальную переменную. Содержимое временного объекта a копируется в переменную, которую можно безопасно увеличить.

 #include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#define SIZE6 6

struct S_Array
{
    int t;
    int a[SIZE6];
};

struct S_Array func_temp(void);

int func(void) {

/* Assign object returned by function call to 
 *local variable
 */
    struct S_Array s = func_temp(); 

/* Local variable can safely be
 *incremented
 */
    ++(s.a[0]);                                           
    return s.a[0];
}

void main(void) {
    (void)func();
}

Проверяйте информацию

Группа: 02. Выражения (EXP)
Введенный в R2019a

[1] Это программное обеспечение было создано MathWorks, включающее фрагменты: «Сайт SEI CERT-C», © 2017 Университет Карнеги Меллон, Веб-сайт SEI CERT-C + + © 2017 Университет Карнеги Меллон, "Стандарт кодирования SEI CERT C - Правила разработки безопасных, Надежные и безопасные системы - 2016 Edition ", © 2016 Университет Карнеги Меллон, и "Стандарт кодирования SEI CERT C++ - Правила разработки безопасных, Надежные и безопасные системы в C++ - 2016 Edition "© 2016 Университет Карнеги Меллон, с специального разрешения от его Института программной инженерии.

ЛЮБОЙ МАТЕРИАЛ УНИВЕРСИТЕТА КАРНЕГИ МЕЛЛОН И/ИЛИ ЕГО ИНЖЕНЕРНОГО ИНСТИТУТА ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ, СОДЕРЖАЩИЙСЯ В НАСТОЯЩЕМ ДОКУМЕНТЕ, ПОСТАВЛЯЕТСЯ НА БАЗИСЕ «КАК ЕСТЬ». УНИВЕРСИТЕТ КАРНЕГИ МЕЛЛОН НЕ ДАЕТ НИКАКИХ ГАРАНТИЙ, ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, В ОТНОШЕНИИ ЛЮБОГО ВОПРОСА, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ, ГАРАНТИЮ ПРИГОДНОСТИ ДЛЯ ЦЕЛЕЙ ИЛИ КОММЕРЧЕСКОЙ ВЫГОДЫ, ИСКЛЮЧИТЕЛЬНОСТИ, ИЛИ УНИВЕРСИТЕТ КАРНЕГИ МЕЛЛОН НЕ ДАЕТ НИКАКИХ ГАРАНТИЙ В ОТНОШЕНИИ СВОБОДЫ ОТ ПАТЕНТА, ТОВАРНОГО ЗНАКА ИЛИ НАРУШЕНИЯ АВТОРСКИХ ПРАВ.

Это программное обеспечение и связанная с ним документация не были рассмотрены и не одобрены Университетом Карнеги-Меллон или его Институтом программной инженерии.