основан на использовании специальных кодов исходных и промежуточных данных
Низкоуровневый язык программирования
Иногда одно мнемоническое обозначение соответствует целой группе машинных команд, выполняющих одинаковое действие над разными ячейками памяти процессора. Кроме машинных команд языки программирования низкого уровня могут предоставлять дополнительные возможности, такие как макроопределения (макросы). При помощи директив есть возможность управлять процессом трансляции машинных кодов, предоставляя возможность заносить константы и литеральные строки, резервировать память под переменные и размещать исполняемый код по определенным адресам. Часто эти языки позволяют работать вместо конкретных ячеек памяти с переменными.
Как правило, использует особенности конкретного семейства процессоров. Общеизвестный пример низкоуровневого языка — язык ассемблера, хотя правильнее говорить о группе языков ассемблера. Более того, для одного и того же процессора существует несколько видов языка ассемблера. Они совпадают в машинных командах, но различаются набором дополнительных функций (директив и макросов).
Связанные понятия
В программировании, ассемблерной вставкой называют возможность компилятора встраивать низкоуровневый код, написанный на ассемблере, в программу, написанную на языке высокого уровня, например, Си или Ada. Использование ассемблерных вставок может преследовать следующие цели.
В приведённой ниже таблице отмечено наличие или отсутствие тех или иных возможностей в некоторых популярных сегодня языках программирования. Столбцы упорядочены по алфавиту. Если возможность в языке недоступна напрямую, но может быть эмулирована с помощью других средств, то в таблице отмечено, что её нет.
Промежуточный код
Байт-код или байтко́д (англ. byte-code ), иногда также используется термин псевдоко́д — машинно-независимый код низкого уровня, генерируемый транслятором и исполняемый интерпретатором. Большинство инструкций байт-кода эквивалентны одной или нескольким командам ассемблера. Трансляция в байт-код занимает промежуточное положение между компиляцией в машинный код и интерпретацией.
Байт-код называется так, потому что длина каждого кода операции — один байт, но длина кода команды различна. Каждая инструкция представляет собой однобайтовый код операции от 0 до 255, за которым следуют такие параметры, как регистры или адреса памяти. Это в типичном случае, но спецификация байт-кода значительно различается в языке.
Программа на байт-коде обычно выполняется интерпретатором байт-кода (обычно он называется виртуальной машиной, поскольку подобен компьютеру). Преимущество — в портируемости, т. е. один и тот же байт-код может исполняться на разных платформах и архитектурах. То же самое преимущество дают интерпретируемые языки. Однако, поскольку байт-код обычно менее абстрактный, более компактный и более «компьютерный», чем исходный код, эффективность байт-кода обычно выше, чем чистая интерпретация исходного кода, предназначенного для правки человеком. По этой причине многие современные интерпретируемые языки на самом деле транслируют в байт-код и запускают интерпретатор байт-кода. К таким языкам относятся PHP и JIT-компиляции. В стандарте открытых загрузчиков Open Firmware фирмы Sun Microsystems байт код представляет операторы языка Forth.
В то же время возможно создание процессоров, для которых данный байт-код является непосредственно машинным кодом (такие процессоры существуют, например, для Forth).
Также некоторый интерес представляет p-код (p-code), который похож на байт-код, но физически может быть менее лаконичным и сильно варьироваться по длине инструкции. Он работает на очень высоком уровне, например «напечатать строку» или «очистить экран». P-код повсеместно используется в СУБД и некоторых реализациях Паскаля.
Языки и среды программирования, использующие байткод
Полезное
Смотреть что такое «Промежуточный код» в других словарях:
п-код — Промежуточный язык, первоначально разработанный как выходной язык для Pascal USCD и ряда других языков в р технологии программирования. [Е.С.Алексеев, А.А.Мячев. Англо русский толковый словарь по системотехнике ЭВМ. Москва 1993] Тематики… … Справочник технического переводчика
Кодогенерация — Кодогенерация часть процесса компиляции, когда специальная часть компилятора, кодогенератор, конвертирует синтаксически корректную программу в последовательность инструкций, которые могут выполняться на машине. При этом могут применяться… … Википедия
C++ — У этого термина существуют и другие значения, см. C. См. также: Си (язык программирования) C++ Семантика: мультипарадигмальный: объектно ориентированное, обобщённое, процедурное, метапрограммирование Тип исполнения: компилируемый Появился в … Википедия
С++ — См. также: Си (язык программирования) C++ Семантика: мультипарадигмальный: объектно ориентированное, обобщённое, процедурное, метапрограммирование Тип исполнения: компилируемый Появился в: 1985 г. Автор(ы): Бьёрн Страуструп … Википедия
Интерпретатор — Эта статья включает описание термина «Интерпретация»; см. также другие значения. Интерпретатор программа (разновидность транслятора) или аппаратное средство, выполняющее интерпретацию.[1] Интерпретация пооператорный (покомандный,… … Википедия
Интерпретация (информатика) — Интерпретатор (языка программирования) Программа или техническое средство, выполняющее интерпретацию.[1] Вид транслятора, осуществляющего пооператорную (покомандную) обработку и выполнение исходной программы или запроса (в отличие от компилятора … Википедия
Транслятор — Эта статья о языках программирования; о естественных языках см.: Перевод. Эта статья включает описание термина «Трансляция»; см. также другие значения. Транслятор программа или техническое средство, выполняющее трансляцию программы.[1][2]… … Википедия
Портирование программного обеспечения — Для термина «Порт» см. другие значения. Портирование (англ. porting[1]) в программировании под портированием понимают адаптацию некоторой программы или её части, с тем чтобы она работала в другой среде, отличающейся от той среды, под… … Википедия
Переносимость — В программировании, под портированием понимают адаптацию некоторой программы или её части, с тем чтобы она работала в другой среде, отличающейся от той среды, под которую она была изначально написана. Процесс портирования также называют портингом … Википедия
Портинг — В программировании, под портированием понимают адаптацию некоторой программы или её части, с тем чтобы она работала в другой среде, отличающейся от той среды, под которую она была изначально написана. Процесс портирования также называют портингом … Википедия
Основан на использовании специальных кодов исходных и промежуточных данных
www.dbmsmag.com
Next-Generation Middleware
David S. Linthicum, senior manager with Ernst & Young’s Center for Technology Enablement in Vienna, Virginia
Сергей Кузнецов, Центр Информационных Технологий
Для правильной ориентации в растущем и усложняющемся рынке промежуточного ПО требуется, во-первых, понимание действующих стандартов. Во-вторых, нужно разобраться с категориями существующего промежуточного ПО и понять назначение продуктов каждой категории. Наконец, полезно познакомиться с существующими продуктами и правильно отнести их к соответствующей категории.
Что такое промежуточное ПО?
База стандартов
Одной из тенденций в мире современного промежуточного ПО является движение к стандартам, включая не только те, которые разрабатываются комитетами по стандартизации (например, CORBA), но и стандарты, предлагаемые мощными компаниями-производителями. В прошлом продукты промежуточного ПО основывались на частных предложениях, которые не предполагали возможности интероперабельности. Сегодня компании, производящие промежуточное ПО, учатся использовать стандарты, такие как CORBA или DCOM (Distributed Component Object Model) в качестве базовой модели продуктов.
DCOM служит стандартной базой в однородной среде Windows. Опора на DCOM позволяет приложениям, написанным на Visual Basic, Delphi и PowerBuilder, связываться по сети с аналогичными приложениями и использовать их сервисы с использованием механизма RPC. Таким образом, DCOM может использоваться либо как примитивный уровень промежуточного ПО, либо как инфраструктура для других продуктов. Продукты промежуточного ПО, использующие DCOM, включают монитор обработки транзакций Microsoft Transaction Server и Microsoft Message Queue Server (MSMQ). В MS Transaction Server для определения транзакций используется ActiveX, а взаимодействия с приложениями и серверами ресурсов основаны на DCOM. Основанным на DCOM MOM-продутом является Falcon. Компания Borland производит продукт Multitier Distributed Application Services Suite, многозвенный продукт промежуточного ПО, позволяющий строить распределенные приложения с использованием основанных на DCOM брокеров объектных заявок.
Типы промежуточного ПО
Предлагается деление продуктов промежуточного ПО на пять категорий: продукты, ориентированные на базы данных; виртуальные системы; промежуточное звено (middle-tier); шлюзы; продукты, ориентированные на Web.
Продукты промежуточного ПО, ориентированные на базы данных
К этой категории относятся продукты, позволяющие приложениям производить доступ к локальным или удаленным базам данных. Идея заключается в том, чтобы создать API для доступа к базам данных с использованием слоя промежуточного ПО, скрывающего от клиента особенности операционной системы и сети. Во многих случаях от разработчика скрыт даже и API, а доступны только функции средства разработки. Например, в мире систем «клиент-сервер» ориентированное на базы данных промежуточное ПО является встроенным. При использовании PowerBuilder можно применять собственные связи продукта, существующие для большинства популярных СУБД, а можно работать с ODBC. Почти во все средства разработки компании Borland встроен BDE со своими собственными средствами доступа к базам данных, но также поддерживается и ODBC.
OLE-DB обеспечивает единую точку доступа к нескольким базам данных. Задача разработки OLE-DB состояла в обеспечении автоматизированного средствами OLE доступа к любому числу баз данных за счет добавления слоя COM между приложением и базой данных.
Имеются и независимые от средств разработки ориентированные на базы данных продукты промежуточного ПО. Например, продукт DB Tools.h++ компании Rogue Wave Software позволяет связать с базами данных большинство приложений, написанных на языке Си++. DB Tools.h++ дает возможность представить реляционные таблицы и атрибуты как собственные объекты Си++. Для тех, кому ближе язык Java, Rogue Wave предлагает Java-версию продукта под названием JDBTools, обеспечивающую доступ к базам данных непосредственно из Java-апплетов и приложений. Аналогичный продукт Persistence компании Persistence Software Inc. позволяет создавать оболочку реляционной базы данных, создающую ее объектное представление, которое может использоваться в объектно-ориентированной среде разработки.
Если требуется доступ к унаследованным данным или к данным, хранящимся на нескольких машинах, следует обратить внимание на такие продукты переднего края как EDA/SQL компании Information Builders Inc. (IBI). Подход, положенный в основу EDA/SQL, состоит в том, чтобы поддерживать максимально возможное число операционных систем, сетей и баз данных. Например, можно получить доступ к информации из базы данных, управляемой Rdb на платформе DEC, и к базе данных, управляемой DB2 на мейнфрейме, используя один драйвер ODBC на стороне клиента. Подобного рода продукты полезны для организаций, желающих перейти к использованию архитектуры «клиент-сервер» без отказа от использования критичных для бизнеса унаследованных систем.
Виртуальные системы
Промежуточное ПО категории виртуальных систем позволяет разработчикам иметь дело со многими различными системами так, как если бы это была одна система, с использованием общего слоя промежуточного ПО и набора API. На каждой системе устанавливается соответствующая версия промежуточного ПО и конфигурируется служба именования. После этого сервисы разнородной распределенной системы становятся доступными всем приложениям в сети. Из одного клиентского приложения с использованием API можно запустить процесс на мейнфрейме, обновить базу данных и затребовать сервис на UNIX-системе.
Хорошим примером промежуточного ПО этой категории является продукт DCE, в котором поддерживается основанный на RPC доступ к разнообразным системам, для которых поддерживается DCE. Помимо прочего, DCE обеспечивает собственные службы безопасности и именования и позволяет взаимодействовать с любым числом серверов ресурсов с использованием шлюзов и интерфейсов. Однако DCE не может удовлетворить требования эффективности разработчиков клиент-серверных систем, для которых важна высокая загрузка серверов и сети. Более того, применяемый механизм синхронных RPC требует, чтобы для выполнения операции все участвующие системы были активны. Обычно выполнение удаленного вызова должно быть полностью завершено перед тем, как приложение сможет продолжить свое выполнение. DCE продается компаниями Transarc Corp. (подразделение IBM) и IBM Corp..
В конце 1997 г. на рынке MOM-продуктов ожидается сражение между IBM и Microsoft. Обе компании летом готовились к битве в связи с выпуском новых версий MOM-продуктов. IBM выпустит новую версию MQSeries под названием Armada, в которой будут поддерживаться дружественный пользователю интерфейс, улучшенные средства конфигурирования и управления, повышенная производительность, возможность использования Java, развитые средства безопасности и интеграция с DCE. Кроме того, IBM планирует снизить цены. В новой версии MSMQ будет использоваться технология COM, и она будет работать на платформах Windows NT и Windows 95 в сетях TCP/IP и IPX/SPX. Первый выпуск MSMQ будет входить в состав сервера NT и Transaction Server. Партнер Microsoft компания Level8 предоставит шлюзы для связи MSMQ с MQSeries и производимыми не Microsoft операционными системами. Компании Software AG и DEC ожидают получить кросс-платформенные реализации MSMQ в конце этого года.
При всех своих достоинствах подход MOM страдает отсутствием стандартов и отсутствием поддержки производителей популярных средств разработки. При использовании MOM в сочетании с традиционными средствами разработки для среды «клиент-сервер» потребуется использовать различные DLL, средства ActiveX или производить интеграцию с библиотекой классов средства разработки. Из-за этого разработчикам приходится отказываться от традиционной парадигмы основанной на репозитарии разработки при использовании таких инструментальных средств как PowerBuilder компании PowerSoft (подразделение компании Sybase Inc.) или Developer/2000 компании Oracle. Не очень быстро, но появляются средства, подобные Allegris компании Intersolve, поддерживающие взаимодействия прикладных объектов внутри системы.
Компания TIBCO Inc. недавно объявила о выпуске MOM-продукта TIB/Rendezvous 3.0. В этом продукте обеспечивается интеграция ActiveX и Java, удостоверяемая доставка сообщений, устойчивость к сбоям за счет использования механизма подписного широковещания. Основанный на технологии ORB TID/Rendezvous представляет наибольшую значимость для финансовых приложений, в которых требуется доставка объемной информации сразу многим клиентам. При использовании технологии подписки и доставки каждое сообщение передается по сети только один раз при том, что его получает каждый подписчик.
Мощность TP-мониторов заключается в том, что они позволяют разработчикам оформить части приложения в виде транзакции. У транзакции имеются четкие точки начала и завершения. Если при выполнении транзакции возникает сбойная ситуация, монитор может выполнить откат этой транзакции, не оставляя систему в нестабильном или несогласованном состоянии. Кроме того, мониторы транзакций в состоянии мультипликсировать запросы к базам данных. Поскольку клиент вызывает транзакции и не связан напрямую с базой данных, монитор транзакций в состоянии пропускать разные запросы через одно подключение к базе данных. Например, для 100 клиентов может понадобиться только 10 активных подключений к базе данных. Тем самым удаляется ограничение, свойственное двухзвенным организациям «клиент-сервер», когда для каждого клиента требуется отдельное подключение к базе данных. В дополнение к этому, TP-мониторы в пределах одной транзакции могут выбирать и обновлять данные в разнородных базах данных и даже поддерживать их совместную целостность. Например, одна транзакция может удалить запись из базы данных, управляемой Oracle в среде Unix, и обновить запись в базе данных DB2 на мейнфрейме. Тем самым, TP-мониторы полезны для связывания различных унаследованных систем и баз данных в общую виртуальную систему.
Основанное на ORB промежуточное ПО включает простые брокеры объектных заявок, существующие на нескольких машинах и взаимодействующие на основе общего протокола, такого как IIOP. Разработчики могут встраивать ORB’ы или программы, дающие доступ к ORB, в свои приложения и взаимодействовать через общий интерфейс с ORB’ами (приложениями), существующими в других системах. К коммерческим ORB, базирующимся на CORBA, относятся Orbix компании Iona и VisiBroker компании Visigenic. Сила этого подхода состоит в строгой поддержке межплатформенных взаимодействий. Однако строить такие распределенные приложения сложно, и лишь немногие инструментальные средства напрямую поддерживают такие разработки. Кроме того, ORB’ы не обеспечивают средств балансировки загрузки и восстановления после сбоев.
Среднее звено
Появляющиеся технологии
Что же будет дальше в мире промежуточного ПО? Коротко говоря, Web, распределенные объекты, Web, Java и снова Web. Появление Web-технологии применительно и к Internet, и к intranet дало новую жизнь бизнесу промежуточного ПО. В то время, как компании начинают связывать свои базы данных и другие ресурсы с Web, производители промежуточного ПО получают удачную возможность создать продукты, облегчающие этот процесс. Ирония ситуации состоит в том, что использование браузера в качестве общей платформы приложений «клиент-сервер» и HTTP в качестве общего промежуточного ПО снижает интерес к промежуточному ПО на стороне клиента. Анализируя возможности Web-технологии, нужно понимать, что наибольшая выгода от промежуточного ПО может быть получена на стороне сервера.
Криком моды являются клиентские Web-продукты промежуточного ПО. Эти продукты позволяют разработчикам связывать с удаленными базами данных выполняемые на стороне клиента Java-апплеты и программные компоненты ActiveX. Среди первых на рынке появился продукт JETConnect компании XDB Systems Inc. JETConnect дает возможность разработчикам подключаться к удаленным серверам баз данных на основе использования входящей в состав продукта библиотеки классов. С появлением JDBC разработчики, применяющие Java, получили стандартный механизм, дающий те же возможности, что и JETConnect. Сегодня JDBC-драйверы имеются для большинства популярных баз данных. Возможности JDBC встраиваются в средства разработки; примером такого продукта является JBuilder компании Borland. Во многих отношениях JDBC похож на ODBC, и те компании, которые создавали драйверы ODBC, найдут свою нишу на новом рынке драйверов JDBC. В некоторых отношениях мир Java будет выглядеть и функционировать очень похоже на то, что сегодня представляет собой мир традиционного двухзвенного клиент-серверного промежуточного ПО.
Производители промежуточного ПО вносят свой вклад и в развитие технологии Web-разработок. Например, компания Open Horison Inc. только что объявила о выпуске продукта Ambrosia, представляющего собой управляемую событиями систему для разработки бизнес-приложений в Internet. В системе используется собственная реализация Java с гарантированной доставкой сообщений, исчерпывающей безопасностью и транзакционными возможностями.
Организация хранения промежуточных данных
Не могу сказать, как часто вопросы такого рода возникают перед разработчиками, но мне он встретилась и вот как я на него ответил.
Была поставлена задача реализовать обработку для сверки данных, которые присылал внешний контрагент, с данными, которые хранятся во внутренней учетной системе. Информация от контрагента поступала в виде таблицы в формате Excel. При этом часть активов однозначно идентифицировалась по уникальному коду, а для других могли быть разночтения. По этой причине процедура сверки состояла из нескольких этапов. На первом происходило чтение исходного файла и заполнялась таблица соответствия активов, на втором этапе пользователь выставлял соответствие между внешними и внутренними активами, для которых оно не было найдено и на заключительном этапе происходило построение отчета. Приступив к работе, я обнаружил, что мне надо организовать промежуточное хранение данных из внешнего файла, которые я читаю на первом шаге. То есть, на первом шаге я получаю некую таблицу значений, которую затем использую для построения итогового отчета. Вот эту таблицу мне и надо было хранить. Для этой цели я создал реквизит формы ДанныеСпецДепа c типом ТаблицаЗначений. Состав колонок данного реквизита изначально неизвестен, мы можем его получить только после того, как прочитаем внешний файл.
Теперь немного кода. У формы есть команда Load (загрузить). Она выполняет следующие действия.
Файл помещается во временной хранилище на сервере, затем вызывается обработчик оповещения. Теперь приведем код процедуры LoadНаСервере.
Пояснение к приведенному фрагменту кода. На первом шаге вызывается процедура из модуля объекта, в которой происходит чтение исходного файла и формирование таблицы значений с данными. На втором шаге состав колонок реквизита формы ДанныеСпецДепа делается аналогичным составу колонок таблицы vt. На заключительном шаге мы копируем таблицу vt в реквизит формы ДанныеСпецДепа. Теперь эти данные можно использовать при построении итогового отчета. Задача промежуточного хранения решена. Разумеется, изложенное решение не является единственным. Поэтому статья и размещена в категории «Теория программирования».
Использование машинного обучения в статическом анализе исходного кода программ
Машинное обучение плотно укоренилось в различных сферах деятельности людей: от распознавания речи до медицинской диагностики. Популярность этого подхода столь велика, что его пытаются использовать везде, где только можно. Некоторые попытки заменить классические подходы нейросетями оканчиваются не столь уж успешно. Давайте взглянем на машинное обучение с точки зрения задач создания эффективных статических анализаторов кода для поиска ошибок и потенциальных уязвимостей.
Команду PVS-Studio часто спрашивают, не хотим ли мы начать использовать машинное обучение для нахождения ошибок в исходном коде программ. Короткий ответ: да, но очень ограниченно. Мы считаем, с применением машинного обучения в задачах анализа кода скрывается много подводных камней. Во второй части статьи мы расскажем про них. А начнём с обзора новых решений и идей.
Новые подходы
В настоящее время существует уже множество реализаций статических анализаторов, основанных на или использующих машинное обучение, в том числе — глубокое обучение и NLP для обнаружения ошибок. На потенциал машинного обучения при поиске ошибок обратили внимание не только энтузиасты, но и крупные компании, например, Facebook, Amazon или Mozilla. Некоторые проекты не являются полноценными статическими анализаторами, а лишь промежуточно находят некоторые определенные ошибки при коммитах.
Интересно, что почти все позиционируются как game-changer продукты, которые с помощью искусственного интеллекта изменят процесс разработки.
Рассмотрим некоторые известные примеры:
DeepCode
Deep Code — инструмент поиска уязвимостей в коде программ, написанных на Java, JavaScript, TypeScript и Python, в котором машинное обучение присутствует в качестве компонента. По заявлению Бориса Паскалева, уже работает более 250 тысяч правил. Этот инструмент обучается на основе изменений, вносимых разработчиками в исходный код открытых проектов (миллион репозиториев). Сама компания говорит, что их проект — это Grammarly для разработчиков.
По сути этот анализатор сравнивает ваше решение со своей базой проектов и предлагает вам предполагаемое наилучшее решение из опыта других разработчиков.
В мае 2018 года разработчики писали, что готовится поддержка языка C++, однако до сих пор этот язык не поддерживается. Хотя на самом сайте и указано, что добавление нового языка можно произвести за считанные недели, благодаря тому, что от языка зависим лишь один этап — парсинг.
Также на сайте опубликована группа публикаций о методах, на которых основан анализатор.
Infer
Фейсбук достаточно широко старается внедрить новые подходы в своих продуктах. Не обошли они своим вниманием и машинное обучение. В 2013 году они купили стартап, который разрабатывал статический анализатор, основанный на машинном обучении. А в 2015 году исходный код проекта стал открытым.
Infer — статический анализатор для проектов, написанных на языках Java, C, C++, и Objective-C, разрабатываемый Фейсбуком. Согласно сайту, он также используется в Amazon Web Services, Oculus, Uber и других популярных проектах.
В настоящее время Infer способен находить ошибки, связанные с разыменовыванием нулевого указателя, утечками памяти. Infer основан на логике Хоара, separation logic и bi-abduction, а также на теории абстрактной интерпретации (abstract interpretation). Использование этих подходов позволяет анализатору разбивать программу на малые блоки (chunks) и анализировать их независимо друг от друга.
Вы можете попробовать использовать Infer и на своих проектах, однако разработчики предупреждают, что, хотя на проектах Facebook полезные срабатывания составляют 80% результатов, на других проектах низкое число ложных срабатываний не гарантируется. Некоторые из ошибок, которые Infer пока что не может находить, но разработчики работают над введением таких срабатываний:
SapFix
SapFix является автоматизированным инструментом внесения правок. Он получает информацию от Sapienz, инструмента для автоматизации тестирования, и статического анализатора Infer, и на основании последних внесенных изменений и сообщениях Infer выбирает одну из нескольких стратегий для исправления ошибок.
В части случаев SapFix откатывает все изменения или их часть. В других случаях он пытается разрешить проблему, генерируя патч из своего набора шаблонов фиксов. Этот набор формируется из шаблонов правок, собранных самими программистами из набора уже произведенных когда-то правок. Если такой шаблон не исправит ошибку, SapFix пытается подстроить шаблон под ситуацию, производя малые модификации в абстрактном синтаксическом дереве, пока потенциальное решение не будет найдено.
Но одного потенциального решения недостаточно, поэтому SapFix собирает несколько решений, которые отбираются на основе трех вопросов: есть ли ошибки компиляции, остается ли падение, не вносит ли правка новых падений. После того, как правки будут полностью протестированы, патчи отправляются на ревью программисту, который примет решение, какая из правок наилучшим образом разрешает проблему.
Embold
Embold – стартап-платформа для статического анализа исходного кода программ, которая до переименования носила имя Gamma. Статический анализ производится на основе собственных диагностик, а также на базе встроенных анализаторов, таких как Cppсheck, SpotBugs, SQL Check и других.
Кроме самих диагностик акцент делается на возможности наглядного отображения инфографики по нагруженности кодовой базы и удобного просмотра найденных ошибок, а также поиска возможности рефакторинга. Кроме того, в этом анализаторе есть набор анти-паттернов, который позволяет обнаруживать проблемы в структуре кода на уровне классов и методов, и различные метрики для расчета качества системы.
Одним из основных преимуществ заявляется интеллектуальная система предложения решений и правок, которая, в дополнение к обычным диагностикам, проверяет правки на основе информации о предыдущих изменениях.
С помощью NLP Embold разбивает код на части и ищет между ними взаимосвязи и зависимости между функциями и методами, что позволяет сэкономить время рефакторинга.
Таким образом, Embold в основном предлагает удобную визуализацию результатов анализа вашего исходного кода различными анализаторами, а также собственными диагностиками, часть которых основана на машинном обучении.
Source
Source
Свой подход к анализу кода с помощью машинного обучения они основывают на Natural Hypothesis, сформулированной в статье «On the Naturalness of Software».
«Языки программирования, в теории, являются сложными гибкими и мощными, но программы, которые на самом деле пишут реальные люди, в большинстве своем простые и достаточно повторяющиеся, и, следовательно, в них присутствуют полезные и предсказуемые статистические свойства, которые можно выразить в статистических языковых моделях и использовать для задач разработки программного обеспечения.»
Исходя из этой гипотезы, чем больше кодовая база для обучения анализатора, тем сильнее выделяются статистические свойства и тем точнее будут достигнутые через обучение метрики.
Для анализа кода в source
Однако source
Обучение ориентируется на несколько основных элементов: пробелы, табуляция, переносы строки и т.д.
В целом, source
Clever-Commit
Clever-Commit – анализатор, созданный Mozilla в сотрудничестве с Ubisoft. Он основан на исследовании CLEVER (Combining Levels of Bug Prevention and Resolution Techniques), проведенном Ubisoft, и основанном на нем продукте Commit Assistant, который выявляет подозрительные коммиты, скорее всего содержащие ошибку. Благодаря тому, что CLEVER основан на сравнении кода, он не только указывает на опасный код, но и делает предложения о возможных правках. Согласно описанию, в 60-70% случаев Clever-Commit находит проблемные места и с той же частотой предлагает для них верные правки. В целом, информации об этом проекте и об ошибках, которые он способен находить, немного.
CodeGuru
А ещё совсем недавно список анализаторов, использующих машинное обучение, пополнился продуктом от Amazon под названием CodeGuru. Это сервис, основанный на машинном обучении, который позволяет находить ошибки в коде, а также выявлять в нем затратные участки. Пока что анализ есть только для Java кода, но пишут о поддержке и других языков в будущем. Хотя он и был анонсирован совсем недавно, СЕО AWS (Amazon Web Services) Энди Джасси говорит, что он уже долгое время используется в самом Амазоне.
На сайте сказано, что обучение проводилось на кодовой базе самого Амазона, а также на более чем 10 000 проектах с открытым исходным кодом.
По сути сервис разделен на две части: CodeGuru Reviewer, обученный с помощью поиска ассоциативных правил и ищущий ошибки в коде, и CodeGuru Profiler, занимающийся мониторингом производительности приложений.
В целом, информации об этом проекте опубликовано не так уж и много. На сайте сказано, что чтобы обучиться ловить отклонения от «лучших практик», Reviewer анализирует кодовые базы Амазона и ищет в них пулл-реквесты, содержащие вызовы API AWS. Далее он просматривает внесенные изменения и сравнивает их с данными из документации, которая анализируется параллельно. В результате получается модель «лучших практик».
Также сказано, что рекомендации для пользовательского кода улучшаются после получения фидбэка по рекомендациям.
Наш скептицизм
Теперь давайте посмотрим на задачу поиска ошибок глазами нашей команды, которая занимается разработкой статических анализаторов много лет. Мы видим ряд высокоуровневых проблем применения обучения, о которых и хотим рассказать. Но в начале грубо разделим все ML подходы на два типа:
Примечание. Мы смотрим с позиции разработки универсального статического анализатора общего назначения. Мы нацелены на разработку анализатора, ориентированного не на конкретную кодовую базу, а который может использовать любая команда в любом проекте.
Обучение статического анализатора «вручную»
Допустим, мы хотим использовать ML для того, чтобы анализатор начал искать в коде аномалии следующего вида:
Странно сравнивать переменную саму с собой. Мы можем написать множество примеров корректного и некорректного кода и обучить анализатор искать такие ошибки. Дополнительно, можно добавить в тесты реальные примеры уже найденных ошибок. Вопрос, конечно, где эти примеры взять. Но будем считать, что это возможно. Например, у нас накопилось некоторое количество примеров таких ошибок: V501, V3001, V6001.
Итак, можно ли искать такие дефекты в коде, используя алгоритмы машинного обучения? Можно. Только непонятно, зачем это делать!
Смотрите, чтобы обучить анализатор нам потребуется потратить много сил на подготовку примеров для обучения. Или размечать код реальных приложений, указывая, где надо ругаться, а где нет. В любом случае потребуется проделать очень большую работу, так как примеров для обучения должны быть тысячи. Или десятки тысяч.
Ведь мы хотим искать не только случаи (A == A), но и:
А теперь давайте посмотрим, как была бы реализована такая простейшая диагностика в PVS-Studio:
И всё. Не нужно никакой базы примеров для обучения!
В дальнейшем диагностику следует научить учитывать ряд исключений и понимать, что надо ругаться на (A[0] == A1). Однако всё это очень легко программируется. А вот как раз с базой примеров для обучения всё будет обстоять плохо.
Отметим, что в обоих случаях ещё потребуется система тестов, написание документации и так далее. Однако трудозатраты по созданию новой диагностики явно на стороне классического подхода, где правило просто жёстко программируется в коде.
Итак, мы хотим искать вызовы таких функций, где результат их работы не используется. Для этого можно сгенерировать множество тестов. И думаем, всё будет хорошо работать. Вот только опять непонятно, зачем это нужно.
Реализация диагностики V530 со всеми исключениями в анализаторе PVS-Studio составляет 258 строк кода, из которых 64 строки являются комментариями. Плюс есть таблица с аннотациями функций, где отмечено, что их результат должен использоваться. Пополнять эту таблицу намного проще, чем создавать синтетические примеры.
Ещё хуже дело будет обстоять с диагностиками, в которых используется анализ потока данных. Например, анализатор PVS-Studio умеет отслеживать значение указателей, что позволяет найти вот такую утечку памяти:
Пример взят из статьи «Chromium: утечки памяти». Если выполнится условие (pkey.n0inv == 0), то происходит выход из функции без освобождения буфера, указатель на который хранится в переменной n.
С точки зрения PVS-Studio здесь нет ничего сложного. Анализатор изучил функцию BnNew и запомнил, что она возвращает указатель на блок выделенной памяти. В другой функции он заметил, что возможна ситуация, когда буфер не освобождается, а указатель на него теряется в момент выхода из функции.
Работает общий алгоритм отслеживания значений. Не важно, как написан код. Не важно, что ещё есть в функции, не относящееся к работе с указателями. Алгоритм универсален и диагностика V773 находит массу ошибок в различных проектах. Посмотрите сколь различны фрагменты кода, где выявлены ошибки!
Мы не являемся знатоками машинного обучения, но, кажется, здесь будут большие проблемы. Есть невероятное количество способов, как можно написать код с утечками памяти. Даже если машина будет обучена отслеживать значение переменных, потребуется обучить её понимать, что бывают вызовы функций.
Есть подозрение, что потребуется столь большое количество примеров для обучения, что задача становится грандиозной. Мы не говорим, что она нереализуема. Мы сомневаемся, что затраты на создание анализатора окупят себя.
Аналогия. На ум приходит аналогия с калькулятором, где вместо диагностик надо программировать арифметические действия. Уверены, можно научить калькулятор на базе ML хорошо складывать числа, введя в него базу знаний про результат операций 1+1=2, 1+2=3, 2+1=3, 100+200=300 и так далее. Как вы понимаете, целесообразность разработки такого калькулятора под большим вопросом (если под это не выделен грант :). Намного более простой, быстрый, точный и надёжный калькулятор можно написать, используя в коде обыкновенную операцию «+».
Вывод. Способ будет работать. Но использовать его, на наш взгляд, не имеет практического смысла. Разработка будет более трудоёмкой, а результат менее надёжен и точен, особенно если речь пойдёт о реализации сложных диагностик, основанных на анализе потока данных.
Обучение на большом количестве открытого исходного кода
Хорошо, с ручными синтетическими примерами мы разобрались, но ведь есть GitHub. Можно отследить историю коммитов и вывести закономерности изменения/исправления кода. Тогда можно указать не только на участки подозрительного кода, но даже, возможно, предложить способ его исправления.
Если остановиться на этом уровне детализации, то всё выглядит хорошо. Дьявол же, как всегда, кроется в деталях. Давайте как раз и поговорим про эти детали.
Первый нюанс. Источник данных.
Правки на GitHub достаточно хаотичны и разнообразны. Люди часто ленятся делать атомарные коммиты и вносят в код одновременно сразу несколько правок. Сами знаете, как это бывает: поправили ошибку, а заодно и порефакторили немножко («А вот тут ещё заодно добавлю обработку такого случая. »). Даже человеку потом может быть непонятно, связаны эти правки между собой, или нет.
Встаёт задача, как отличить собственно ошибки от добавления новой функциональности или чего-то ещё. Можно, конечно, посадить 1000 человек вручную размечать коммиты. Люди должны будут указать, что вот здесь ошибку поправили, здесь рефакторинг, здесь новая функциональность, здесь требования поменялись и так далее.
Возможна такая разметка? Возможна. Но обратите внимание, как быстро происходит подмена. Вместо «алгоритм научится сам на базе GitHub» мы уже обсуждаем как озадачить сотни человек на продолжительное время. Трудозатраты и стоимость создания инструмента резко возрастают.
Можно попытаться выявить автоматически, где именно исправлялись ошибки. Для этого следует анализировать комментарии к коммитам, обращать внимание на небольшие локальные правки, которые, скорее всего, являются именно правкой ошибки. Затрудняюсь сказать, насколько хорошо можно автоматически искать правки ошибок. В любом случае, это большая задача, требующая отдельного исследования и программирования.
Итак, мы ещё не добрались до обучения, а уже есть нюансы :).
Второй нюанс. Отставание в развитии.
Анализаторы, которые будут обучаться на основании таких баз, как GitHub, всегда будут подвержены такому синдрому, как «задержка психического развития». Это связано с тем, что языки программирования изменяются со временем.
В C# 8.0 появились Nullable Reference типы, помогающие бороться с Null Reference Exception’ами (NRE). В JDK 12 появился новый оператор switch (JEP 325). В C++17 появилась возможность выполнять условные конструкции на этапе компиляции (constexpr if). И так далее.
Языки программирования развиваются. Причем такие, как C++, очень быстро и активно. В них появляются новые конструкции, добавляются новые стандартные функции и так далее. Вместе с новыми возможностями появляются и новые паттерны ошибок, которые тоже хотелось бы выявлять с помощью статического анализа кода.
И здесь у рассматриваемого метода обучения проблема: паттерн ошибки может быть уже известен, есть желание его выявлять, но учить ещё не на чем.
Давайте рассмотрим эту проблему на каком-то конкретном примере. В C++11 появился Range-based for loop. И можно написать следующий код, перебирающий все элементы в контейнере:
Новый цикл принёс с собой и новый паттерн ошибки. Если внутри цикла изменить контейнер, то это приведёт к инвалидации «теневых» итераторов.
Рассмотрим следующий некорректный код:
Компилятор превратит его в нечто подобное:
При операции push_back может произойти инвалидация итераторов __begin и __end, если произойдёт реаллокация памяти внутри вектора. Результатом будет неопределённое поведение программы.
Итак, паттерн ошибки давно известен и описан в литературе. Анализатор PVS-Studio диагностирует его с помощью диагностики V789 и уже находил реальные ошибки в открытых проектах.
Как скоро на GitHub наберётся достаточно нового кода, чтобы заметить такую закономерность? Хороший вопрос… Надо понимать, что если появился range-based for loop, это не означает, что все программисты сразу начали его массово использовать. Могут пройти годы, прежде чем появится много кода, использующего новый цикл. Более того, должно быть совершено множество ошибок, а потом они должны быть поправлены, чтобы алгоритм смог заметить закономерность в правках.
Сколько должно пройти лет? Пять? Десять?
Десять — это слишком много, и мы пессимисты? Отнюдь. К моменту написания статьи прошло уже восемь лет, как в C++11 появился range-based for loop. Но пока в нашей базе выписано только три случая такой ошибки. Три ошибки — это не много и не мало. Из их количества не следует делать какой-то вывод. Главное, можно подтвердить, что такой паттерн ошибки реален и есть смысл его обнаруживать.
Теперь сравним это количество, например, вот с этим паттерном ошибки: указатель разыменовывается до проверки. Всего при проверке open-source проектов мы уже выявили 1716 подобных случаев.
Возможно, вообще не стоит искать ошибки range-based for loop? Нет. Просто программисты инерционны, и этот оператор очень неспеша приобретает популярность. Постепенно кода с его участием станет много, и ошибок, соответственно, тоже станет больше.
Произойдёт это, по всей видимости, лишь спустя 10-15 лет с момента появления C++11. И теперь философский вопрос. Уже зная паттерн ошибки, мы будем просто ждать многие годы, пока накопится много ошибок в открытых проектах?
Если ответ — «да», то тогда можно заранее обоснованно поставить всем анализаторам на базе ML диагноз «задержка психического развития».
Если ответ — «нет», то как быть? Примеров нет. Писать их вручную? Но тогда мы возвращается к предыдущей главе, где мы рассматривали написание человеком множества примеров для обучения.
Такое можно сделать, но вновь встаёт вопрос целесообразности. Реализация диагностики V789 со всеми исключениями в анализаторе PVS-Studio составляет всего 118 строк кода, из которых 13 строк являются комментариями. Т.е. это очень простая диагностика, которую можно легко взять и запрограммировать классическим способом.
Аналогичная ситуация будет с любыми другими нововведениями, появляющимися в любых других языках. Как говорится, есть над чем подумать.
Третий нюанс. Документация.
Важной составляющей любого статического анализатора является документация, описывающая каждую диагностику. Без неё использовать анализатор будет крайне сложно или вообще невозможно. В документации к PVS-Studio у нас есть описание каждой диагностики, где приводится пример ошибочного кода и как его исправить. Также приводится ссылка на CWE, где можно прочитать альтернативное описание проблемы. И всё равно, иногда пользователям бывает что-то непонятно, и они задают нам уточняющие вопросы.
В случае статических анализаторов, которые строятся на алгоритмах машинного обучения, вопрос документации как-то замалчивается. Предполагается, что анализатор просто укажет на место, которое кажется ему подозрительным и, возможно, даже предложит как его исправить. Решение вносить правку или нет остаётся за человеком. И во тут… кхм… Непросто принять решение, не имея возможности прочитать, на основании чего анализатору кажется подозрительным то или иное место в коде.
Конечно, в ряде случаев всё будет очевидно. Предположим, анализатор укажет вот на этот код:
И предложит заменить его на:
Сразу понятно, что программист опечатался и прибавил 1 не туда, куда надо. В результате чего памяти будет выделено меньше необходимого.
Тут и без документации всё понятно. Однако так будет далеко не всегда.
Представьте, что анализатор «молча» указывает на этот код:
И предлагает изменить тип возвращаемого значения с сhar на int:
Документации для предупреждения нет. Да и видимо самого текста у предупреждения, как мы понимаем, тоже не будет, если мы говорим про полностью самостоятельный анализатор.
Что делать? В чём разница? Стоит ли делать такую замену?
В принципе, здесь можно будет рискнуть и согласиться исправить код. Хотя соглашаться на правки без их понимания, это так себе практика… 🙂 Можно заглянуть в описание функции memcmp и прочитать, что функция действительно возвращает значения типа int: 0, больше нуля и меньше нуля. Но всё равно может быть непонятно, зачем вносить правки, если код сейчас и без того успешно работает.
Теперь, если вы не знаете, в чём смысл такой правки, ознакомьтесь с описанием диагностики V642. Сразу становится понятным, что это самая настоящая ошибка. Более того, она может стать причиной уязвимости.
Возможно, пример показался неубедительным. Ведь анализатор предложил код, который, скорее всего, будет лучше. Ok. Давайте рассмотрим другой пример псевдокода, на этот раз, для разнообразия, на языке Java.
Есть какой-то объект. Он сериализуется. Затем состояние объекта меняется, и он вновь сериализуется. Вроде всё хорошо. Теперь представим, что анализатору, вдруг, этот код не нравится, и он предлагает заменить его на:
Вместо изменения объекта и его повторной записи, создаётся новый объект и уже он сериалиузется.
Описания проблемы нет. Документации нет. Код стал длиннее. Зачем-то добавлено создание нового объекта. Вы готовы сделать в своём коде такую правку?
Вы скажете, что непонятно. Действительно, непонятно. И так будет непонятно постоянно. Работа с таким «молчаливым» анализатором станет бесконечным исследованием в попытке понять, почему анализатору что-то не нравится.
Если же есть документация, то всё становится прозрачно. Класс java.io.ObjectOuputStream, который используется для сериализации, кэширует записываемые объекты. Это означает, что один и тот же объект не будет сериализован дважды. Один раз класс сериализует объект, а во второй раз просто запишет в поток ссылку на тот же самый первый объект. Подробнее: V6076 — Recurrent serialization will use cached object state from first serialization.
Надеемся, нам удалось объяснить важность наличия документации. А теперь вопрос. Как появится документация для анализатора, построенного на базе ML?
Когда разрабатывается классический анализатор кода, то здесь всё просто и понятно. Есть некий паттерн ошибок. Мы его описываем в документации и реализуем диагностику.
В случае ML всё наоборот. Да, анализатор может заметить аномалию в коде и указать на неё. Но он ничего не знает о сути дефекта. Он не понимает и не расскажет, почему же так нельзя писать код. Это слишком высокоуровневые абстракции. Тогда уж анализатору надо заодно научиться читать и понимать документацию для функций.
Как я уже сказал, поскольку тема документации обходится в статьях по машинному обучению, мы не готовы рассуждать дальше. Просто ещё один большой нюанс, который мы вынесли на обозрение.
Примечание. Можно возразить, что документация не обязательна. Анализатор может привести отсылки к множеству примеров исправления на GitHub и человек, просматривая коммиты и комментарии к ним, разберётся что к чему. Да, это так. Но идея не выглядит привлекательной. Анализатор по-прежнему вместо помощника выступает в качестве инструмента, который ещё больше будет озадачивать программиста.
Четвёртый нюанс. Узкоспециализированные языки.
Описываемый подход не применим для узкоспециализированных языков, для которых статический анализ также может быть крайне полезен. Причина в том, что на GitHub и других источниках просто не наберётся достаточно большой базы исходных кодов, чтобы проводить эффективное обучение.
Рассмотрим это на конкретном примере. Для начала зайдём на GitHub и выполним поиск репозиториев для популярного языка Java.
Результат: language:«Java»: 3,128,884 available repository results
Теперь возьмём специализированный язык «1C Enterprise», используемый в бухгалтерских приложениях, выпускаемых российской компанией 1C.
Результат: language:«1C Enterprise»: 551 available repository results
Быть может анализаторы для этого языка не нужны? Нужны. Имеется практическая потребность в анализе таких программ и уже существуют соответствующие анализаторы. Например, существует SonarQube 1C (BSL) Plugin, выпускаемый компанией «Серебряная Пуля».
Думаю, каких-то специальных пояснений не требуется, почему подход с машинным обучением будет затруднителен для специализированных языков.
Пятый нюанс. C, C++, #include.
Статьи, посвящённые исследованиям в области статического анализа кода на базе ML, тяготеют к таким языкам, как Java, JavaScript, Python. Объясняют это их крайней популярностью. А вот языки C и C++ как-то обходят стороной, хотя их точно нельзя назвать непопулярными.
У нас есть гипотеза, что дело не в популярности/перспективности, а в том, что с языками C и C++ есть проблемы. И сейчас мы «вытащим» одну неудобную проблему на обозрение.
Абстрактный c/cpp-файл бывает очень непросто скомпилировать. По крайней мере, не получится выкачать проект с GitHub, выбрать какой-то cpp-файл и просто его скомпилировать. Сейчас мы поясним, какое отношение всё это имеет к ML.
Вот здесь и начинается проблема. Недостаточно просто изучить исправления. Необходимо иметь полный контекст. Необходимо знать описание используемых классов, необходимо знать прототипы используемых функций, нужно знать как раскрываются макросы и так далее. А для этого нужно выполнить полноценное препроцессирование файла.
Рассмотрим пример. Вначале код выглядел так:
Следует ли на основании этого случая начинать учиться, чтобы в дальнейшем предлагать заменять (x == «y») на strcmp(x, «y»)?
На этот вопрос нельзя дать ответ, не зная, как объявлен член m_name в классе. Могут быть, например, такие варианты:
Правки будут вноситься в том случае, если речь идёт об обыкновенном указателе. Если не учитывать тип переменной, можно научиться вместе с полезными предупреждениями выдавать и вредные (для случая std::string).
Если кто-то скажет, что можно обойтись без препроцессирования, то он или шарлатан, или просто недостаточно знаком с языками C или C++.
Чтобы собрать всю необходимую информацию, нужно правильно выполнить препроцессирование. Для этого нужно знать, где и какие заголовочные файлы находятся, какие макросы задаются в процессе сборки. Для этого нужно знать, как собирается конкретный cpp-файл.
В этом и состоит проблема. Нельзя просто взять и скомпилировать файл (вернее, указать ключ компилятору, чтобы он сгенерировал препроцессированный файл). Нужно разобраться, как этот файл компилируется. Эта информация есть в скриптах сборки, но вот как её оттуда достать, в общем случае задача крайне сложная.
Можно сказать, хорошо, проблема с несобирающимися проектами понятна, но не страшна. Давайте работать только с теми проектами, которые можно собрать. Всё равно остаётся задача препроцессирования конкретного файла. И уж промолчим про те случаи, когда будут использоваться какие-то специализированные компиляторы, например, для встраиваемых систем.
В целом, описанная проблема не является непреодолимой. Однако всё это в любом случае очень сложно и трудозатратно. В случае C и C++ исходные коды, лежащие на GitHub, сами по себе ничего не дают. Нужно проделать колоссальную работу, чтобы научиться автоматически запускать компиляторы.
С другими языками могут быть похожие проблемы, но в C и C++ они особенно выражены.
Шестой нюанс. Цена устранения ложных срабатываний.
Статические анализаторы склонны к генерации ложных срабатываний и приходится постоянно дорабатывать диагностики, чтобы сократить их количество.
Вернёмся к уже рассмотренной ранее диагностике V789, выявляющей изменения контейнера внутри Range-based for loop. Допустим, мы были недостаточно тщательны при её разработке, и клиент сообщает про ложное срабатывание. Он пишет, что анализатор не учитывает сценарий, когда после модификации контейнера цикл завершается, и поэтому никакой беды нет. И приводит следующий пример кода, где анализатор выдаёт ложное срабатывание:
Да, это недоработка. В классическим анализаторе её устранение крайне быстро и дёшево. В PVS-Studio реализация этого исключения состоит из 26 строк кода.
Эту недоработку можно устранить и в случае, когда анализатор построен на алгоритмах обучения. Наверняка, его можно доучить, составив десятки или сотни примеров кода, который должен считаться корректным.
Здесь вновь вопрос не в реализуемости, а в практичности. Есть подозрение, что борьба с конкретными ложными срабатываниями, которые беспокоят клиентов, намного более затратна в случае ML. Т.е. поддержка клиентов в плане устранения ложных срабатываний будет стоить больше денег.
Седьмой нюанс. Редко используемые функции и длинный хвост.
Ранее рассматривалась проблема узкоспециализированных языков, для которых может оказаться недостаточно исходного кода для обучения. Аналогичная проблема с редко используемыми функциями (системными, WinAPI, из популярных библиотек и т.д.).
Если речь идёт о таких функциях языка C, как strcmp, то здесь есть почва для обучения. GitHub, available code results:
Есть популярные функции и их мало. Есть непопулярные, но их много. Например, существуют ещё вот такие разновидности функции сравнения строк:
В PVS-Studio мы вручную аннотируем функции. Например, для C и С++ на данный момент размечено около 7200 функций. Разметке подвергается:
Теперь вопрос. Какие преимущества может дать ML? Существенные преимущества не видны, зато видны сложности.
Можно сказать, что алгоритмы, построенные на ML, сами выявят закономерности с часто используемыми функциями и их не придётся размечать. Да, это правда. Однако нет никакой проблемы самостоятельно разметить такие популярные функции, как strcmp или malloc.
C длинным же хвостом начинаются проблемы. Можно обучать, составляя синтетические примеры. Однако здесь мы возвращаемся к разделу статьи, где рассматривали, что тогда проще и быстрее писать классические диагностики, а не генерировать множество примеров.
Возьмём для примера такую функцию, как _fread_nolock. Конечно, она используется реже, чем fread. Но при её использовании можно допустить все те же ошибки. Например, буфер должен быть достаточного размера. Этот размер должен быть не меньше, чем результат умножения второго и третьего аргумента. То есть хочется обнаруживать вот такой некорректный код:
Вот как выглядит аннотирование этой функции в PVS-Studio:
На первый взгляд подобная аннотация может выглядеть сложно, но, на самом деле, когда начинаешь их писать, это становится просто. Плюс надо учитывать, что это write-only код. Написал и забыл. Аннотации меняются редко.
Теперь поговорим про эту функцию с точки зрения ML. GitHub нам не поможет. Находится около 15000 упоминаний этой функции. Толкового кода там ещё меньше. Существенную часть результатов поиска занимает подобное:
Какие есть варианты?
Здесь люди, связанные с тематикой ML, нам возражали и говорили, что мы не учитываем вариант, когда анализатор изучит функции и сделает выводы, что они делают. Тут, видимо, или мы не понимаем экспертов, или они не понимают нас.
Тела функций могут быть неизвестны. Например, это может быть функция, относящаяся к WinAPI. Если это редко используемая функция, то как анализатор поймёт, что она делает? Мы можем фантазировать, что анализатор во время обучения сам воспользуется Google, найдёт описание функции, прочитает и поймёт его. Более того, нужно сделать высокоуровневые выводы из документации. В описании _fread_nolockнигде не говорится про взаимосвязь между размером буфера, вторым и третьим аргументом. Это сопоставление надо вывести искусственному интеллекту самостоятельно, основываясь на понимании общих принципов программирования и том, как работает язык C++. Думается, что всерьёз над всем этим можно будет подумать лет так через 20.
Тела функций могут быть доступны, но толку от этого может не быть. Рассмотрим такую функцию, как memmove. Она часто реализуется как-то так:
А что такое __builtin___memmove_chk? Это intrinsic функция, которую реализует уже сам компилятор. Исходного кода у этой функции нет.
Или memmove может выглядеть как-то так: первый попавшийся вариант на ассемблере. Можно учить анализатор понимать ещё и разные варианты ассемблера, но что-то это не то.
Ok, иногда тела функций действительно известны. Более того, ведь известны и тела функций в пользовательском коде. Казалось бы, в этом случае ML получает колоссальные преимущества, читая и разбираясь в том, что делают все эти функции.
Однако и в этом случае мы полны пессимизма. Эта задача слишком сложна. Она для человека и то сложна. Вспомните, с каким трудом вам приходится разбираться в коде, написанном не вами. Если это сложно человеку, почему эта задача должна быть легка для AI? Собственно, у AI большая проблема в том, чтобы осознать высокоуровневые понятия. Если же мы говорим о понимании кода, то не обойтись без умения абстрагироваться от деталей реализации и рассматривать алгоритм на высоком уровне. Думается, что и здесь лет на 20 можно отложить обсуждение.
Выводы
Мы не отрицаем перспективности направления машинного обучения, в том числе, в задачах статического анализа кода. Есть потенциал использовать ML в задачах поиска опечаток, при фильтрации ложных сообщений, в поиске новых (ещё нигде не описанных) паттернов ошибок и так далее. Однако мы совершенно не разделяем и оптимизм, которыми пропитаны статьи, посвящённые ML в задачах анализа кода.
В статье мы описали несколько проблем, над которыми придётся работать, если брать за основу ML. Описанные нюансы во многом сводят на нет преимущества от нового подхода, более того, старые классические подходы имплементации анализаторов оказываются более выигрышными и более экономически целесообразными.
Что интересно, в статьях приверженцев методологии ML про эти подводные камни не говорится. Впрочем, это не удивительно. Тема ML сейчас является слишком «хайповой» и странно ждать от её апологетов взвешенных оценок её применимости в задачах статического анализа кода.
С нашей точки зрения, машинное обучение займёт свою нишу в технологиях, используемых в статических анализаторах, наряду с анализом потоков управления, символьными вычислениями и так далее.
Методология статического анализа может получить пользу от внедрения ML, но не стоит преувеличивать возможности этой технологии.
Поскольку статья носит в целом критический характер, то кто-то может подумать, что мы опасаемся нового и как луддиты ополчились против ML, боясь потерять рынок инструментов статического анализа.
Нет, мы не боимся. Просто мы не видим смысла тратить деньги на неэффективные подходы в развитии анализатора кода PVS-Studio. В том или ином виде мы возьмём на вооружение ML. Более того, некоторые диагностики уже содержат в себе элементы самообучающихся алгоритмов. Однако мы точно будем очень консервативны и брать только то, что явно даст больший эффект, чем классические подходы, построенные на циклах и if-ах :). Ведь нам надо создавать эффективный инструмент, а не отрабатывать грант :).
Статья же написана по той причине, что по рассмотренной теме задают всё больше вопросов и захотелось иметь статью-ответ, расставляющую всё по своим местам.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov, Victoria Khanieva. Machine Learning in Static Analysis of Program Source Code.