Поиск пересечений в исходном коде

Поиск пересечений в исходном коде

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

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

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

Впервые проблема плагиата и его особенностей в академической среде была поднята в статье Cheating Policy in a Computer Science Department (1980).

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

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

Модификации кода

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

  • Лексические изменения.
  • Структурные изменения.

Лексические изменения

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

  • Изменение комментариев.
  • Изменение отступов и пробелов.
  • Переименование функций и переменных.
  • Разделение или объединение объявления переменных.

Структурные изменения

К структурным можно отнести более сложные методы:

  • Изменение типов циклов (while → for...).
  • Замена выражения для условий (if, else → switch...).
  • Перестановки блоков кода.
  • Вынос части кода в отдельные функции и наоборот.

Часто встречаются классификации обходов по сложности обнаружения, вот пример одной из таких классификаций:

Рисунок позаимствован из статьи Plagiarism Level Taxonomy as defined by Faidhi & Robinson (1987)
Рисунок позаимствован из статьи Plagiarism Level Taxonomy as defined by Faidhi & Robinson (1987)

Методы обнаружения пересечений

Классические методы

Выделяется четыре группы методов:

  • Анализ кода средствами обработки текстов на естественном языке (NLP).
  • Анализ на уровне токенов.
  • Построение абстрактных синтаксических деревьев (AST).
  • Построение графа зависимостей (PDG).

Анализ средствами NLP

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

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

Поиск пересечений в исходном коде

Анализ на уровне токенов

Один из самых распространённых методов обнаружения плагиата. В данном случае программный код превращается в поток неделимых (с точки зрения языка программирования) токенов. Для дальнейшего анализа можно также применять методы NLP, где в тексте вместо отдельных символов будут использоваться токены.

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

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

  • В зависимости от языка программирования используется какой-либо несложный парсер для преобразования текста программы в последовательность токенов.
  • Часто последовательность токенов преобразовывается, например:

а) удаляются все названия переменных,

б) удаляются комментарии,

в) чистятся все строки и т. п.

  • Используются более-менее стандартные алгоритмы для сравнения последовательности токенов:

а) Winnowing,

б) Rabin–Karp,

в) Suffix tree (array) и т. п.

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

Построение AST

В данных методах уже используются полноценные парсеры языков и вместо последовательностей токенов строятся AST-деревья, которые каким-либо образом можно сравнивать между собой. Например, в статье Improved Plagiarism Detection Algorithm Based on Abstract Syntax Tree (2013, Guo Tao, Dong Guowei, Qin Hu and Cui Baojiang) вводится специальная хеш-функция для AST-деревьев, благодаря которой можно искать код с перестановками:

Поиск пересечений в исходном коде

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

Построение PDG

Идея метода — построить граф зависимостей между разными выражениями в программе. На рисунке ниже приведён пример PDG из статьи GPLAG: Detection of Software Plagiarism by Program Dependence Graph Analysis (2006, Chao Liu, Chen Chen, Jiawei Han):

Поиск пересечений в исходном коде

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

  • Необходимо сравнивать графы — искать изоморфные подграфы.
  • Непонятно, как работать с большими объёмами, — неясно, что и как индексировать.

На основе ML

В последнее время появилось несколько методов на основе ML, например:

  • Code Clone Detection using Code2Vec (2020, Anupriya Prasad).
  • Source Code Plagiarism Detection with Pre-Trained Model Embeddings and Automated Machine Learning (2023, Fahad Ebrahim, Mike Joy).

Что мы сотворили

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

Для начала рассмотрим более простую задачу — сравнение двух репозиториев.

Сравнение двух репозиториев

Пусть дано два репозитория:

  • Проверяемый репозиторий.
  • Репозиторий-источник.

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

На рисунке изображено три различные ситуации:
На рисунке изображено три различные ситуации:

1. Проверяемый участок совпал с одним участком в источнике.

2. Проверяемый участок совпал с двумя участками в источнике, причём длины участков в источнике отличаются от длины проверенного (например, за счёт различий в форматировании или комментариях).

3. Два проверяемых участка пересекаются между собой.

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

А термин «репозиторий-источник» или просто «источник» используем для удобства.

Чтобы получить какую-то численную оценку совпадений с учётом подобных случаев, мы вводим понятие покрытия.

Покрытие — суммарная длина участков кода проверяемого репозитория, которые нашлись в источнике, при этом участок считается только один раз, даже если он несколько раз нашёлся в источнике.

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

Файл A имеет два участка с пересечениями — 40 символов и 60 символов:

  • 40 символов — нашёлся один раз (40 символов в «файле 1»).
  • 60 символов — нашёлся дважды (50 символов в «файле 1» и 70 символов в «файле 2»).
В итоге покрытие для файла A будет равно 60 + 40 = 100 символов.
В итоге покрытие для файла A будет равно 60 + 40 = 100 символов.

Файл B также имеет два участка с пересечениями, но они накладываются друг на друга, в итоге получаем три кусочка:

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

В итоге покрытие для файла B будет равно 50 + 30 + 50 = 130 символов. А итоговое покрытие для репозитория будет равно 100 + 130 = 230 символов.

Задачу поиска блоков решаем в несколько этапов, основные из которых:

  • Парсинг кода — построение AST.
  • Обход AST и построение последовательности токенов.
  • Сравнение последовательностей токенов.

Рассмотрим первые два этапа на примере: допустим, есть какой-то код (пусть будет Python):

|# комментарий print (1+x)

После парсинга будет сформировано AST:

Поиск пересечений в исходном коде

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

  • Добавить ли значение узла без изменений.
  • Добавить ли вместо узла заглушку.
  • Следует ли пропустить узел и всех его потомков.

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

|# комментарий | print | ( | 1 | + | X | ) |

Так и упрощённую: например, удалив комментарии, заменив все числа на 42, а идентификаторы переменных — на val:

| print | ( | 42 | + | val | ) |

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

  • Комментарии.
  • Атрибуты и аннотации.
  • Подсказки к типам.

Пробовали сводить имена переменных к одному значению, чтобы можно было находить совпадения с учётом переименований, но пока от этого отказались: находится слишком много «лишних» совпадений — интерфейсы, объекты DTO и т. п.

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

  • C
  • C++
  • C#
  • Java
  • Kotlin
  • JavaScript
  • TypeScript
  • Python
  • Protocol Buffers
  • PHP
  • Go
  • Swift
  • Rust

Полученные последовательности токенов мы сравниваем между собой, используя алгоритм нечёткого поиска: https://habr.com/ru/companies/antiplagiat/articles/461903/.

Индексация и поиск по индексу

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

Для работы с такими объёмами мы сначала выполняем индексацию, а сам поиск проводим в два этапа:

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

В качестве индекса мы используем имеющийся у нас индекс шинглов: https://habr.com/ru/companies/antiplagiat/articles/445952/.

Сами шинглы формируем традиционно — наборы токенов небольшой длины. Разве что дополнительно фильтруем их с помощью алгоритма Winnowing: https://theory.stanford.edu/~aiken/publications/papers/sigmod03.pdf, но это не обязательно.

Архитектурное решение

Схематично архитектуру можно представить следующим образом:

Поиск пересечений в исходном коде

Здесь в зависимостях:

  • PostgreSQL — хранятся метаданные репозитория.
  • Shingle Index — хранятся шинглы и выполняется поиск по ним.
  • Blob Storage — хранятся BLOB'ы с текстами и позициями токенов в файлах.

Альтернативы

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

В мире Open Source решения куда более интересные:

  • MOSS — выполняет сверку переданных файлов и выдаёт результат в виде HTML. Индекс отсутствует. Разрабатывался для использования в университетской среде при проверке студенческих работ.
  • Code Detective — аналог MOSS. Может выполнить попарные проверки только на основе локальных данных, заточена на проверки студенческих работ. Нет построения индекса.
  • JPlag — может выполнить попарные проверки только на основе локальных данных, заточена на проверки студенческих работ. Нет построения индекса.
  • Dolos — производит попарную проверку загруженных файлов, заточена на проверки студенческих работ. Нет построения индекса.
  • Sherlock — довольно старая система и не особо развивается, последние изменения — 2009 года.

Ключевые их проблемы — отсутствие персистентного индекса и невозможность работы на больших объёмах (как по размеру проверяемого репозитория, так и по количеству источников).

Рубрика «Эксперименты»

Поиск пересечений в исходном коде

В экспериментах мы хотели проверить репозитории из GitHub и было интересно посмотреть, встречается ли в репозиториях с permissive-лицензиями код из репозиториев с copyleft-лицензиями.

В OSS-мире присутствует два крупных семейства лицензий: copyleft и permissive.

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

Но только семейство copyleft налагает ограничения на конечный продукт или на форму поставки компонента в нём.

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

Первый эксперимент

Индексация

Изначально мы проиндексировали около 90 000 репозиториев из GitHub по одним из наиболее популярных языков: Python, Javа, JavaScript, С/C++, C#. Уникальных проектов оказалось не очень много — к моменту, когда мы перешли к индексации репозиториев со 100–200 звёздами (шли от наиболее популярных и вниз), получили такую статистику:

Поиск пересечений в исходном коде

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

Среди проиндексированных репозиториев получили следующее распределение по лицензиям:

  • Permissive: 50 765
  • Copyleft: 4039
  • Неизвестные: 35 748

Подробнее:

Поиск пересечений в исходном коде
Поиск пересечений в исходном коде
Поиск пересечений в исходном коде

Проверка

В первом эксперименте мы проверили все репозитории с permissive-лицензиями по всем репозиториям с copyleft-лицензиями. Неизвестные лицензии из проверки исключили, т. к. их можно отнести к обоим семействам лицензий. В результате проверки получили 185 ГБ отчётов. Стоит отметить, что на каждый проверяемый репозиторий могло найтись до десяти источников, с которыми были пересечения (на самом деле их могло быть сильно больше, но мы специально ограничили поиск десятью, с которыми пересечений было больше всего). Чтобы хоть как-то уменьшить объём, решили дополнительно отфильтровать результаты по значимости. Значимость определили следующим образом.

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

  • Нашёлся один файл с покрытием не менее 50 000 символов.
  • Нашлось не менее пяти файлов, каждый из которых имеет покрытие не менее 10 000 символов.

После фильтрации осталось 7083 репозитория со значимыми совпадениями, а для них получилось 51 156 отчётов о парных сверках, из которых мы проанализировали вручную 200 пар (менее 0,4%).

Каких-либо интересных результатов обнаружить не удалось, все отчёты были заполнены «корректными» совпадениями:

  • Совпадали исходные коды используемых библиотек, особенно отличились JavaScript-библиотеки (jQuery, Angular, Bootstrap и т. д.).
  • JavaScript-бандлы.

· Куски сгенерированного кода — это могли быть клиенты для взаимодействия по gRPC; REST-клиенты, сформированные по спецификации OpenAPI; специфичные генерации для языка программирования (например, code-behind-файлы в C#: *.Designer.cs, *.csd.cs).

· Proto-контракты — дополнительные контракты от Google, контракты популярных Open Source продуктов из Apache Software Foundation и т. п.

Были и забавные моменты:

  • Находилось совпадение в каком-либо файле, но при просмотре репозитория этот файл найти не удавалось — оказывается, он уже был удалён.
  • С момента индексации и до проверки уже успела поменяться лицензия репозитория.

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

  • На данный момент мы не работаем с историей из GIT.
  • Не исключено, что в GitHub был добавлен уже готовый репозиторий без учёта его истории.

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

Фильтры

Проанализировав проблемы, мы сделали несколько доработок, чтобы убрать «лишние» корректные совпадения.

Первые шаги

Начали с простейших фильтров:

  • Фильтры по именам файлов, чтобы отбрасывать минифицированный JavaScript-код и наиболее популярные библиотеки:
  • *.min.js*jquery*.js*bootstrap*.js
  • Игнорирование файлов, имеющих определённые строки в комментариях:
  • generated byauto-generated

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

Поиск пересечений в исходном коде

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

Из новых находок:

  • Стало попадаться много шаблонного кода ML/DL-обвязки.
  • Находились скопированные примеры кода библиотек и фреймворков.

Следующим этапом мы добавили ещё пару фильтров, но использовали уже другие подходы.

Фильтр минифицированного кода

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

А нужно ли вообще убирать минифицированный код из сравнений?

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

Поэтому мы посчитали некоторые метрики для обычного кода и минифицированного:

  • Доля пробельных символов.
  • Доля переводов строк.
  • Доля комментариев.

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

Фильтрация часто используемого кода

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

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

Результаты

В итоге удалось отбросить ещё несколько совпадений:

Поиск пересечений в исходном коде

На самом деле всё равно какие-то библиотеки находятся — или редко используемые, или каких-то редких версий. Всё так же находятся обвязки ML/DL-моделей. Но большой пласт «корректных» совпадений удалось действительно отбросить.

Второй эксперимент

Индексация

Для второго эксперимента мы заново переиндексировали репозитории с использованием описанных выше фильтров. Кроме того, мы как увеличили количество проиндексированных репозиториев для старых языков (Python, Javа, JavaScript, С/C++, C#), так и реализовали поддержку ещё шести языков (Go, Kotlin, PHP, Rust, Swift, TypeScript), которые тоже добавили в индекс, доведя количество проиндексированных репозиториев до 165 000.

В итоге получили такое распределение по языкам:

Поиск пересечений в исходном коде

И по лицензиям:

Поиск пересечений в исходном коде

Проверка и результаты

Как и в первом эксперименте, мы проверяли все репозитории с permissive-лицензиями по всем репозиториям с copyleft-лицензиями, исключив репозитории с неизвестными лицензиями. Несмотря на почти двукратный рост числа проверяемых репозиториев, суммарный объём отчётов о проверке уменьшился до ~50 ГБ, что объясняется работой фильтров на библиотечный, сгенерированный и минифицированный код.

Для определения значимых пар использовали те же критерии значимости, что и в первом эксперименте:

  • Есть хотя бы один файл с покрытием в 50 000 символов.
  • Есть не менее пяти файлов, каждый из которых имеет покрытие в 10 000 символов.

В результате осталось 3067 репозиториев, для которых имеется 5497 отчётов о парных сверках, содержащих значимые совпадения, что более чем в девять раз меньше по сравнению с 51 146 парами из первого эксперимента.

Как и раньше, дальнейший анализ был ручной: рассмотрели 57 пар (чуть больше 1% от общего числа). Кроме того, отдельно были рассмотрены межъязыковые совпадения (например, когда Java-код «совпадает» с C#-кодом), тут уже смотрели без учёта значимости. Результаты по большей части опять были представлены «корректными» совпадениями, хотя их состав изменился:

  • По библиотекам стало встречаться много C++, хотя и JavaScript остался.
  • Минифицированный и сгенерированный код практически исчез.
  • Попадались совпадения массивов и перечислений (особенно много в межъязыковых пересечениях).

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

И наконец-то стали встречаться и интересные результаты:

Репозиторий cguweb-com/Arduino-Projects

Лицензия: MIT License

Нашлись библиотеки с copyleft-лицензиями:

Лицензия GPL-2.0

Лицензия LGPL-3.0

Репозиторий nissl-lab/npoi

Есть совпадения в классе BigInteger, в том числе совпадения с классом MutableBigInteger из JDK на 40% (совпадения вплоть до комментариев).

Примеры совпадений:

Поиск пересечений в исходном коде

Репозиторий xiangyuecn/Recorder

Совпадает код некоторых функций с функциями из mp3 LAME encoder:

Примеры совпадений

Поиск пересечений в исходном коде
Поиск пересечений в исходном коде

Прочее

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

Оказалось, что во втором репозитории используется библиотека https://github.com/marek-stoj/NLangDetect, которая является портом под dotnet-библиотеку https://github.com/shuyo/language-detection, используемую в первом репозитории.

Ещё одна пара репозиториев:

В обоих используется портированный из Go код: https://github.com/moby/moby/blob/master/pkg/namesgenerator/names-generator.go.

Проверки избранных репозиториев

Наверное, наиболее очевидным был выбор для попарной сверки репозиториев OpenOffice и LibreOffice. Из 1,7 ГБ файлов репозитория мы смогли распознать и проверить 245 МБ (13%) по поддерживаемым на текущий момент языкам. Множество файлов было отброшено, т. к. не представляют особого интереса, таковыми для нас были, например, *.xml, *.xslt, *.md, а также различные документы, файлы локализации и т. п. После обработки получилось около 221 млн символов, и в результате найдены пересечения по 27 млн символов (12%). В разбивке по языкам получились такие статистики:

Поиск пересечений в исходном коде

Так, к примеру, для C# совпадают обвязки для работы с Uno Platform: https://github.com/apache/openoffice/tree/trunk/main/cli_ure/source/basetypes и https://github.com/LibreOffice/core/tree/master/cli_ure/source/basetypes/uno.

А ещё мы выборочно проверили открытые исходники известных компаний и проектов. Получили такие результаты.

Для справки

Мы намеренно не проверяли известные утёкшие исходники (Microsoft, Yandex и т. д.).

  • YTsaurus — в основном нашли пересечения по сторонним библиотекам, исходники которых лежат в репозитории.
  • Gravity UI — ничего не нашли.
  • AppMetrica — ничего не нашли.
  • Diplodoc — ничего не нашли.
  • DataLens — ничего не нашли.
  • YDB — в основном не нашли ничего интересного, были пересечения по минифицированному JS и пересечения с ClickHouse.
  • ClickHouse — в основном не нашли ничего интересного, были пересечения по минифицированному JS и файлам-заголовкам (С, С++).
  • CatBoost — ничего не нашли.

Docker и Kubernetes

  • Docker — ничего не нашли.
  • Docker Registry — пересечения по минифицированному JS.
  • Docker Swarm — есть пересечения по .proto-файлам, но лицензии совместимы.
  • Kubernetes — очень много пересечений по .proto-файлам, в основном общеизвестные контракты. Мусорные пересечения вроде последовательностей нолей, лицензии совместимы.

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

Apache Software Foundation

  • Apache Airflow — есть пересечения по авторизации, взаимодействию с Kubernetes и фоновыми задачами/потоками, что не удивительно, т. к. задачи решаются одинаково. Лицензии совместимы.
  • Ignite — пересечения с Elasticsearch в реализации фильтра Блума и форматировании логируемых сообщений. Лицензии совместимы. Очень много пересечений по внешним пакетам, ядру Java и .min.js.
  • Kafka — в основном не нашли ничего интересного, много пересечений с ядром Java.
  • JMeter — пересечения по минифицированному JS.
  • Lucene — пересечения с Apache Lucene.NET (порт с Java на C#).

Отдельным пунктом было интересно сравнить между собой несколько IDE, для этого мы взяли NetBeans IDE, IntelliJ Community и Visual Studio Code. С VS Code пересечений не нашлось. При сравнении между собой NetBeans и IntelliJ обнаружили сильную схожесть используемых лексеров (e. g. TwigTopColoringLexer.java & _RegExLexer.java), но при разборе обнаружилось, что код автосгенерирован.

Странное

Проверяли библиотеку https://github.com/ricmoo/pyaes, нашли её же в исходниках другого репозитория https://github.com/Alessanddotnet swap variablesroZ/LaZagne/tree/master/Windows/lazagne/config/crypto/pyaes по очень неявному расположению.

Заключение

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

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

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

В тех репозиториях с GitHub, что удалось допроверить руками, в основном всё было хорошо, заимствование кода из copyleft-репозиториев встречалось относительно редко. Намного чаще имел место непорядок с лицензиями: в репозитории использовались библиотеки, для которых забыли указать лицензию, хотя и сам репозиторий, и библиотеки имеют permissive-лицензии. Впрочем, подобные нарушения достаточно легко исправляются.

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

Исходя из вышеуказанного, видятся следующие применения разработанного нами решения:

  1. Вспомогательный инструмент для поиска плагиата из больших кодовых баз.
  2. Проверка ученических работ между собой.
  3. Поиск известного malware — проиндексировать malware и проверять свои репозитории на наличие вредоносного кода.
  4. Поиск утечек — проиндексировать свои репозитории и сканировать открытые источники в поисках совпадений.

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

Начать дискуссию