КОДАХ ПРОГРАММ
О.А. Евтифеева*, А.Л. Красс, М.А. Лакунин*, Е.А. Лысенко*, Р.Р. Счастливцев*
* (Санкт-Петербургский государственный университет)
Научный руководитель – д.т.н., профессор А.А. Шалыто
Cтатья содержит краткий обзор существующих алгоритмов поиска плагиата в исходных кодах программ/
Рассмотрены их особенности, преимущества и недостатки, приведены результаты тестирования наиболее
популярных некоммерческих детекторов плагиата, использующих эти алгоритмы.
1. Введение
2. Алгоритмы
2.1. Классификация
Существующие в данной области алгоритмы можно классифицировать следую-
щим образом:
• текстовые алгоритмы,
• структурные алгоритмы,
• семантические алгоритмы.
Особенностью текстовых алгоритмов является то, что они представляют исходные
коды программ в виде текста, а точнее, строки над алфавитом, символ которого соот-
ветствует определенному оператору или группе операторов языка программирования.
Причем аргументы операторов (которые сами могут быть операторами) игнорируются,
что делает многие элементарные действия по сокрытию плагиата (например, изменения
имен переменным) бесполезными. Символ такого алфавита традиционно называют то-
кеном.
188
Текстовые алгоритмы включают в себя как наиболее эффективные современные
алгоритмы поиска плагиата в исходных кодах программ, так и самые старые алгорит-
мы. Примером последних может служить [1], где для проверки на плагиат производит-
ся подсчет некоторых характеристик программ (например, количество операторов
ветвления в программе), а затем полученные векторы характеристик сравниваются ме-
жду собой. Конечно, этот метод очень легко обойти, к тому же он находит плагиат во
всей программе и не может быть эффективно адаптирован для нахождения относитель-
но небольшого фрагмента плагиата в большом исходном коде.
Практически во всех алгоритмах текстовой группы считается, что если исходные
коды программ считаются похожими (т.е. один с большой вероятностью содержит пла-
гиат другого), то и на уровне вышеописанных строковых представлений у них присут-
ствуют существенные общие части. Причем эти общие части не обязательно должны
быть непрерывны (в противном случае алгоритм было бы легко обмануть). Одним из
наиболее ранних алгоритмов, использующих этот подход, описывается в работе [2], но
из-за эвристики, которой пользуется данный алгоритм, его легко обойти плагиатору. В
следующей части мы кратко опишем современные алгоритмы данной области.
Структурные алгоритмы, что следует из названия, используют саму структуру
программы, обычно это или граф потока управления, или абстрактное синтаксиче-
ское дерево. Вследствие такого представления алгоритм сводит на нет многие возмож-
ные действия плагиатора. Но все алгоритмы этого класса являются крайне трудоемки-
ми, поэтому не используются на практике. Единственным исключением может служить
алгоритм из статьи [3]. Он использует представление программы в виде абстрактного
синтаксического дерева и имеет, по утверждению авторов, квадратичную сложность,
хотя проверить это не представилось возможным, так как авторы скрывают некоторые
детали алгоритма. Но даже этого недостаточно на практике для наиболее распростра-
ненного случая поиска плагиата, когда у нас есть исходный код программы (а возмож-
но – несколько) и большая база оригиналов, с которыми мы хотим его сравнить.
Семантические алгоритмы во многом похожи на структурные и текстовые. На-
пример, в статье [4] используется представление исходного кода программы в виде
графа с вершинами двух типов. Одни строятся из последовательности операторов, ко-
торым назначается определенная семантика (например, математическое выражение,
цикл), другие задают отношение, в котором состоят соседние с ней вершины (напри-
мер, вхождение). Между двумя вершинами первого типа обычно стоит вершина второ-
го.
189
Применение этого алгоритма в нашем случае выглядит так: получаем токенизиро-
ванное представление s и t двух программ, делим вторую строку t на подстроки (сек-
ции), каждая из которых представляет модуль (функцию или процедуру) исходной про-
граммы. Для каждой секции и s получаем значение оптимального локального выравни-
вания, комбинируем результаты. Это позволяет алгоритму корректно обрабатывать пе-
рестановки модулей исходной программы. С деталями алгоритма можно ознакомиться
в статье [5].
190
где Comp(.) (Comp(.|.)) – длина сжатой (условно) строки, полученной из исходной с по-
мощью какого-либо алгоритма сжатия. В работе [8] специально был разработан алго-
ритм сжатия, который удовлетворяет следующим специфическим требованиям.
• От традиционных алгоритмов сжатия очень часто требуют возможность выполне-
ния в реальном времени или с линейной асимптотикой. Для нас же главное – каче-
ство сжатия, а скорость менее важна. Наш алгоритм может работать за сверхлиней-
ное время, но должен достигать наилучшей степени сжатия для используемого типа
данных.
• Для получения d(x,y) нужно вычислить Comp(x), условное сжатие x по y (Comp(x|y))
и Comp(xy). Обычно программа, которая является плагиатом другой, содержит
длинные, приблизительно совпадающие с фрагментами оригинальной программы
блоки. Они возникают, когда злоумышленник копирует часть кода оригинальной
программы и делает в нем простые изменения. Требуемый алгоритм сжатия должен
эффективно справляться с такими вещами, поэтому применяется алгоритм семейст-
ва LZ (Lempel-Ziv, см. [11]), использующий найденные неточные совпадения.
Рассмотрим непосредственно процесс сравнения двух программ. Сначала произ-
водится их токенизация, далее запускается алгоритм TokenCompress, основанный на
алгоритме сжатия LZ (см. [11]). Первым делом он находит длиннейшую неточно по-
вторяющуюся подстроку (approximately duplicated substring), заканчивающуюся в теку-
щем символе; кодирует ее указателем на предыдущее размещение и сохраняет инфор-
мацию о внесенных поправках. Реализация отличается от классического LZ-сжатия не-
которыми особенностями, связанными со спецификой задачи: классический алгоритм
семейства LZ с ограниченным буфером может пропустить некоторые длинные повто-
ряющиеся подстроки из-за ограничения на размер словаря (или скользящего окна) во
время кодирования. Это не страшно для кодирования обычных текстов, где нужно,
прежде всего, экономить память и уменьшать время исполнения, ведь потери качества
сжатия будут крайне малы. Но в силу специфики задачи и выбранного алгоритма ее
решения, использующего неточные совпадения, потеря даже небольшого количества
длинных повторяющихся подстрок неприемлема.
В оригинальной статье [8] не приводится алгоритм подсчета условного сжатия x
по y (Comp(x|y)), но, скорее всего, предполагается, что в начале алгоритма инициализи-
руется словарь (буфер) всеми подстроками строки y, а дальше алгоритм остается без
изменений.
Классические алгоритмы семейства LZ не поддерживают неточных совпадений.
Данный же алгоритм ищет неточно повторяющиеся подстроки и кодирует их несовпа-
дения. TokenCompress использует пороговую функцию, чтобы определить, выгоднее ли
кодировать несовпадения или лучше воспользоваться альтернативами (кодировать час-
ти отдельно или вообще не производить кодирования некоторых частей). Соответст-
венно, некоторые неточные повторы могут быть объединены в один с небольшим ко-
личеством ошибок (несовпадений). Неточно совпавшие пары подстрок (т.е. неточные
повторы) записываются в файл и позже могут быть просмотрены человеком. На основе
работы этого алгоритма считается величина d(x,y), которая затем используется для оп-
ределения, насколько вероятно то, что одна из этих программ является плагиатом дру-
гой.
191
лись. Этот метод позволяет организовать эффективную проверку по базе данных. Ме-
тод отпечатков можно представить в виде четырех шагов.
1. Последовательно хэшируем 1 подстроки токенизированной программы P длины k
(фиксированный параметр).
2. Выделяем некоторое подмножество их хэш-значений, хорошо характеризующее P.
Проделываем те же шаги для токенизированных программ T1 , T2 KTn и помещаем
их выбранные хэш-значения в хэш-таблицу.
3. С помощью хэш-таблицы (базы) получаем набор участков строки P, подозритель-
ных на плагиат.
4. Анализируем полученные на предыдущем шаге данные и делаем выводы.
Очевидно, что содержательная часть этого алгоритма – выбор k и шага 2. Число k
ограничивает длину наименьшей подстроки, с которой может работать данный алго-
ритм. Если в двух программах есть общая подстрока длиной, например, два символа, то
не обязательно, что мы нашли плагиат. Скорее всего, это совпадение – просто случай-
ность и обусловлено малой мощностью нашего алфавита. Т.е. значение k может спо-
собствовать игнорированию «шума», но при больших значениях k мы можем проигно-
рировать настоящий случай плагиата, поэтому это число нужно выбирать с осторожно-
стью.
Пусть наша хэш-функция – это h, а h1 , h2 K h| P|−k +1 – последовательность значений
хэш-функций, полученных на шаге 1. Рассмотрим некоторые возможные реализации
шага 2.
• Наивный подход заключается в выборе каждого i-ого из n хэш-значений, но он не-
устойчив к модификациям кода. (Если мы добавим в начало файла один лишний
символ, то получим совершенно другое подмножество хэш-значений после выпол-
нения алгоритма.)
• Мы можем назначать метками p минимальных хеш-значений (см. [13]), их количе-
ство для всех документов будет постоянно. С помощью этого метода нельзя найти
частичные копии, но он хорошо работает на файлах примерно одного размера, на-
ходит похожие файлы, может применяться для классификации документов.
• Манбер [14] предложил выбирать в качестве меток только те хеш-значения, для ко-
торых h ≡ 0 mod p, так останется только n/p меток (объем идентификационного на-
бора для разных файлов будет отличаться, сами метки будут зависеть от содержи-
мого файла). Однако в этом случае расстояние между последовательно выбранными
хеш-значениями не ограничено и может быть велико. В этом случае совпадения,
оказавшиеся между метками, не будут учтены.
• Метод просеивания (winnowing) [15] не имеет этого недостатка. Алгоритм гаранти-
рует, что если в двух файлах есть хотя бы одна достаточно длинная общая подстро-
ка, то как минимум одна метка в их наборах совпадет.
1
Обычно используют хэш-функцию из алгоритма Карпа-Рабина поиска подстроки в строке [12].
192
Последний пункт гарантируется величиной параметра k. Теперь разберемся с пер-
вым пунктом. Очевидно, что он будет соблюдаться, если из каждых (t − k + 1) хэш-
значений от идущих непосредственно последовательно подстрок будет выбрана хотя
бы одна в качестве метки. Будем продвигать окно размера w = (t − k + 1) вдоль последо-
вательности h1...hn, на каждом шаге перемещаем окно на одну позицию вправо. Назна-
чаем меткой минимальное hj в окне; если таких элементов несколько, то назначается
самый правый из них.
Рассмотрим пример. Строке abrakadabra соответствует последовательность хеш-
значений:
12, 35, 78, 3, 26, 48, 55, 12, 35
Пусть k = 2, t = 4 , следовательно w = t − k + 1 = 3 . Выпишем последовательно со-
держание окон, полученных алгоритмом, жирным шрифтом выделены те хэш-значения,
которые мы будем назначать метками:
(12, 35, 78), (35, 78, 3), (78, 3, 26), (3, 26, 48), (26, 48, 55), (48, 55, 12), (55, 12, 35)
Заметим, что если в двух последовательных окнах хэш-значения от одной и той
же подстроки являются минимальными, то меткой назначается только одна из них (нет
смысла хранить две последовательные абсолютно одинаковые метки), поэтому полу-
чим такой набор меток:
12, 3, 26, 12
Нужно отметить, насколько компактен набор идентификационных меток строки.
Показателем эффективности алгоритма может служить плотность d – доля хэш-
значений, выбранных алгоритмом в качестве меток, среди всех хэш-значений докумен-
та. Для метода просеивания
2
d=
w +1 .
(см. оригинальную статью [15].) Некоторые другие реализации метода отпечатков тоже
могут быть изменены так, чтобы соблюдались описанные выше требования, но для них
показатель d в этом случае будет значительно выше, полученного для метода просеива-
ния.
Как мы видим, выбор метки определяется только содержимым окна, такой алго-
ритм называется локальным. Любой локальный алгоритм выбора меток корректен.
Действительно, если в двух файлах есть достаточно большая общая подстрока, то будут
и одинаковые окна, а значит, будут назначены одинаковые метки. По ним определим,
что в файлах есть совпадения. Это доказывает корректность приведенного алгоритма.
3. Результаты тестирования
193
Простая коллекция. Содержит настоящий случай плагиата для небольшой про-
граммы, плагиат практически не пытались скрыть. Все детекторы обнаружили плагиат.
Коллекция с олимпиады по программированию. Она построена на основе про-
грамм, написанных во время различных олимпиад по программированию, и представ-
ляет собой подмножество программ, выложенных в открытый доступ на сайте
http://neerc.ifmo.ru. Алгоритмически решения задач в большинстве случаев достаточно
схожи, реализации же достаточно сильно зависят от стиля программирования конкрет-
ного участника. Все детекторы, кроме SIM, справились с задачей, ни один не посчитал
плагиатом независимо написанные программы. Но стоит отметить, что для менее спе-
цифических программ, содержащих больше шаблонных решений, могли возникнуть
проблемы. Детектор SIM использует алгоритм, основанный на выравнивании строк,
поэтому на небольших программах он бывает неоправданно подозрителен, в 14 % слу-
чаев SIM посчитал плагиатом программы, написанные независимо.
Тестовая коллекция детектора SID. Здесь мы имеем дело с четырьмя видами ис-
кусственных изменений кода: перегруппировка переменных, различные виды рефакто-
ринга кода, перегруппировка методов, случайные вставки операторов. На первых трех
видах изменений кода все детекторы справились идеально. Для четвертого вида изме-
нений мы имеем более сложную картину. Для небольшого количества вставок, 20 на
100 строк кода или 300 на 1100 строк, все детекторы находят плагиат. Если же количе-
ство вставок достигает 50 на 100 строк кода или 400 на 1100 строк, то все детекторы,
кроме SID, перестают находить плагиат, что неудивительно из-за построения этих ал-
горитмов. Отметим, что даже SID в одном случае не смог обнаружить плагиат, что за-
кономерно: его алгоритм просто более устойчив к данному способу сокрытия плагиата.
194
чаям плагиата), но при этом, возможно, неприемлемо низкой точностью (доля настоя-
щих случаев плагиата в найденных алгоритмом) для самостоятельного использования,
а внутри кластера применить алгоритм с большей точностью, причем, возможно, даже
несколько алгоритмов, но неустойчивых к разным способам сокрытия плагиата. Наи-
более интересно, что для уточнения результатов внутри кластера мы можем применить
алгоритм с достаточно высокой трудоемкостью, т.е. вышеупомянутые структурные или
семантические методы.
5. Заключение
195
6. Благодарности
Литература
1. Faidhi J.A.W., Robinson S.K. An Empirical Approach for Detecting Program Similarity
within a University Programming Environment. // Computers and Education. 1987. 11(1).
P. 11–19.
2. Heckel P. A Technique for Isolating Differences Between Files. // Communications of the
ACM 21(4). April 1978. P. 264–268.
3. Baxter I., Yahin A., Moura L., Anna M.S., Bier L. Clone Detection Using Abstract Syntax
Trees. // Proceedings of ICSM. IEEE. 1998.
4. Mishne G., M. de Rijke. Source Code Retrieval using Conceptual Similarity // Proceed-
ings RIAO. Vaucluse. 2004. P. 539–555.
5. Huang X., Hardison R.C., Miller W. A space-efficient algorithm for local similarities. //
Computer Applications in the Biosciences 6. 1990. P. 373–381.
6. Wise M.J. String similarity via greedy string tiling and running Karp-Rabin matching. //
Dept. of CS, University of Sydney. December 1993.
7. Prechelt L., Malpohl G., Philippsen M. JPlag: Finding plagiarisms among a set of pro-
grams. // Technical Report No. 1/00, University of Karlsruhe, Department of Informatics.
March 2000.
8. Chen X., Francia B., Li M., McKinnon B., Seker A. Shared Information and Program Pla-
giarism Detection. // IEEE Trans. Information Theory. July 2004. P. 1545–1550.
9. Li M., Vitanyi P. An introduction to Kolmogorov complexity and its applications. 2nd
Ed., Springer, New York. 1997.
10. Li M., Chen X., Li X., Ma B., Vitanyi P. The similarity metric. // Proceedings of the 14th
Annual ACM-SIAM Symposium on Discrete Algorithms. 2003. P. 863–872.
11. Ziv J., Lempel A. A universal algorithm for sequential data compression // IEEE Transac-
tions on Information Theory 23. 1977. P. 337–343.
12. Karp R.M., Rabin M.O. Efficient randomized pattern-matching algorithms // IBM J. of
Research and Development. 1987. 31(2). P. 249–260.
13. Heintze N. Scalable document fingerprinting. // In 1996 USENIX Workshop on Elec-
tronic Commerce, 1996.
14. Manber U. Finding similar files in a large file system. // Proceedings of the USENIX Win-
ter 1994 Technical Conference. San Francisco. 1994. P. 1–10.
15. Aiken A., Schleimer S., Wikerson D. Winnowing: local algorithms for document finger-
printing. // Proceedings of ACMSIGMOD Int. Conference on Management of Data. San
Diego. 2003. P. 76–85. ACM Press. New York, USA. 2003.
16. Moussiades L.M., Vakali A. PDetect: A Clustering Approach for Detecting Plagiarism in
Source Code Datasets. // The Computer Journal Advance Access. June 24, 2005.
196