|
(06.10.2006)
|
 |
|
(05.09.2006)
|
 |
|
(10.08.2006)
|
 |
|
(17.05.2006)
|
 |
|
(01.02.2006)
|
 |
|
(29.12.2005)
|
 |
|
(27.10.2005)
|
 |
|
(19.08.2005)
|
 |

|
|
 |
Главная
Главная
Borland
Cтатический анализатор ошибок в java-программах
|
|
Java Tools
Значительная часть усилий при создании программ тратится на обнаружение, локализацию и устранение различного рода дефектов. Для этого используются как динамические (тестирование, отладка), так и статические (ручной и автоматический просмотр кода, доказательство правильности) методы.
|
Простейшие формы статического анализа осуществляются компиляторами. Обычно набор обнаруживаемых ими ошибок ограничен нарушениями синтаксиса и правил совместимости типов. На другом конце спектра систем статического анализа находятся средства автоматического доказательства правильности программ. Эти средства требуют детальной формальной спецификации свойств программы, которые надо доказать. Усилия программиста по составлению спецификаций очень велики и часто превосходят усилия по разработке собственно программы, а также требуют основательной специальной подготовки. Еще одним недостатком таких систем является требовательность к ресурсам.
Описываемый здесь статический анализатор Sapient, разработанный авторами для компании Borland Software Corporation, ориентирован на обнаружение за разумное время (сравнимое с компиляцией) максимального числа “существенных” ошибок в программах на языке Java. Sapient способен обрабатывать проекты объемом более миллиона строк исходного текста и не требует от программиста использования дополнительных спецификаций.
Для обеспечения легкой расширяемости и конфигурируемости в Sapient использована модульная архитектура, позволяющая подключать новые компоненты без перекомпиляции существующих.
Sapient состоит из ядра и набора загружаемых модулей. Ядро предоставляет общий интерфейс для доступа к исходным кодам, содержит анализаторы потока данных и управления, а также механизм загрузки и конфигурации модулей.
Каждый модуль в Sapient описывается при помощи XML дескриптора. Дескриптор определяет вид модуля и позволяет определять параметры (специфичные для каждого конкретного модуля), которые могут быть изменены пользователем. Поддерживается три различных вида модулей: синтаксические анализаторы, инспекторы, и библиотеки общего назначения.
Сценарий анализа текста программы выглядит следующим образом. Ядру Sapient передается список исходных файлов и список инспекторов, которые должны протестировать анализируемые файлы. Ядро загружает требуемые компоненты, строит абстрактное синтаксическое дерево (АСТ) для исходных файлов, и передает его загруженным инспекторам. Анализ потоков данных и управления также производится ядром на основе АСТ по запросу того или иного инспектора.
Инспекторы, включенные в Sapient, выполняют три различные задачи. По этому признаку они могут быть разделены на:
-
аудиты, обнаруживающие ошибки и подозрительные места в программном коде,
-
метрики, численно выражающие те или иные характеристики программного кода,
-
генераторы отчетов, подсчитывающие статистическую информацию и строящие визуальное представление результатов, полученных от аудитов и метрик.
Рассмотрим аудиты подробнее. Можно выделить следующие группы аудитов, разделив их по виду обнаруживаемых ошибок и используемого для этого анализа:
-
Несоответствие стилю и ошибки кодирования, такие как присвоение значения параметру метода или модификация одной и той же переменной в нескольких местах одного выражения. Такие ошибки обнаруживаются при помощи анализа на уровне АСТ.
-
Критические ошибки, вызывающие исключения во время выполнения программы. В эту группу входят доступ по null-ссылке, выход индекса массива за допустимые пределы, переполнение при арифметических операциях. Эти ошибки могут быть обнаружены при помощи анализа потока данных.
-
Аномалии потока управления программы. Например, бесконечная рекурсия, оператор if, условие которого всегда истинно или ложно, ветка case, метка которой никогда не может быть выбрана.
-
Недостатки низкоуровневого дизайна, такие как неиспользуемый член класса, поле, используемое как локальная переменная, поля или методы, которые лучше перенести в другой класс.
-
Недостатки высокоуровневого дизайна. Аудиты Sapient обнаруживают такие проблемы, как ошибочное использование наследования или отсутствие шаблона проектирования Singleton.
Остановимся подробнее на некоторых интересных аудитах.
Контейнеры. Java не поддерживает параметризованные классы. Поэтому при необходимости работы с коллекцией объектов, программисту приходится приводить извлекаемый из контейнера объект к нужному типу. Так как у компилятора нет информации о типе элементов контейнера, он не способен проверить корректность этих преобразований. Например, если в контейнер помещается объект типа A, а после извлечения он приводится к типу В, то компилятор не выдаст предупреждения.
Sapient пытается отследить множество типов объектов, которые были помещены в контейнер. Если извлекаемый объект приводится к типу, несовместимому с этим множеством, это свидетельствует об ошибке в коде.
Этот же подход обобщается для проверки использования массивов и итераторов.
Доступ по нулевой ссылке. Без выполнения программы, в общем случае, понять какое значение в данный момент содержит переменная нельзя. Но существует несколько полезных частных случаев. Например, если переменная сравнивается с null, можно сделать сразу три заключения:
-
Переменная может иметь значение null в этом месте.
-
В ветке условного оператора проверяющего, что переменная не равна null, точно известно, что её значение не равно null.
-
Наоборот, в else ветке можно точно сказать, что переменная равна null.
Используя анализ потока данных, можно проследить, куда могут попадать нулевые и возможно-нулевые значения. Если методу в качестве одного из параметров передаётся null, и метод без проверки использует его для доступа к компоненте класса, то это свидетельствует об ошибкеИспользуя анализ потока данных, можно проследить, куда могут попадать нулевые и возможно-нулевые значения. Если методу в качестве одного из параметров передаётся null, и метод без проверки использует его для доступа к компоненте класса, то это свидетельствует об ошибке.
Преобразования и проверки типов. Компилятор Java проводит проверку корректности выражений, основываясь на декларированных типах операндов. Это приводит игнорированию некоторых типов ошибок. Например, следующий код допускается любым компилятором Java:
interface I1 {}
class C1 implements I1 {}
class C2 implements I1 {}
I1 var = new C1();
((C2)var).foo(); // Ошибка, var имеет тип C1 |
Sapient использует анализ потока данных для определения типа выражения времени выполнения.
Та же самая информация используется и при анализе операторов проверки типа (instanceof). В отличие от преобразования типа, здесь во время выполнения не возникает исключительной ситуации , а значение условия будет постоянным.
Перечисления. В Java нет перечислимого типа (enumeration). Вместо этого программисты вынуждены определять каждое из перечислимых значений как отдельную константу:
public static final int RED=1;
public static final int GREEN=2;
public static final int BLUE=3; |
Помимо того, что это неудобно, уменьшается возможность автоматического контроля правильности использования перечислимых значений.
Sapient использует эвристический подход к определению перечислений. Несколько последовательно определённых целочисленных констант очень часто свидетельствуют о том, что на самом деле эти константы — части одного перечисления. Если они к тому же образуют арифметическую или геометрическую прогрессию, то это можно утверждать почти с полной уверенностью. После кого как эти константы отнесены к одному перечислимому типу, мы можем отследить все присвоения этих констант и, таким образом, установить перечислимые типы для переменных. Если при этом обнаруживаются конфликты (например, одной и той же переменной присваиваются константы из различных перечислений), это свидетельствует о возможной ошибке. Так же можно проверить, что оператор switch или содержит case для каждой константы из перечисления или определяет действие по умолчанию.
Синхронизация. Большую проблему представляет поиск ошибок синхронизации в многопоточных программах. Некоторые проблемы могут проявиться только в очень редких ситуациях, иногда после нескольких лет эксплуатации.
К сожалению, полностью решить задачу проверки корректности синхронизации в многопоточной программе не представляется возможным.
В Sapient используется упрощённая модель блокировки ресурсов – на уровне классов и переменных. В рамках этой модели выявляются возможные тупиковые ситуации, ситуации перехвата (race condition), противоречия в синхронизации при доступе к объектам, a также возможные исключительные ситуации и другие некорректности при использовании метода wait.
Позиционные параметры. Sapient обнаруживает некоторые ошибки, возникающие при вызове методов, используя следующие эмпирические правила:
-
Если один из формальных параметров имеет то же имя, что и переменная, переданная в качестве фактического значения другого параметра, то выдаётся сообщение о возможной ошибке в порядке аргументов.
-
Если переменные с одинаковым именем передаются при вызовах метода на разных позициях, то выдается предупреждение. Например, если метод foo(int width, int length) в большинстве случаев вызывается как foo(x, y), а в одном — foo(y, x), то, возможно, программист перепутал параметры.
Освобождение ресурсов при возникновении исключительной ситуации. Иногда приходится работать с ресурсами, которые необходимо закрыть по окончанию работы, независимо от того, как это произошло: нормальным образом или вследствие возникновения исключительной ситуации. В Java для этого предусмотрена конструкция finally. К сожалению, компилятор не проверяет, что во всех местах, где при работе с критическим ресурсом может возникнуть исключительная ситуация, используется finally.
Sapient не в состоянии отличить критический ресурс от некритического, но если в одном месте метод используется внутри finally блока, а в другом, где также возможно возникновение исключительной ситуации — нет, то будет выдано предупреждающее сообщение.
Ошибки переопределения метода. В языке Java все методы по умолчанию виртуальные — они допускают полиморфные вызовы. Метод выведенного класса переопределяет метод базового класса тогда и только тогда, когда их имена и сигнатуры совпадают. В случае опечатки в названии метода или неправильного указания типов параметров компилятор не выдаст предупреждающих сообщений.
Sapient пытается обнаружить ошибки обоих видов — он выдаёт сообщение если:
-
В выведенном классе содержится метод, чьё имя соответствует методу в базовом классе, но сигнатуры методов не совпадают.
-
В выведенном классе содержится метод, с сигнатурой, совпадающей с сигнатурой метода из базового класса, но его имя незначительно отличается от имени метода в базовом классе (степень различия – длина минимальной редактирующей последовательности — является настраиваемым параметром этого аудита).
Ошибочная последовательность действий при работе с классом. Для некоторых классов существуют правила порядка вызова их методов. Например, файл сначала необходимо открыть, затем с ним можно проводить операции чтения и записи, а потом его надо закрыть. Попытка записи в закрытый файл приведёт к возникновению исключительной ситуации во время выполнения.
Sapient позволяет определить конечный автомат для проверки допустимых переходов между состояниями объекта. Переходы между состояниями происходят при вызовах методов.
Подозрительные вычисления. Sapient производит оценку возможных множеств значений результатов операций. Для целочисленных типов отслеживается максимальное и минимальное значение выражения, маска установленных и сброшенных битов. Используя эту информацию Sapient, способен сделать, в частности, следующие выводы:
-
Результат операции — константа (пример: x >> 32).
-
Условие всегда принимает только одно значение (пример: (x & 31) < 32).
-
Возможный выход за границу массива при индексации.
-
Возможное деление на 0.
-
Побитовый сдвиг на число позиций превышающий размер типа в битах.
-
Возможную потерю точности при проведении операции или преобразовании типа.
Хотя некоторые из подобных сообщений будут выдаваться для вполне корректного кода (написанного, например, из-за отсутствия в Java условной компиляции), в большинстве случаев они свидетельствуют о действительно подозрительных местах в программе.
Дублирование кода. Сравнивая абстрактные синтаксические деревья, Sapient находит похожие участки кода и тем самым подсказывает возможности реструктуризации.
Sapient может использоваться из командной строки как самостоятельное приложение, однко наибольший эффект достигается при его интеграции в интерактивную среду разработки программ. В настоящее время Sapient может быть встроен в среды Eclipse (www.eclipse.org) и IBM WebSphere Studio в качестве самостоятельной компоненты или как часть пакета Borland Together Edition for WebSphere Studio.
Пользовательский интерфейс Sapient выполняет позиционирование на место ошибки в тексте программы и генерацию отчётов, предоставляет ряд дополнительных возможностей. Для части сообщений об ошибке предоставляется возможность автоматического внесения исправлений в исходный текст. Например, автоматически могут быть изменены атрибуты доступа компонент класса, осуществлен перенос полей и методов в другой класс, добавлены скобки в сложное арифметическое выражение.
Более подробную информацию о статическом анализаторе ошибок в java можно найти на сайте компании Borland.
Исследовательский центр фирмы Borland, Москва,
Московский Государственный Индустриальный Университет.
{Igor.Abramov | Konstantin.Knizhnik | Askar.Rahimberdiev}@borland.com
|
|
 |
|