Владислав Власов, инженер-программист в 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, то есть выполнения кода в обратном порядке для поиска выражений, послуживших источником ошибки или неправильных расчетов.
Мнение автора и редакции может не совпадать. Хотите написать колонку для «Нетологии»? Читайте наши условия публикации.