TGViewer
Блог* Блог* @dereference_pointer_there · 1.98K subscribers
Post #11253 832

Forwarded from Находки в опенсорсе

Если вы понимаете данный баг, то вы знаете питон лучше 95% людей

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

Вот код, который делает две критичные безумные вещи (попробуйте их найти прежде, чем читать дальше):


class Evil:
def __eq__(self, other):
return other

leaked = vars(list) == Evil()
name = "example"
leaked[name] = lambda self: "probe"
print(getattr(list, name)([]))
del leaked[name]
print(hasattr(list, name))


Разбор бага

Во-первых, что произойдет?
1. Мы мутируем встроенный и иммутабельный тип list, хотя такое должно быть невозможно
2. Интерпретатор закрашится; не упадет с исключением, а словит core dump на уровне C кода

Но почему? Пройдемся по каждой строке. Со ссылками на исходники: кликайте и читайте!

1. Сначала мы создадим класс Evil, который просто возвращает из __eq__ второй объект, который ему передали. Так можно делать, тут нет ничего сломанного.
2. Далее, мы сравниваем vars(list) с Evil, и вот тут как раз в leaked попадет второй объект из Evil.__eq__, в нашем случае vars(list)
3. vars возвращает вам list.__dict__, который является не обычным dict, а types.MappingProxyType, то есть иммутабельным маппингом поверх оригинального значения. Добавлять в него ключи нельзя. Потому что мы не хотим, чтобы в список или другие типы нам подкидывали какие-то новые методы во время работы программы
4. Как работает сравнение для mappingproxy? mappingproxy хранит в себе оригинальный мутабельный словарь, который он "проксирует" или "защищает от изменений". И сравнивает на самом деле не себя, а оригинальный объект
5. В случае с list.__dict__ мы получаем PyDictProxy_New(self->tp_dict), где хранится тот самый настоящий и защищенный __dict__ из типа list, который обычно не доступен вне C кода
6. При сравнении mappingproxy разворачивается и достает из себя ->mapping, тот самый чистый и мутабельный ->tp_dict
7. Теперь у нас есть ->tp_dict, мы можем в него добавлять методы: leaked[name] = lambda self: "probe". Они будут работать. Мы только что достигли пункта 1. и мутировали встроенный Python тип без единого импорта
8. Далее происходит еще более дикое. Мы удаляем метод, который добавили через del leaked[name]
9. Питон не ожидает такого: методы у встроенных типов не могут появляться и исчезать. И при следующем обращении к hasattr(list, name) крашится вот тут на обращении к уже освобожденной памяти. EXC_BAD_ACCESS, пункт 2. пал

пу-пу-пу

Фикс

Баг: https://github.com/python/cpython/issues/152405

Как такое чинить?
1. Нужно сохранить обратную совместимость для всех видов сравнений. Менять типы или значения нельзя
2. Необходимо убрать креш и мутацию типа
3. Сильно раздувать потребление памяти / время работы тоже нельзя

Мой PR: https://github.com/python/cpython/pull/152483

Что он делает? Если мы сравниваем прокси поверх обычного словаря, но не с известными нам безопасными типами, то мы делаем копию словаря и сравниваем ее:


if (
PyDict_CheckExact(v->mapping) &&
!(PyAnyDict_CheckExact(w) || PyODict_CheckExact(w))
) {
// So, instead we send a copy:
PyObject *copy = PyDict_Copy(v->mapping);
if (copy == NULL) {
return NULL;
}
PyObject *res = PyObject_RichCompare(copy, w, op);
Py_DECREF(copy);
return res;
}


Таким образом - все ошибки выше уходят. Любая мутация останется в копии. Доп память не тратится в большом количестве популярных случаев.
Данный баг все еще есть на всех версиях питона. Я вам его не показывал, вы ничего не видели.

Обсуждение: какие у вас были самые кринжовые / прикольные баги?

| Поддержать | YouTube | GitHub | Чат |
GitHub cpython/Python/bltinmodule.c at f6e904e1a666cb1e5664750b1c3d8f89cba3a769 · python/cpython The Python programming language. Contribute to python/cpython development by creating an account on GitHub.
  • 🔥 10
  • 🤯 6
  • ❤ 2
  • 💩 2
  • 👍 1
More from @dereference_pointer_there
  1. Sep 27, 2026photo post
  2. Sep 27, 2026#meme про прожиг дисков
  3. Sep 27, 2026Если вы считаете себя бесстрашным, попробуйте познакомиться с женщиной.
  4. Sep 27, 2026Почему я не работаю на себя? Начальник кретин
  5. Sep 26, 2026Эм...
  6. Sep 26, 2026#politota #meme, видимо
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →