﻿# Сворачиваем CPython вокруг PVS\-Studio

Python\.\.\. язык программирования, не нуждающийся в особом представлении\. За удобство в обработке "больших данных" заслуженно получил звание "лучшего Excel"\. За удобство интеграции в C и C\+\+ код его любит геймдев\. А также у него низкий порог вхождения\!

![1246_CurlingCPythonAround_ru/image1.png](https://import.viva64.com/docx/blog/1246_CurlingCPythonAround_ru/image1.png)

## Введение

В этой статье мы рассмотрим основную и самую распространённую вариацию интерпретатора Python — CPython\. Из названия следует, что она написана на C\. Предыдущая проверка была почти [девять лет назад](https://pvs-studio.ru/ru/blog/posts/cpp/0414/), и без [уточнений](https://pvs-studio.ru/ru/blog/posts/cpp/0418/) тоже не обошлось\. С тех пор язык сильно шагнул вперёд в серии релизов Python 3, и внутренний мир интерпретатора изменялся вместе с ним\. API [Python/C](https://docs.python.org/3/c-api/index.html) тоже расширялся, переписывались по несколько раз встроенные модули интерпретатора, а некоторые ушли в историю после стадии `DeprecationWarning`\. И вот однажды\.\.\.

## Установка PVS\-Studio

Начнём с установки PVS\-Studio\. Загрузить установочный дистрибутив можно с [этой](https://pvs-studio.ru/ru/pvs-studio/download/) страницы\. Для запуска анализа понадобится лицензия — её триальную версию так же просто получить, буквально [по щелчку мыши](https://pvs-studio.ru/ru/pvs-studio/try-free/) и быстрому аккорду пальцами по клавиатуре\. Установка анализатора тоже пройдёт легко: нам понадобится только одна дополнительная интеграция для Visual Studio 2022\. Актуальная на момент написания статьи версия PVS\-Studio — 7\.35\.

## Конфигурация сборки и анализа

И вот однажды на наш стол попал готовящийся к выпуску релиз CPython 3\.13\.3, который мы взяли на коммите [e6dfa9d](https://github.com/python/cpython/tree/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2)\. Обычно я проверяю проекты в "отладочной" \(Debug\) конфигурации, но в этот раз меня разобрало любопытство и захотелось попробовать запустить анализ на "релизной" \(Release\), чтобы посмотреть на происходящее в интерпретаторе, поставляемом конечному пользователю\.

Современные версии CPython 3 требуют немного повозиться перед запуском сборки под Windows: перейдите в папку `PCbuild` и запустите из неё файл `get_externals.bat`\. Будут загружены сторонние зависимости, необходимые для сборки интерпретатора\.

## Анализ найденных ошибок

В той же папке `PCbuild` находится файл решения `pcbuild.sln`, настроенный для полной сборки CPython\. Когда запускался batch\-файл `get_externals.bat`, в консоли перечислялись имена папок сторонних зависимостей:

```cpp
D:\Git\cpython\PCbuild>get_externals.bat
Using py -3.12 (found 3.12 with py.exe)
Fetching external libraries...
Fetching bzip2-1.0.8...
Fetching mpdecimal-4.0.0...
Fetching sqlite-3.45.3.0...
Fetching xz-5.2.5...
Fetching zlib-1.3.1...
Fetching external binaries...
Fetching libffi-3.4.4...
Fetching openssl-bin-3.0.15...
Fetching tcltk-8.6.15.0...
Finished.
```

Не надо перечислять каждую папку для исключения из анализа: достаточно добавить одну папку `externals`, находящуюся уровнем выше\. Чтобы исключить её из анализа при работе в Visual Studio, перейдите в соответствующий раздел настроек: **Extensions \> PVS\-Studio \> Options \> Don't Check Files**\. Это не единственный способ изменить исключённые из анализа папки — больше способов описано [в нашей документации](https://pvs-studio.ru/ru/docs/manual/6640/)\. Для своего удобства я исключил сторонние зависимости через абсолютный путь:

```cpp
D:\Git\cpython\externals\
```



Что ж, лезем внутрь\.\.\.

### MD5 вселяет уверенность\.\.\.

Предупреждения PVS\-Studio:

[V522](https://pvs-studio.ru/ru/docs/warnings/v522/) There might be dereferencing of a potential null pointer 'p'\. Check lines: 1174, 1173\. [Hacl\_Hash\_MD5\.c 1174](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/Modules/_hacl/Hacl_Hash_MD5.c#L1174)

```cpp
#ifndef KRML_HOST_MALLOC
#  define KRML_HOST_MALLOC malloc
#endif

Hacl_Streaming_MD_state_32 *Hacl_Hash_MD5_malloc(void)
{
  ....
  Hacl_Streaming_MD_state_32
  *p = (Hacl_Streaming_MD_state_32 *)
        KRML_HOST_MALLOC(sizeof (Hacl_Streaming_MD_state_32));
  p[0U] = s;                                                   // <=
  ....
}
```



[V575](https://pvs-studio.ru/ru/docs/warnings/v575/) The potential null pointer is passed into 'memcpy' function\. Inspect the first argument\. Check lines: 1415, 1414\. [Hacl\_Hash\_MD5\.c 1415](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/Modules/_hacl/Hacl_Hash_MD5.c#L1415)

[V575](https://pvs-studio.ru/ru/docs/warnings/v575/) The potential null pointer is passed into 'memcpy' function\. Inspect the first argument\. Check lines: 1417, 1416\. [Hacl\_Hash\_MD5\.c 1417](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/Modules/_hacl/Hacl_Hash_MD5.c#L1417)

```cpp
#ifndef KRML_HOST_CALLOC
#  define KRML_HOST_CALLOC calloc
#endif

Hacl_Streaming_MD_state_32 *
Hacl_Hash_MD5_copy(Hacl_Streaming_MD_state_32 *state)
{
  Hacl_Streaming_MD_state_32 scrut = *state;
  uint32_t *block_state0 = scrut.block_state;
  uint8_t *buf0 = scrut.buf;
  ....
  uint8_t *buf = (uint8_t *)KRML_HOST_CALLOC(64U, sizeof (uint8_t));
  memcpy(buf, buf0, 64U * sizeof (uint8_t));                         // <=
  uint32_t *block_state = (uint32_t *)KRML_HOST_CALLOC(4U, sizeof (uint32_t));
  memcpy(block_state, block_state0, 4U * sizeof (uint32_t));         // <=
  ....
}
```



Пошло довольно кучно\. Нет, эти три срабатывания не являются основной причиной, из\-за которой MD5 признали некриптостойким алгоритмом хеширования\. Дело в другом: выделение памяти через `calloc`/`malloc` [не гарантирует](https://pvs-studio.ru/ru/blog/posts/cpp/0938/) возврата указателя на выделенную память\.

Функция `memcpy` с виду безобидная букашка\. Настолько безобидная, что тот же MSVC за использование этой функции [побьёт SDL\-проверками](https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/memcpy-wmemcpy) и будет прав\. Теоретически `memcpy` может успеть повредить данные в памяти перед тем, как порвать интерпретатор на части из\-за `NULL` вместо буфера\. Реализация этой функции не обязана читать области памяти с начала, и если переданный размер достаточно большой, то перед падением программы она может получить доступ к запрещённой области памяти\.

### \.\.\.а управляющие структуры — нет?

Предупреждение PVS\-Studio:

[V547](https://pvs-studio.ru/ru/docs/warnings/v547/) Expression 'fut\-\>fut\_state \!\= STATE\_PENDING' is always false\. [\_asynciomodule\.c 506](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/Modules/_asynciomodule.c#L506)

```cpp
static PyObject *
future_set_exception(asyncio_state *state, FutureObj *fut, PyObject *exc)
{
  PyObject *exc_val = NULL;

  if (fut->fut_state != STATE_PENDING) {           // <=
      PyErr_SetString(state->asyncio_InvalidStateError, "invalid state");
      return NULL;
  }

  if (PyExceptionClass_Check(exc)) {
      exc_val = PyObject_CallNoArgs(exc);
      if (exc_val == NULL) {
        return NULL;
      }
      if (fut->fut_state != STATE_PENDING) {      // <=
          Py_DECREF(exc_val);
          PyErr_SetString(state->asyncio_InvalidStateError, "invalid state");
          return NULL;
      }
  }
  ....
}
```



Очень [частый случай](https://pvs-studio.ru/ru/blog/examples/v547/)\. Уже есть проверка `fut->fut_state`:

```cpp
if (fut->fut_state != STATE_PENDING) {           // <=
    PyErr_SetString(state->asyncio_InvalidStateError, "invalid state");
    return NULL;
}
```



Мы сообщили о неправильном состоянии просто текстом, и теперь хотим проверить, надо ли нам бросать исключение\.\.\. и снова проверяем значение `fut->fut_state`? Но первая проверка показала, что там `STATE_PENDING`\! В итоге лишняя ссылка на объект исключения так и будет висеть\. Уплощаем и упрощаем код:

```cpp
if (PyExceptionClass_Check(exc)) {
    exc_val = PyObject_CallNoArgs(exc);
    if (exc_val == NULL) {
      return NULL;
    }
    Py_DECREF(exc_val);
    PyErr_SetString(state->asyncio_InvalidStateError, "invalid state");
    return NULL;
}
```

Вот теперь порядок\. Коллекция схожих срабатываний:

* V547 Expression 'start\_state \!\= deque\-\>state' is always false\. \_collectionsmodule\.c [1156](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/Modules/_collectionsmodule.c#L1156)
* V547 Expression 'start\_state \!\= deque\-\>state' is always false\. \_collectionsmodule\.c [1190](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/Modules/_collectionsmodule.c#L1190)
* V547 Expression 'start\_state \!\= deque\-\>state' is always false\. \_collectionsmodule\.c [1450](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/Modules/_collectionsmodule.c#L1450)

### Невозможное невозможно

Предупреждение PVS\-Studio:

[V547](https://pvs-studio.ru/ru/docs/warnings/v547/) Expression 'error\_handler \=\= \_Py\_ERROR\_UNKNOWN' is always false\. [unicodeobject\.c 15765](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/Objects/unicodeobject.c#L15765)



Осторожно, сейчас будет много контекста:

```cpp
static _Py_error_handler
get_error_handler_wide(const wchar_t *errors)
{
    if (errors == NULL || wcscmp(errors, L"strict") == 0) {
        return _Py_ERROR_STRICT;
    }
    if (wcscmp(errors, L"surrogateescape") == 0) {
        return _Py_ERROR_SURROGATEESCAPE;
    }
    if (wcscmp(errors, L"replace") == 0) {
        return _Py_ERROR_REPLACE;
    }
    if (wcscmp(errors, L"ignore") == 0) {
        return _Py_ERROR_IGNORE;
    }
    if (wcscmp(errors, L"backslashreplace") == 0) {
        return _Py_ERROR_BACKSLASHREPLACE;
    }
    if (wcscmp(errors, L"surrogatepass") == 0) {
        return _Py_ERROR_SURROGATEPASS;
    }
    if (wcscmp(errors, L"xmlcharrefreplace") == 0) {
        return _Py_ERROR_XMLCHARREFREPLACE;
    }
    return _Py_ERROR_OTHER;
}

static int
init_fs_codec(PyInterpreterState *interp)
{
    const PyConfig *config = _PyInterpreterState_GetConfig(interp);

    _Py_error_handler error_handler;
    error_handler = get_error_handler_wide(config->filesystem_errors);
    if (error_handler == _Py_ERROR_UNKNOWN) {                              // <=
        PyErr_SetString(PyExc_RuntimeError, "unknown filesystem error handler");
        return -1;
    }
    ....
}
```

Если вы чувствуете запах подгоревшей чешуи, вы на верном пути :\) Если не чувствуете, не бойтесь, сейчас всё покажем\.



Функция `get_error_handler_wide` возвращает что угодно, кроме `_Py_ERROR_UNKNOWN`, на который и идёт проверка\. Из неё невозможно выйти с этим результатом: все пути **ветвления** заканчиваются на `return` с каким\-либо определением ошибки\. Раз у нас богатый выбор сюжетных концовок, почему бы не посмотреть все? Хм\-м\-м\.\.\.

```cpp
typedef enum {
    _Py_ERROR_UNKNOWN=0,          // <=
    _Py_ERROR_STRICT,
    _Py_ERROR_SURROGATEESCAPE,
    _Py_ERROR_REPLACE,
    _Py_ERROR_IGNORE,
    _Py_ERROR_BACKSLASHREPLACE,
    _Py_ERROR_SURROGATEPASS,
    _Py_ERROR_XMLCHARREFREPLACE,
    _Py_ERROR_OTHER
} _Py_error_handler;
```



Ой, а тут есть одна ~~секретная~~ незадействованная концовка\. Можно спекулятивно предположить, что вместо "прочей ошибки" и должна быть "неизвестная ошибка", но когда использовать "неизвестную ошибку", если есть "прочая ошибка"? Возможно, ответ дадут несколько мест, где явно присваивается это состояние, но для рассматриваемого случая они все мимо\.

![1246_CurlingCPythonAround_ru/image2.png](https://import.viva64.com/docx/blog/1246_CurlingCPythonAround_ru/image2.png)

### Объективно не нулевой

Сообщение PVS\-Studio:

[V547](https://pvs-studio.ru/ru/docs/warnings/v547/) Expression 'capi \=\= NULL' is always false\. [\_datetimemodule\.c 7236](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/Modules/_datetimemodule.c#L7236)

```cpp
/* The C-API is process-global.  This violates interpreter isolation
 * due to the objects stored here.  Thus each of those objects must
 * be managed carefully. */
// XXX Can we make this const?
static PyDateTime_CAPI capi = {
    ....
}

/* Get a new C API by calling this function.
 * Clients get at C API via PyDateTime_IMPORT, defined in datetime.h.
 */
static inline PyDateTime_CAPI *
get_datetime_capi(void)
{
    return &capi;
}

static int
_datetime_exec(PyObject *module)
{
    ....
    /* At last, set up and add the encapsulated C API */
    PyDateTime_CAPI *capi = get_datetime_capi();
    if (capi == NULL) {                            // <=
        goto error;
    }
    ....
}
```

Посмотрите на вызов `get_datetime_capi`\. Судя по коду, функция возвращает указатель на `PyDateTime`, и программист предположил, что он может быть нулевым\. Взглянем на её реализацию: она просто возвращает указатель на _статическую_ переменную в глобальной области видимости\. Нет никаких причин думать, что на момент вызова этой функции ей не будет присвоено значение\. Точнее, корректного значения может и не быть, но адрес у этой переменной точно будет\. Более того, объявление структур и функций для Python/C расширений статическими — стандартный подход к их написанию, теряться нечему\. Если всё ещё не верите, то в ход пойдёт тяжёлая артиллерия — _WinDbg_\. Отладчик, который в первую очередь предназначен для работы на уровне ядра Windows\!

![1246_CurlingCPythonAround_ru/image3.png](https://import.viva64.com/docx/blog/1246_CurlingCPythonAround_ru/image3.png)

Чтобы всё было честно, я не стал присоединять отладчик на полностью запустившийся процесс, а запустил Python из\-под него\. Как можно заметить, интерпретатор ещё не выдал приглашение, а статическая структура `capi` уже заняла место в переднем ряду\. Вывод: проверку на нулевой указатель можно совершенно безболезненно убрать из кода, она ни на что не влияет\. Конечно же, если имплементация `get_datetime_capi` не меняется каким\-то хитрым образом\.

### SDK бьёт по рукам

Предупреждение PVS\-Studio:

[V547](https://pvs-studio.ru/ru/docs/warnings/v547/) Expression 't \!\= u' is always false\. [\_datetimemodule\.c 5245](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/Modules/_datetimemodule.c#L5245)

```cpp
static long long
local(long long u)
{
    struct tm local_time;
    time_t t;
    u -= epoch;
    t = u;
    if (t != u) {
        PyErr_SetString(PyExc_OverflowError,
        "timestamp out of range for platform time_t");
        return -1;
    }
    ....
}
```

Обсуждение этого срабатывания превратилось в 38\-минутный спор на повышенных тонах, и это не шутка для подогрева интереса\. В чём проблема?

Читатель уже догадывается, что я скажу про:

```cpp
t = u;
if (t != u) { .... }
```

И он будет мыслить в правильном направлении, мол, что за безобразие, сравниваем только что присвоенное значение с самим собой, ведь `time_t` уже `long long`\! А вот не всё так просто\.

Тип `time_t` [принудительно задаётся 64\-битным](https://learn.microsoft.com/en-us/cpp/c-runtime-library/time-management), начиная с Visual Studio 2005\. Нельзя использовать 32\-битный `time_t` на 64\-битной платформе, о чём нам говорит стандартный заголовочный файл `corecrt.h` из _Universal C Runtime_\. Visual Studio использует _UCRT_ с Visual Studio 2015\.

```cpp
#if defined _USE_32BIT_TIME_T && defined _WIN64
    #error You cannot use 32-bit time_t (_USE_32BIT_TIME_T) with _WIN64
#endif

#ifndef _CRT_NO_TIME_T
    #ifdef _USE_32BIT_TIME_T
        typedef __time32_t time_t;
    #else
        typedef __time64_t time_t;
    #endif
#endif
```

Visual Studio 2005 тоже это запрещает, о чём свидетельствует похожая цепочка директив препроцессора в стандартном заголовочном файле `crtdefs.h` из Microsoft Visual C Runtime \(MSVCRT\)\.

```cpp
#ifdef  _USE_32BIT_TIME_T
#ifdef  _WIN64
#error You cannot use 32-bit time_t (_USE_32BIT_TIME_T) with _WIN64
#undef  _USE_32BIT_TIME_T
#endif
#else
#if     _INTEGRAL_MAX_BITS < 64
#define _USE_32BIT_TIME_T
#endif
#endif
```

Но одно дело MSVC, а если взять MinGW? Там будет та же самая картина из MSVC, [просто форматирование отличается](https://sourceforge.net/p/mingw-w64/mingw-w64/ci/0e0eed62328177b31a54023d9e3dad7bc12c0fd0/tree/mingw-w64-headers/crt/corecrt.h#l128)\. Проблемы могут начаться, если `time_t` не определён в 64\-битное целое число, [но это уже совсем другая история](https://pvs-studio.ru/ru/docs/warnings/v795/)\.

### Дезориентация в пространстве

Пожалуй, хватит жести\. Давайте немного отдохнём и посмотрим на что\-нибудь попроще\. Например, сразу три сообщения PVS\-Studio:



[V523](https://pvs-studio.ru/ru/docs/warnings/v523/) The 'then' statement is equivalent to the 'else' statement\. [crossinterp\.c 1416](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/Python/crossinterp.c#L1416)

```cpp
void
_PyXI_FreeNamespace(_PyXI_namespace *ns)
{
    ....
    if (interpid == PyInterpreterState_GetID(PyInterpreterState_Get())) {
        _sharedns_free(ns);
    }
    else {
        // If we weren't always dynamically allocating the cross-interpreter
        // data in each item then we would need to using a pending call
        // to call _sharedns_free(), to avoid the race between freeing
        // the shared namespace and releasing the XI data.
        _sharedns_free(ns);
    }
}
```

[V524](https://pvs-studio.ru/ru/docs/warnings/v524/) It is odd that the body of 'FStar\_UInt128\_u32\_combine\_' function is fully equivalent to the body of 'FStar\_UInt128\_u32\_combine' function\. [FStar\_UInt128\_Verified\.h 309](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/Modules/_hacl/include/krml/FStar_UInt128_Verified.h#L309)

```cpp
static inline
uint64_t FStar_UInt128_u32_combine(uint64_t hi, uint64_t lo)  // <=
{
  return lo + (hi << FStar_UInt128_u32_32);
}

static inline
uint64_t FStar_UInt128_u32_combine_(uint64_t hi, uint64_t lo) // <=
{
  return lo + (hi << FStar_UInt128_u32_32);
}
```



[V1048](https://pvs-studio.ru/ru/docs/warnings/v1048/) The 'val' variable was assigned the same value\. [blob\.c 491](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/Modules/_sqlite/blob.c#L491)

```cpp
static int
ass_subscript_index(pysqlite_Blob *self, PyObject *item, PyObject *value)
{
    ....
    long val = PyLong_AsLong(value);
    if (val == -1 && PyErr_Occurred()) {
        PyErr_Clear();
        val = -1;                          // <=
    }
    ....
}
```

У всех трёх примеров есть общая проблема — случайно сорвавшаяся рука или неожиданное сбитие с мысли\. 



Ситуация из первого примера возникает у многих, это не обошло стороной даже [LLVM](https://pvs-studio.ru/ru/blog/posts/cpp/1126/), где к качеству кодовой базы относятся довольно трепетно\. Но в нашем случае код выглядит так, будто его хотели дописать позже и забыли\. 



Второй вариант проблемы наталкивает на вопрос: сбитие кого с мысли, писателя или читателя, — особенно при рассмотрении второго фрагмента кода\. Да, это `inline`, да, в теле функции буквально один и тот же текст, компилятор может встроить её и никто не заметит подвоха\. Вот только он _не обязан_ это делать\. Предположим, что встраивания тела функции не произошло\. Обе функции также `static`, и если не сработает `inline`, то это будут обычные обращения к двум разным статическим функциям\.



Третья проблема возникла, вероятно, на почве сомнений в [собственной документации](https://docs.python.org/3/c-api/long.html#c.PyLong_AsLong)\. Функция `PyLong_AsLong` возвращает `-1`, если вывести из `PyObject*` число типа `long` не удалось\. Это функция из стабильного ABI, изменение этого поведения маловероятно\.

### По стопам прошлых проверок

"Дежавю?" — спросил я себя, читая следующие сообщения от PVS\-Studio:

[V781](https://pvs-studio.ru/ru/docs/warnings/v781/) The value of the 'i' index is checked after it was used\. Perhaps there is a mistake in program logic\. [lexer\.c 143](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/Parser/lexer/lexer.c#L143)

```cpp
static int
set_fstring_expr(struct tok_state* tok, struct token *token, char c) {
  tokenizer_mode *tok_mode = TOK_GET_MODE(tok);
  ....
  if (hash_detected) {
    Py_ssize_t input_length =
      tok_mode->last_expr_size - tok_mode->last_expr_end;

    ....

    for (i = 0, j = 0; i < input_length; i++) {
      if (tok_mode->last_expr_buffer[i] == '#') {
        // Skip characters until newline or end of string
        while (   tok_mode->last_expr_buffer[i] != '\0'
               && i < input_length) {                           // <=
          ....
        }
....
}
```

Не показалось\. Такое уже было в [x64dbg](https://pvs-studio.ru/ru/blog/posts/cpp/1187/#ID03823B42D3)\. Доподлинно неизвестно, будет ли `input_length` всегда меньше `i`, и не возникнет ли чтение за пределами массива, если условие не будет соблюдено\.



[V1109](https://pvs-studio.ru/ru/docs/warnings/v1109/) The 'PathRemoveFileSpecW' function is deprecated\. Consider switching to an equivalent newer function\. [pyshellext\.cpp 464](https://github.com/python/cpython/blob/e6dfa9d6013018c1f5d8e3a72b73cdab2a5664c2/PC/pyshellext.cpp#L464)

```cpp
class DECLSPEC_UUID(CLASS_GUID) PyShellExt : public RuntimeClass<
  RuntimeClassFlags<ClassicCom>,
  IDropTarget,
  IPersistFile
>
{
public:
  STDMETHODIMP Load(LPCOLESTR pszFileName, DWORD dwMode) {
    ....
    if (!PathRemoveFileSpecW(target_dir)) {                     // <=
        OutputDebugStringW(
          L"PyShellExt::Load - failed to remove filespec from target"
        );
        return E_FAIL;
    }
    ....
  }
}
```

Срабатывание диагностики V1109 в дебаггере тоже было, но на [другую функцию](https://pvs-studio.ru/ru/blog/posts/cpp/1146/#ID688FFC349C)\. Для расширения оболочки проводника Windows это срабатывание актуально по другой причине: на официальном сайте прямо сказано, что Python 3\.13 не поддерживается на Windows 7 и более старых версиях системы\.

![1246_CurlingCPythonAround_ru/image4.png](https://import.viva64.com/docx/blog/1246_CurlingCPythonAround_ru/image4.png)

Вместо устаревшей функции Microsoft рекомендует использовать [`PathCchRemoveFileSpec`](https://learn.microsoft.com/en-us/windows/desktop/api/pathcch/nf-pathcch-pathcchremovefilespec), доступную для использования с Windows 8\. Преимущество новой функции с функциональной стороны — возможность работы с [UNC\-путями](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dfsc/149a3039-98ce-491a-9268-2f5ddef08192), в первую очередь с сетевыми папками\. С технической стороны — исключён риск переполнения буфера\. Функцию уже используют в CPython и вполне возможно, что актуализация используемых функций Windows API растянулась во времени\.

![1246_CurlingCPythonAround_ru/image5.png](https://import.viva64.com/docx/blog/1246_CurlingCPythonAround_ru/image5.png)

## Заключение

Сложно комментировать происходящее человеку, пишущему в свободное время преимущественно на Python 2\.6/2\.7 :\)



За последние несколько лет в Python 3 произошло очень много изменений, обсуждаемых многими критиками\. Одних возмущает увеличение количества синтаксического сахара, других радует возможность в официальном CPython выключить глобальную блокировку интерпретатора\. Обеспечить безопасность самого интерпретатора и стандартных библиотек, безусловно, важный пункт, который надо учитывать в процессе разработки, иначе знаменитые "батарейки" рискуют "протечь"\.



Интерпретатор языка программирования с простым синтаксисом сложно устроен изнутри, и с возрастом проекта эта сложность неизбежно увеличивается\. По забавному совпадению, в обсуждении одного из запросов на слияние появился вопрос про применение статического анализа, [хотя бы Clang\-Tidy](https://github.com/python/cpython/pull/129844#discussion_r1987010029), и сразу же следует ответ, что "сложно настроить линтер, который будет игнорировать существующий код и работать только с изменённым"\. PVS\-Studio не линтер, но умеет проводить [анализ модифицированных файлов, коммитов и запросов на слияние](https://pvs-studio.ru/ru/docs/manual/0055/)\. А если у вас есть свой проект с открытым исходным кодом, то вы можете получить [бесплатную лицензию на год](https://pvs-studio.ru/ru/order/open-source-license/) с возможностью продления в будущем\.



Поговаривают, что Python — это про чистый код, поэтому всем коллективом пожелаем именно его Python Software Foundation и участникам разработки от сообщества\!