Анализ методов выявления заимствований программного кода

Анализ методов выявления заимствований программного кода

Волкова Г. А.

МИЭМ. Факультет информатики и телекоммуникаций. Кафедра ИТАС.

Оценка качества освоения образовательных программ учащимся является важнейшим элементом образовательного процесса [1]. В программу оценивания знаний учащегося входит проведение проверочных и аттестационных работ, к которым относятся лабораторные и самостоятельные работы, контрольные и проверочные работы, курсовые работы. В технических ВУЗах многие курсовые и лабораторные работы студентов состоят из описательной и программной части. Обычно, результаты работ представляются в электронном виде, что позволяет использовать чужие разработки программной части как частично, так и полностью. Данный факт существенно мешает производить объективную оценку знаний студента.

Для обнаружения фактов списывания студентами друг у друга в настоящее время разработано некоторое количество программ (таких как WordCHECK, WCopyFind, другие). Данные приложения позволяют сравнить две работы между собой, или работу с внутренним архивом, что исключает возможность копирования работ студентов прошлых лет [2]. Существуют так же Интернет-сервисы, занимающихся поиском плагиата в студенческих рефератах и курсовых работах. К таковым относятся: antiplagiat.ru, 2balla.ru, plagiarism.org.

Один из самых надежных способов проверки сданного материала на плагиат -поиск отдельных отрывков из него с помощью поисковых систем (www.google.ru, www.yandex.ru и др. Некоторые системы используют в своей работе результаты поисковых машин. Так, сервис Coopyscape для поиска в Интернет использует поисковую машину Google. Для этого он обращается по протоколу SOAP к Web-сервису поисковой машины Google Web API. Для поиска в Интернете документов, потенциально похожих на исходный текстовый документ, система "Детектор плагиата" использует ресурсы поисковой системы Яндекс. Таким образом, поиск

осуществляется по всем документам, проиндексированным поисковой машиной.

- 10 -

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

1. Переименование функций, процедур, переменных

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

2. Изменение визуального интерфейса программы

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

3. Использование директив препроцессора

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

4. Изменение существующих или добавление новых комментариев

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

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

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

Сервис «Поиск кода», разработанный компанией Google [3], помогает найти описания функций и образцы кода, предоставляя возможность поиска кода в общедоступных источниках, размещенных в Интернете. Сервис обладает возможностью поиска программного кода, написанного более чем на 70 языках программирования. Но данная система не в состоянии решить проблему заимствования программного кода между студентами, так как поиск ведется только в сети Интернет, и нет возможности сравнивать работы локально в пределах существующего архива работ. С точки зрения образовательного процесса актуально создание приложений, работающих с внутренним архивом программной системы, позволяющих легко установить заимствование программного кода.

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

Ярким примером индивидуального стиля программирования является стиль оформления кода. Программист может придерживаться т.н. стандарта кодирования (англ. coding standards, coding convention или programming style) — набора правил и соглашений, используемых при написании исходного кода на некотором языке программирования, или же в процессе работы выработать индивидуальный стиль, в зависимости от личных предпочтений.

Индивидуальный стиль оформления программного кода могут характеризовать:

1) Способы выбора названий и используемый регистр символов для имён переменных и других идентификаторов:

а) Запись типа переменной в её идентификаторе

Применяемая система префиксов зависит от многих факторов: языка программирования; стиля программирования (объектно-ориентированный код может не требовать префиксов); предметной области (например, префиксы могут применяться для записи единиц измерения); доступных средств автоматизации (генератор документации, навигация по коду, и т.д.).

b) Регистр символов

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

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

2) Способ расстановки скобок, ограничивающих логические блоки

В C/C++ любая обособленная часть кода должна выделяться фигурными скобками ( { и } ); в Паскале эту функцию выполняют операторы begin и end соответственно. Существует несколько способов выделение логически-обособленных фрагментов. Так, стиль «K&R», названный в честь авторов книги "The C Progamming Language”, был впервые использован Кернинганом и Ричи. Отличительной его особенностью является постановка открывающей скобки { непосредственно после условного оператора или же описания функции. Такой способ позволяет сэкономить вертикальное пространство при написании программного кода. Стиль Олмана имеет сходство с Паскалем и Алголом. В отличии от стиля «K&R», здесь открывающая скобка переносится на следующую строку на том же уровне, что и оператор (заголовок функции). Так же, возможно использование отступов для скобок, или т.н. стиль Уайтсмита.

3) Стиль отступов при оформлении логических блоков;

В языках программирования отступы используются для форматирования исходного кода программы для улучшения читабельности. Отметим, что некоторые языки программирования (Haskell, Occam, Python) используют отступы для синтаксического выделения блоков кода вместо операторных скобок. Но, как правило, использование отступов является эстетическим предпочтением программиста, так как не обрабатывается большинством интерпретаторов и компиляторов.

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

4) Использование пробелов при оформлении логических и арифметических выражений;

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

5) Стиль комментариев и использование документирующих комментариев

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

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

оформления комментариев и в случае, когда комментарии не будут использованы генераторами документации.

Комментирование программного кода не является обязательным требованием, поэтому, зачастую, факт наличия комментариев и их содержание, является характерной чертой индивидуального стиля программирования. Заметим, что в случае анализа студенческих работ, наличие комментариев может судить о самостоятельном написании программы так как, в отсутствии обширного опыта программирования, большинство студентов не комментируют свой программный код.

При рассмотрении стиля программирования часто используется термин "родимые пятна" (англ. birthmarks), введенный в употребление в Британском обществе вычислительной техники (англ. British Computer Society) для обозначения особенностей, которые присущи стадии разработки программы. В качестве примеров таких особенностей можно назвать, части кода из более ранних версий программы, фрагменты, написанные при разработке программы, но не удаленные после того, как они стали ненужными [4]. В качестве родимых пятен также рассматриваются ошибки. Воспроизведение ошибки может служить доказательством заимствования, поскольку маловероятно, чтобы один человек самостоятельно и независимо повторил ту же ошибку, что и другой

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

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

У многих программистов существуют наработанные наборы заголовочных файлов. Зачастую, списки заголовочных файлов копируются из предыдущих проектов

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

Характеристические особенности стиля программирования, описанные выше, являются очень субъективными, поэтому не могут быть использованы для автоматического анализа заимствований программного кода. Для сравнения текстов программными средствами используют методы формального распознавания (англ. academic program plagiarism). Алгоритмы могут быть основаны на двухметодах -комплексном анализе характеристик исходного кода программы и представлении программного кода в удобном для автоматического сравнения виде (например, в виде множеств, строк, графов и т.п., в зависимости от выбранного алгоритма сравнения).

Алгоритм анализа характеристик программы использует т.н. метрики, т.е. количество фрагментов кода, схожих в соответствии с определенным критерием. Это могут быть количество циклов или условных операторов, число используемых переменных. Наиболее приемлемой характеристикой является последовательность операторов программы, поскольку модификация этой последовательности требует понимания логики функционирования программы.

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

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

Пики на подобных диаграммах автор [4] называет пиками взаимной корреляции двух программ, и отмечает, что может возникнуть заметная случайная корреляция,

поэтому каждый случай высокого значения корреляции следует анализировать другими методами.

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

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

Приведем классификацию форм представления исходной информации в зависимости от используемых алгоритмов сравнения:

1. Строки

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

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

2. Лексемы

Анализ лексем аналогичен анализу, основанному на строках, но использует

лексический анализатор (англ. lexical analyzer) для преобразования исходного кода программы в последовательности лексем.

Лексический анализ — процесс аналитического разбора входной

последовательности символов (например, такой как исходный код на одном из языков

программирования) с целью получения на выходе последовательности символов,

- 17 -

называемых «токенами». При этом, группа символов входной последовательности, идентифицируемая на выходе процесса как токен, называется лексема, то есть в процессе лексического анализа производится распознавание и выделение лексем из входной последовательности символов.

Как правило, лексический анализ производится с точки зрения определённого языка или набора языков. Грамматика языка задаёт определённый набор лексем, которые могут встретиться на входе процесса.

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

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

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

3. Деревья синтаксического анализа

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

В информатике, синтаксический анализ означает процесс сопоставления линейной последовательности лексем (слов, токенов) языка с его формальной

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

Большинство известных методов анализа принадлежат одному из двух классов, один из которых объединяет нисходящие (англ. top-down) алгоритмы, а другой -восходящие (англ. bottom-up) алгоритмы. Происхождение этих терминов связано с тем, каким образом строятся узлы синтаксического дерева: либо от корня (аксиомы грамматики) к листьям (терминальным символам), либо от листьев к корню.

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

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

4. Графы зависимостей

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

является достаточно сложным логическим приемом, особенно это касается студенческих работ.

Построение графа вызовов функций позволяет выявить функции или процедуры, которые не вызываются ни одной подпрограммой. Для программ, написанных на языке СИ, это является важной характеристической особенностью исходного кода программы, так как не вызываемая другими функциями (функцией ша1п() в том числе) функция в языке Си является избыточной, что, как следствие, относится к числу «родимых пятен».

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

Статические графы строятся путем анализа непосредственно исходного кода программы, что позволяет зафиксировать все возможные взаимосвязи между подпрограммами программного модуля. Для построения возможно использование лексических и синтаксических анализаторов, или же алгоритмов, основанных на регулярных выражениях.

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

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

наличием «заглушек» (англ. stub, функций, возвращающих пустой результат или входные данные в неизменном виде), изменению порядка следования условных операторов и т.п.

Список литературы

1. Закон РФ "Об образовании" от 10.07.1992 N 3266-1

http://www.consultant.ru/popular/edu/43_1.html

2. Colin J. Neill, Ganesh Shanmuganthan. A Web-Enabled Plagiarism Detection Tool.

IEEE IT Pro, September/October 2004. перевод: Колин Нейл, Ганеш

Шанмагантан. Web-инструмент для выявления плагиата. Открытые системы № 1, 2005, с.40-44. http://www.osp.ru/os/2005/01/185200/_p1.html

3. Официальный сайт Интернет-сервиса «Поиск кода» компании Google. http://www.google.com/codesearch

4. С.Иванчегло. Методы выявления плагиата в программировании. Software, №49, 2000. http://www.kv.by/index2000491105.htm