morfizm (morfizm) wrote,
morfizm
morfizm

Tricky Python - II

Upd.: raindog указал на то, что мне следовало выложить больше контекста, так как в том виде, что есть это нельзя считать багом, а всего лишь плохим стилем. Пожалуй, соглашусь & приношу свои извинения.

Upd.2: я начинаю сомневаться, что raindog прав. Если выложить достаточно контекста, можно будет тупо мысленно выполнить по шагам и найти баг. Это совсем неинтересно делать, и, кроме того, эта ситуация очень далека от реальной - когда баг надо поймать на code review, и контекста мало. Нужно делать "разумные, естественные assumptions" и смотреть, будут ли они всегда выполняться в этом коде или же иногда нет. И вот это "иногда нет" - если оно явно не оговорено в комментарии или иным образом, это баг.

* * *

Follow-up отсюда http://morfizm.livejournal.com/471580.html

Баг состоял в том, что result = self.tags устанавливает результат на ссылку на поле tags у узла дерева, таким образом, последующее изменение result непреднамеренно портит дерево. (Фикс выглядел бы, например, так: result = set(self.tags) - это создало бы копию множества).

Причём баг особо нехороший, потому что проявляется он только при определённом стечении обстоятельств, до которого трудно догадаться во время тестирования, если не знать про этот класс багов и специально их не ловить:

1. Дерево должно быть таким, чтобы операторы result = result | ... ни разу не выполнились - т.к. бинарная операция | создаёт копию множества, которую можно впоследствии безопасно изменять.

2. result нужно таки поменять - например, вызывать обработку нескольких деревьев, объединяя результаты:

result = t1.evaluate_tree(item)
result |= t2.evaluate_tree(item) // here we will sometimes edit t1.tags via "result" name

(операция |= будет редактировать left-side множество in-place, не создавая промежуточного объекта)

3. Вышеописанный код может испортить дерево t1, но баг проявится только когда этот код (или другой, использующий t1) будет вызван повторно.

archaicos после многочисленных подсказок нашёл правильный ответ, причём он погуглил "python pitfalls", нашёл вот эту замечательную страничку:
http://zephyrfalcon.org/labs/python_pitfalls.html
и прошёлся по пунктам :) Второй пункт - "Assignment, aka names and objects" - как раз про это.

_m_e_ шёл правильным путём, по сути, проверив первый пункт упомянутой странички, но не добравшись до второго.

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

Вообще, это очень интересный класс багов - когда не понятно, можешь ли ты безопасно пользоваться возвращаемым значением (в данном случае, изменять его). На самом деле, похожий баг я видел C++, когда использовался XML-парсер FAXPP. Для эффективности он возвращает строку в виде указателя на свой внутренний буфер. Баг состоял в том, что эту строку не скопировали, а просто запомнили указатель, потом вызвали фукнцию, которая где-то в глубине опять вызывала парсер, менявший буфер, потом вызываемая функция пыталась использовать запомненную строку, рассчитывая, что буфер не изменился. Было вроде такого:
FAXPP_TEXT* mytoken = faxpp_get_string()
call_something() // something will call faxpp_get_string() again, destroying the buffer
... // try using mytoken, which has wrong value.
Tags: fun, in english, polls questions and social games, software development, work
Subscribe
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 21 comments