Построение управляющего графа программ, содержащих неявный поток управления

ПОСТРОЕНИЕ УПРАВЛЯЮЩЕГО ГРАФА ПРОГРАММЫ, СОДЕРЖАЩЕЙ НЕЯВНЫЙ ПОТОК УПРАВЛЕНИЯ

А.М. Карпушинский Научный руководитель - к.т.н., доцент Т.А. Павловская

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

Введение

Современные технологии производства программного обеспечения предусматривают повсеместное использование объектно-ориентированных языков программирования (C++, C#, Java). Наряду с большими преимуществами таких языков, некоторые их возможности сопряжены с определенными трудностями, возникающими у разработчиков. Так, основная проблема, связанная с использованием полиморфизма и механизма обработки исключений, состоит в том, что они вводят (implicit control flow) - поток управления, не очевидный в исходном тексте [1, 2]. Из-за сложности, привносимой неявным потоком управления в объектно-ориентированный код, разработчики могут упустить важные взаимодействия в программах. Например, в процессе продвижения исключения из вызванного метода до вызывающего через несколько промежуточных методов, цепочка вызовов может создать непреднамеренные зависимости данных в программе. В результате, если не учитывать наличие неявного потока управления, эффективность разработки и сопровождения программного кода может снизиться.

Привнесение обработки исключений в исходный код также увеличивает трудоемкость процесса тестирования. Структурное тестирование проводится по принципу «белого ящика», т.е. основано на знании внутренней структуры программы, и характеризуется степенью, в которой тесты покрывают логику программы. При структурном тестировании программы необходимо определить критерий тестирования. Затем представить исходный код в виде некоторой структуры. Логику программы принято представлять в виде графовой модели - управляющего графа (УГ). Следующим этапом структурного тестирования является выделение путей в графе согласно выбранному критерию с целью обеспечения полного покрытия выделенных элементов (операторов, ветвей, путей).

Теоретическая часть

Рассмотрим вкратце механизм обработки исключений в языке Java. Для обработки исключений используются конструкции try, состоящие из обязательного блока try и необязательных блоков catch и finally. Блок try содержит исполняемый код, в котором могут выбрасываться исключения с помощью оператора throw, за ним следует один или несколько блоков catch или блок finally, либо и блоки catch, и finally. Блок try выполняется либо полностью, либо пока не выбросится исключение. Если в процессе выполнения команд блока try выбрасывается исключение определенного типа, управление передается в соответствующий этому типу блок catch, затем оператору, следующему за блоком try. Если для соответствующего исключения не было найдено ни одного блока catch, оно выбрасывается на блок выше и далее согласно стеку вызовов, пока не найдется соответствующий этому исключению обработчик. Если определен блок finally, он выполняется после блока try, с которым связан. При этом передача управления в блок finally осуществляется как в случае нормального завершения выполнения блока try (нормальный контекст), так и в случае возникновения исключения (исключительный

контекст). На рис. 1 представлены различные варианты передачи управления при обработке исключений.

Рис. 1. Передача управления при обработке исключений: в случае одиночной try-конструкции

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

Алгоритм построения УГ метода

Определение. Управляющий граф G метода M - это направленный граф (N, E, s, e), где N - множество вершин, E - множество дуг, s, e - начальная и конечная вершины. Вершины УГ соответствуют операторам либо блокам операторов, а дуги -всевозможным передачам управления между операторами (блоками).

С учетом конструкций механизма обработки исключений УГ должен содержать вершины, представляющие оператор throw и блоки catch и finally, а также дуги, представляющие нормальную и исключительную передачу управления. В таком УГ вершина throw может иметь несколько вершин-детей. Количество вершин определяется числом типов исключений, которые могут быть выброшены соответствующим оператором throw. Для определения всех возможных типов исключений используется метод выведения типов и каждому типу сопоставляется одна выходящая из throw-вершины дуга. В УГ вершина, представляющая блок catch, который обрабатывает явно выброшенные исключения, имеет входящие дуги для этих исключений. Однако вершина, представляющая блок catch, который обрабатывает только неявно

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

Рис. 2. Пример управляющего графа метода

Алгоритм построения УГ для метода M предназначен для построения УГ из абстрактного синтаксического дерева (АСД) этого метода и сбора информации о методе M, необходимой для построения межпроцедурного представления программы, содержащей M. Входными данными для алгоритма является АСД метода, выходными - УГ метода. В описании алгоритма не приводится этап построения вершин для общих конструкций языка (ветвления, циклы, и т.п.).

В УГ программы, содержащей обработку исключений, будем соединять вершину throw с каждым обработчиком catch, который может обработать выброшенное исключение. Чтобы определить условия перехода к конкретному обработчику, необходимо:

1) определить все возможные типы исключений, которые могут быть выброшены при выполнении каждого оператора;

2) так как исключение может быть обработано вне метода, в котором оно было выброшено, вершина throw может не иметь выходящих дуг; такие дуги появляются, когда УГ для всех методов соединяются в один межпроцедурный УГ.

Алгоритм построения УГ для метода предусматривает создание специальных вершин для каждого оператора try, throw и каждого блока catch. Для каждого блока finally, встречающегося в методе, строится отдельный УГ со своими входом и выходом, а также создаются вершины-вызовы этого блока в УГ метода. Каждая вершина throw соединяется с вершиной catch, если типы выбрасываемого и обрабатываемого исключений являются подходящими [1].

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

Для каждого блока finally строится отдельный УГ, и УГ метода дополняется вершинами - вызовами для обоих контекстов выполнения (см. рис. 2).

Алгоритм состоит в рекурсивном обходе вершин АСД. Действия, выполняемые алгоритмом, основаны на типе очередной вершины АСД. Обобщенная блок-схема алгоритма представлена на рис. 3.

Рис. 3. Общая схема алгоритма построения УГ метода Алгоритм построения межпроцедурного УГ

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

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

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

Алгоритм построения межпроцедурного УГ состоит из следующих действий.

1. Создать список всех методов программы.

2. Для каждого метода N найти все методы, вызывающие этот метод. Для каждого такого метода М выполнить п.п. 3, 4, 5, 6.

3. Создать дуги вызова N возврата и исключительного возврата из N.

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

5. Создать вершины и дуги исключительного выхода.

6. Создать дуги безусловного перехода из йпаПу-блоков.

Алгоритм состоит в итеративной обработке всех методов в программе с целью определения типов исключений, неявно продвигаемых каждым методом. В процессе определения таких типов для метода М, происходит добавление вершин исключительного вызова, возврата и выхода в УГ метода М, соединение всех УГ, используя четыре перечисленных выше типа межпроцедурных дуг.

Алгоритм выведения типов исключений, выбрасываемых оператором

Правильность приведенных алгоритмов построения УГ зависит от точности определения типов исключений, которые могут быть выброшены в различных точках программы. В приведенной выше графовой модели каждая вершина throw имеет столько выходящих дуг, сколько типов исключений может быть выброшено соответствующим оператором throw. Для определения типов исключений используется алгоритм выведения типов, основанный на подходах, описанных в [3, 4].

В ходе построения УГ, если текущий узел АСД оказался throw-оператором, происходит определение типов исключений, выбрасываемых этим оператором с помощью алгоритма ОПРЕДЕЛИТЬ_ТИПЫ, блок-схема которого изображена на рис. 4.

Рис. 4. Общая схема алгоритма выведения типов исключений

Этот алгоритм предполагает обратный анализ потока данных, начиная с вершины throw УГ.

ОПРЕДЕЛИТЬ_ТИПЫ (THROW, УГ).

THROW - узел throw, для которого определяются типы, УГ - неполный УГ.

Шаг 1. Если выражение, представленное в throw, является вызовом конструктора, вернуть тип исключения, соответствующего этому конструктору. Если выражение является вызовом метода, вернуть тип возвращаемого методом исключения и все его подтипы. Иначе в throw указана переменная, перейти к шагу 2.

Шаг 2. Обратный анализ потока данных, начиная с текущей вершины throw. Рассматриваются все родители текущей вершины. Для каждой такой вершины: если это вершина catch, перейти к шагу 3, если это вершина присваивания, перейти к шагу 4, если это вершина-вход УГ, перейти к шагу 5. Иначе перейти к шагу 2 для этой вершины.

Шаг 3. Добавить к возвращаемым типам тип, заданный в обработчике, и все его подтипы, перейти к шагу 2 для следующего родителя.

Шаг 4. Если выражение в правой части присваивания является вызовом конструктора, вернуть тип исключения, соответствующего этому конструктору. Если выражение является вызовом метода, вернуть тип возвращаемого методом исключения и все его подтипы. Иначе в throw указана переменная, перейти к шагу 2 для текущей вершины.

Шаг 5. Вернуть тип переменной и все его подтипы.

Реализация

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

Заключение

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

Литература

1. Sinha S., Orso A., Harrold M.J. Automated support for development, maintenance, and testing in the presence of implicit control flow. / Proceedings of the 26th International Conference on Software Engineering (ICSE'04), 2004.

2. Shujuan Jiang, Yuanpeng Jiang. An analysis approach for testing exception handling programs. // SIGPLAN Notices. «ACM» - NY, USA. - 2007. - Vol. 42.

3. Palsberg J., Schwartzbach M. Object-oriented type inference / Proceedings of Object-Oriented Programming Systems, Languages and Applications. - 1991. - P. 146-161.

4. Plevyak J., Chien A. Precise concrete type inference for object-oriented languages. // Proceedings of Object-Oriented Programming Systems, Languages and Applications. - 1994. - P. 324-340.