научная статья по теме ИССЛЕДОВАНИЕ ДЕФЕКТОВ В КОДЕ ПРОГРАММ НА ЯЗЫКЕ PYTHON Математика

Текст научной статьи на тему «ИССЛЕДОВАНИЕ ДЕФЕКТОВ В КОДЕ ПРОГРАММ НА ЯЗЫКЕ PYTHON»

ПРОГРАММИРОВАНИЕ, 2013, No 6, с. 25-32

ЯЗЫКИ И СИСТЕМЫ ПРОГРАММИРОВАНИЯ

У л :

ИССЛЕДОВАНИЕ ДЕФЕКТОВ В КОДЕ ПРОГРАММ НА

ЯЗЫКЕ PYTHON

© 2013 г. И.Е. Бронштейн

Институт системного программирования РАН 109004 Москва, ул. А. Солженицына, 25 E-mail: ibron.stein@ispras. ru Поступила в редакцию 29.04.2013

В статье рассматриваются виды дефектов, которые обычно встречаются в программном коде на языке Python. Показывается, что возможные дефекты для Python не похожи на те, что часто встречаются в коде на Си/Си++ и, следовательно, необходимо исследование дефектов в крупных проектах с открытым исходным кодом. Даётся классификация найденных дефектов на основе того, нужен ли для нахождения ошибки вывод типов. Показывается, что существует небольшая доля "простых" дефектов, но для обнаружения большинства дефектов вывод типов необходим. Рассматривается вопрос, какие конструкции языка Python должны поддерживаться при выводе типов для нахождения реальных дефектов.

1. ВВЕДЕНИЕ

Многообразие существующих языков программирования можно классифицировать по тому, в какой момент в них производится контроль типов. В том или ином языке могут использоваться статическая типизация, динамическая типизация либо некоторая комбинация первых двух вариантов. Говорят, что в языке программирования применяется статическая типизация, если проверка типов в нём осуществляется во время компиляции программы. Тип конкретного выражения или переменной строго фиксирован: он либо явно задаётся программистом, либо выводится компилятором на основе известного набора правил. Примеры статически типизированных языков: Си, Си++, Java. Напротив, при динамической типизации контроль типов производится лишь в момент выполнения программы. В этом случае тип имеется у тех или иных значений, однако у переменных тип как таковой отсутствует. В процессе работы программы одной и той же переменной могут быть присвоены значения различных типов. Динамически типизированными являются, например, языки JavaScript, Python и Ruby.

В настоящее время всё большую популярность приобретают именно языки, в которых присутствует динамическая типизация. Согласно авторитетному рейтингу от компании ТЮВЕ Software [1], в десятку самых популярных языков программирования входят Objective-C (смешанная типизация) и динамически типизированные PHP, Python, Perl и Ruby (JavaScript — 11-й). Python стал использоваться не только для написания небольших скриптов, но и для создания крупных программных систем (например, веб-фреймворка Django, состоящего из сотен тысяч строк программного кода). Аналогичным образом сфера применения JavaScript — не только простые сценарии, выполняющиеся в веб-браузере, но и прикладное программное обеспечение (около 15 % исходного кода Mozilla Firefox, а также предполагаемые мобильные приложения в Firefox OS).

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

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

облегчает статический анализ программ на таких языках. Для Си/Си++ существует множество инструментов, использующих информацию о типах для поиска нетривиальных дефектов — ошибок и уязвимостей — в программном коде. Среди таких средств можно отметить статические анализаторы Svace [2], Coveritv Prevent [3], Klocwork Insight [4] и PVS-Studio [5].

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

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

2. РАСПРЕДЕЛЕННЫЕ ВИДЫ ДЕФЕКТОВ В СИ-ПРОГРАММАХ И ЯЗЫК PYTHON

Виды дефектов, которые встречаются в программном коде на статически типизированных языках, известны. Для Си/Си++ они описаны, например, в документации к вышеупомянутым статическим анализаторам. Перечислим некоторые виды распространённых в программах на Си/Си++ дефектов:

• разыменование нулевого указателя; ванной переменной; вание освобождённой памяти).

Теперь рассмотрим каждый из перечисленных пунктов по отдельности применительно к языку Python.

Разыменование нулевого указателя. Указателей в языке Python нет. Аналогом нулевого указателя может служить значение None. Однако None — это обычный объект некоторого класса, и не любое обращение, скажем, к атрибутам этого объекта является ошибкой. Например,

х = None.__class__— это вполне правильное с

точки зрения языка Python присваивание. Поэтому говорить о разыменовании нулевого указателя как таковом не приходится.

Выход за границы массива. В Python используются не массивы, а списки элементов. Обращение к спискам обычно осуществляется с помощью цикла с итератором (for ... in ...), который не допускает выхода за пределы списка. Прямой аналогии с выходом за границы массива в Си/Си++ (с использованием неправильного индекса в массиве) здесь нет.

Использование значения неинициализированной переменной. Этот дефект в Си/Си++ возможен в нескольких случаях: например, при декларации переменной без одновременной инициализации или при отсутствии инициализации атрибута объекта в конструкторе. В языке Python переменные не декларируются: при присваивании переменной определённого значения имя переменной связывается с этим значением (и теряет связь с предшествующим значением, если таковое было). Поэтому при выполнении программы возможны ситуации, когда идёт обращение к несуществующей переменной или к отсутствующему атрибуту объекта. Однако это нельзя назвать аналогом использования неинициализированного значения тем более, что в таких случаях в отличие от Си/Си++ возбуждается исключение.

Утечка памяти и использование освобождённой памяти. В Python реализована сборка мусора, поэтому проблем с освобождением памяти быть не должно.

Как мы видим, в коде на языке Python не встречаются дефекты, аналогичные тем, что присутствуют в коде на Си/Си++. Чтобы определить, какие виды дефектов специфичны для языка Python, необходимо исследовать их

опытным путём, например, при помощи анализа ошибок, которые были ранее обнаружены в реальных программах.

3. ОБЗОР ДЕФЕКТОВ ИЗ РЕАЛЬНЫХ ПРОГРАММ

Источником сведений о дефектах, встречающихся в программах на языке Python, могут служить системы отслеживания ошибок (bug trackers) проектов с открытым исходным кодом. В качестве таких проектов были выбраны крупные системы с сотнями тысяч строк кода: Django, Gramps и Twisted. Были проанализированы более 150 отчётов.

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

Всё остальное, т. е. найденные на реальных проектах дефекты, можно разделить на две основные категории:

можно обнаружить без вывода типов;

торые можно, лишь используя информацию, полученную при выводе типов.

3.1. "Простые" дефекты

Перечислим виды найденных "простых" дефектов:

Дублирование ключа в **...-аргументах. Дефект специфичен для языка Python и заключается в том, что при создании словаря kwargsl для передачи в функцию А.__init__именной аргумент param дублируется (и указывается явно, и уже присутствует в kwargs2 при конструировании объекта Ь2):

class A(object):

def __init__(self,

param, **kwargsl):

pass

class В(A):

def __init__(self, **kwargs2):

super(B, self). __init__(param=l,

**kwargs2)

M = B(foo=2)

Ь2 = В(foo=2, param=3)

Здесь приведены два вызова конструктора класса В, первый из которых не приводит к ошибке, а второй приводит. Однако для нахождения этого дефекта анализировать цепочку вызовов конструкторов и выводить какие-либо типы необязательно: достаточно обойти вызов конструктора А и проверить, не используется ли в вызове и именной, и **. . .-аргумент.

Непредусмотренная распаковка строки. Это довольно необычный дефект, вызванный тем, как приведённый ниже код обрабатывает опции item — элементы списка opts:

for item in opts: try:

field, aux = item except ValueError:

field = item pieces = field.split('__')

Предполагается, что item будет либо кортежем/списком из двух элементов, первый из которых — строка, либо просто строкой. В любом случае на выходе ожидается строка field, которая будет передана в функцию split ("разбиение" строки по заданному разделителю). Проблема в том, что вместо явной проверки на список/кортеж производится попытка распаковать item на два элемента, и, если это не получилось, item считается строкой. Однако, строка в языке Python — это список символов. Таким образом, если в качестве item передать строчку из двух элементов, распаковка пройдёт успешно. Это вряд ли то, чего ожидал программист. Для обнаружения приведённого дефекта не нужен вывод типов. Можно написать чекер, пусть

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

Оператор return в конструкторе. В языке Python конструкторы не должны ничего возвращать или, что то же самое, должны возвращ

Для дальнейшего прочтения статьи необходимо приобрести полный текст. Статьи высылаются в формате PDF на указанную при оплате почту. Время доставки составляет менее 10 минут. Стоимость одной статьи — 150 рублей.

Показать целиком