Advanced EcmaScript6: динамический анализ кода с помощью Iroh.js

Владислав Власов, инженер-программист в Developer Soft и преподаватель Нетологии, написал для блога цикл статей о EcmaScript6. В первой части на примерах рассмотрим динамический анализ кода в EcmaScript с помощью Iroh.js.

Статический и динамический анализ кода

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

Программа обучения: «Профессия веб-разработчик»

В языке EcmaScript, обладающим утиной типизацией (duck-typing), часто используются средства статического анализа, позволяющие без выполнения обнаружить потенциально опасные ситуации в коде: несоответствия типов передаваемых и принимаемых аргументов, некорректные операции с переменными несоответствующих типов, невыполняемые секции кода и так далее. Наиболее популярными являются решения Typescript и Flow, основанные на расширении языка специальным синтаксисом. В отличие от языков программирования, в которых строгая система типов является их неотъемлемой частью, и соответственно, описания типов переменных и объектов уже заложено в исходном коде, в EcmaScript, напротив, требуется мануальное описание, причем зависимое и различающееся для отдельных средств статической типизации. Это влечет за собой следующие недостатки:

  • средство статической типизации вводит набор синтетических инструкций в язык, так что исходный код обязательно должен быть обработан транспилером (transpiler), вроде Вabel с соответствующим плагинами, или TypeScript compiler соответственно;
  • язык EcmaScript — динамический по своей природе, и во множестве случаев статическая типизация просто неприменима: для любых объектов, получаемых из внешней среды, типом данных будет string или any.

Iroh.js — решение для динамического анализа EcmaScript-приложений

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

Iroh.js может быть использован для получения информации о следующих аспектах времени выполнения: дерева вызова функций, актуальных типов данных объектов и переменных, неявных преобразований в объект и примитивный тип (boxing/unboxing), включая преобразования в строку.

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

Iroh.js отслеживает стек вызовов только для модифицированного кода и не может предоставить метаинформацию или модификацию на лету для нативных функций среды исполнения или импортируемых библиотечных элементов. Как правило, не требуется анализировать и модифицировать поведение библиотечного кода, но при необходимости это также возможно — область действия Iroh.js определяется кодом, который был добавлен в песочницу исполнения Iroh.js посредством предварительного patching-а.

Iroh.js может быть добавлен в состав node.js приложения в качестве npm-пакета, так и запущен непосредственно в браузере, что может быть полезно для динамического анализа небольших независимых фрагментов кода или создания демонстрационных примеров.

Пример 1. Анализ графа вызовов для функции вычисления чисел Фибоначчи

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

function fibonacciNaive (n) {
if (n = 0) return 0;
return fibonacciNaive (n — 1) + fibonacciNaive (n — 2);
};
function fibonacciMemoized (n, x1 = 0, x2 = 1) {
if (n = 0) return x1;
return fibonacciMemoized (n — 1, x2, x1 + x2);
};

Теперь можно произвести динамический анализ этих функций. Разумеется, обе реализации обеспечивают правильный результат, однако fibonacciMemoized работает значительно быстрее, чем fibonacciNaive, и к тому же потребляем меньше памяти. Очевидно, что дело в сохранении промежуточных вычислений и оптимизации хвостовой рекурсии. Анализатор потока исполнения на основе Iroh.js позволяет увидеть это наглядно:

Хотя задача расчета чисел Фибоначчи крайне проста, и приведенная оптимизация тривиальна и широко известна, Iroh.js позволяет отследить граф вызовов функций и найти избыточные или некорректные вызовы.Динамический анализатор позволяет с легкостью выявить, что в первом, неоптимальном случае, для расчета 6-го числа Фибоначчи число рекурсивных вызовов достигает 41, в то время как во втором — всего лишь 7. С увеличением номера рассчитываемого числа, в первом случае рост рекурсивных вызовов будет экспоненциальным, а во втором — линейным.

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

Пример 2. Модификация объектов и переменных на лету

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

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

const urlAddresses = [ADDR1, ADDR2, ADDR3];
const resultPromise = Promise.all (urlAddresses.map (addr = fetch (addr).then (res = res.text ()).catch (err = new Error (err))));
resultPromise.then (result = console.log (result))

Допустим, необходимо заменить URL-адрес ADDR3 на ADDR3-test. С помощью Iroh.js соответствующую операцию можно провести следующим образом:

const stage = new Iroh.Stage (code); // code — вышеуказанный код в строковом виде
const listener = stage.addListener (Iroh.CALL);
const originalFetch = fetch;
listener.on («before», function (e) {
if (e.object === fetch) {
e.call = function (url) {
const targetUrl = url === ADDR3? ADDR3-test: url;
return originalFetch.call (null, [targetUrl].concat (arguments.slice (1)))
});
}
});
eval (stage.script);

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

Выводы

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

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

Читать ещё: «Что нового в ECMAScript 2017 (ES8)»

На основе Iroh.js можно замерять производительность проблемных участков кода и даже реализовывать online IDE-среду разработки с возможностью пошаговой отладки и time-travelling debug, то есть выполнения кода в обратном порядке для поиска выражений, послуживших источником ошибки или неправильных расчетов.

Мнение автора и редакции может не совпадать. Хотите написать колонку для «Нетологии»? Читайте наши условия публикации.

Полный текст статьи читайте на Нетология