(VK internal hackathon) Tool for searching and aggregating data for PHP
CodeQuery это инструмент который нацелен на поиск различных сложных связей между функциями и другими символами.
CodeQuery анализирует кодовую базу и создает слепок данных. Этот слепок содержит в себе всю необходимую для работы информацию и не зависит от кода. В данный момент он хранит все функции, глобальные переменные, а также граф вызовов.
CodeQuery использует подмножество языка SQL для запросов к данным.
Перейдите на страницу релизов и скачайте нужную версию.
Для установки вам потребуется Golang версии не ниже 1.16.
Склонируйте репозиторий:
git clone https://github.com/i582/CodeQuery
cd CodeQuery
Выполните команду:
make build
Бинарный файл будет лежать в папке build
Для того чтобы создать базу данных для вашего кода, перейдите в папку с проектом, а затем выполните следующую команду:
CodeQuery create --name testDatabase ./folder1 ./folder2 ...
Где после флага --name
вам нужно указать список папок или файлов для анализа.
После выполнения этой команды у вас появится файл testDatabase.db
с которым мы будет взаимодействовать дальше.
Для входа в интерактивную оболочку выполните следующую команду:
CodeQuery open --name testDatabase
Для выполнения запросов в интерактивной оболочке используется команда run
. Запрос указывается после команды в кавычках.
Например:
run "SELECT * FROM funcs"
Если вы хотите запустить запрос из файла, то используйте флаг -f
:
run -f "test.sql"
Обратите внимание, передавать имя файла необходимо в кавычках!
Простой запрос:
SELECT * FROM funcs
Таблица состоит из 4 полей:
- ID
(id)
- Имя
(name)
- Количество использований
(uses)
- Количество глобальных переменных
(globals)
По каждому из полей можно сортировать таблицу используя ORDER BY
:
SELECT * FROM funcs ORDER BY name
Поддерживается сортировка только по одному полю
По-умолчанию, выводятся первые 20 записей, вы можете изменить это количество с помощью LIMIT
:
SELECT * FROM funcs LIMIT 100
Саму таблицу можно фильтровать с помощью WHERE
:
SELECT * FROM funcs WHERE "<bool expression>"
Для выражений поддерживаются следующие операторы:
AND
OR
NOT
>
<
<=
>=
=
<>
()
Поддерживаются целочисленные и числа с плавающей запятой в качестве литералов, а также строки в двойных и одинарных кавычках.
Рассмотрим простой запрос:
SELECT * FROM funcs WHERE func.countUse() > 0
Заметьте, когда вы работаете с таблицей funcs
для доступа к каждому элементу вы должны использовать переменную func
.
Переменная func
имеет тип IFunc
, который определяет следующие методы:
countUse
— возвращает количество использований функцииglobals
— возвращает глобальные переменные используемые внутри функцииfullName
— возвращает полное имя, включает в себя пространство имен и класс, если естьname
— возвращает имя функции или метода без пространства имен и классаnamespace
— возвращает пространство именclassName
— возвращает имя класса
Например, если вы хотите выбрать только функции:
SELECT * FROM funcs WHERE func.className() = ''
Методы fullName
, name
и прочие похожие возвращают строку для которой также определены некоторые методы:
contains
— проверяет содержит ли в себе строка переданную строку
Например, если нужно найти функции только в определенном пространстве имен использующие глобальные переменные:
SELECT * FROM funcs
WHERE func.className() = ''
AND func.namespace().contains('Some\\Namespace')
AND func.globals().count() > 0
Здесь func.globals()
возвращает значение типа IGlobals
, который определяет следующие методы:
count
— возвращает количество глобальных переменныхcontains
— проверяет наличие хотя бы одной функции из выборки в списке
Давайте рассмотрим метод contains
, так как он не является обычным, потому что может принимать аргументом подзапрос.
Например, мы хотим найти все функции, которые используют определенную глобальную переменную:
SELECT * FROM funcs
WHERE func.className() = ''
AND func.globals().contains(
SELECT * FROM globals WHERE global.name() = 'someName'
)
Первым аргументом мы передаем подвыражение SELECT
которое выбирает все глобальные переменные подходящие под условия, а потом проверяет, что в глобальных переменных функции есть хотя бы одна из подзапроса.
Простой запрос:
SELECT * FROM globals
Таблица состоит из 4 полей:
- ID
(id)
- Имя
(name)
- Количество использований
(uses)
Таблицу также можно сортировать по столбцам, как в случае функций.
Запрос следующего вида:
SELECT * FROM calls
WHERE call.func().name() = "someFunc"
AND call.args() > 0
AND call.arg(0).isString()
Найдет все вызовы функции someFunc
где первый аргумент является строковым литералом.
Переменная call
через которую можно обратиться к текущему вызову имеет тип IFuncCall
и определяет следующие методы:
func
— возвращает структуру функции которая вызываетсяargs
— возвращает количество аргументовarg
— возвращает аргумент по индексу
Аргумент функции имеет тип IFuncArg
и определяет следующие методы:
string
— возвращает строковое представление аргументаisInt
— проверяет является ли аргумент целочисленным литераломisFloat
— проверяет является ли аргумент литералом с плавающей точкойisBool
— проверяет является ли аргумент булевым литераломisString
— проверяет является ли аргумент строковым литераломisConstant
— проверяет является ли аргумент константойisVariable
— проверяет является ли аргумент переменнойisExpression
— проверяет является ли аргумент выражением, которое не подпадает под условия выше
Используя методы выше можно очень гибко искать вызовы каких-либо функций.
Рассмотрим следующий запрос
SELECT deps FROM (
SELECT * FROM funcs WHERE func.name() = 'someFunc'
)
Этот запрос найдет все зависимости функции someFunc
от других функций на глубине 3 (по-умолчанию), в итоге мы получим некоторый граф в котором корневым элементом будет функция someFunc
, а от него будут идти узлы представляющие вызовы функций.
Когда мы запускаем запрос, мы можем указать программе создать не текстовое представление графа, а графическое, для этого нужно добавить флаг --graph
:
run --graph "graph" "SELECT deps FROM (SELECT * FROM funcs WHERE func.name() = 'someFunc')"
И передать ему имя файла в который будет записан граф.
Обратите внимание, что название файла (или путь) нужно писать в кавычках
Граф создается в формате SVG и может быть открыт в браузере, для удобства в него встроен скрипт для удобного скроллинга и перетаскивания.
Значение максимальной глубины по-умолчанию не всегда достаточно, для того, чтобы установить свою максимальную глубину добавьте выражение WITH
:
SELECT deps FROM (
SELECT * FROM funcs WHERE func.name() = 'someFunc'
) WITH depth=10
При большой глубине или если функция вызывает множество других функций граф может становится очень громоздким и неудобным в использовании. Чтобы исправить это ситуацию вы можете фильтровать пути которые будут отображены на графе.
Для этого используйте переменную path
в выражении WHERE
.
Например, мы хотим оставить только те пути в которых есть функция someFunc2
:
SELECT deps FROM (
SELECT * FROM funcs WHERE func.name() = 'someFunc'
) WHERE path.contains(
SELECT * FROM funcs
WHERE func.className() = ''
AND func.name() = 'someFunc2'
LIMIT 1
) WITH depth=10
Переменная path
имеет тип IFuncPath
и определяет следующие методы:
begin
— возвращает начальную функцию путиend
— возвращает конечную функцию путиlength
— возвращает длину путиat
— возвращает функцию по индексуcontains
— проверяет наличие переданных функций в пути