Владислав Власов, инженер-программист в Developer Softи преподаватель Нетологии, написал для блога цикл статей о EcmaScript6. В первой части на примерах рассмотрим динамический анализ кода в EcmaScript с помощью Iroh.js.
Статический и динамический анализ кода
Средства анализа кода - полезный инструмент, позволяющий обнаруживать и выявлять ошибки и особенности в работе кода. Анализ кода бывает статическим и динамическим. В первом случае производится разбор и анализ исходного кода без его выполнения, во втором - выполнение происходит в управляемой среде-песочнице (sandboxing), предоставляющей метаинформацию об элементах приложения в процессе его выполнения.
Программа обучения: "Профессия веб-разработчик"
В языке EcmaScript, обладающим утиной типизацией (duck-typing), часто используются средства статического анализа, позволяющие без выполнения обнаружить потенциально опасные ситуации в коде: несоответствия типов передаваемых и принимаемых аргументов, некорректные операции с переменными несоответствующих типов, невыполняемые секции кода и так далее. Наиболее популярными являются решения Typescriptи Flow, основанные на расширении языка специальным синтаксисом.
- средство статической типизации вводит набор синтетических инструкций в язык, так что исходный код обязательно должен быть обработан транспилером (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. Анализ графа вызовов для функции вычисления чисел Фибоначчи
В качестве самого простого примера можно рассмотреть две реализации функции вычисления чисел Фибоначчи - наивный вариант с полным рекурсивным деревом, и оптимизированное решение с мемоизацией предыдущих результатов. Исходный код предполагаемых функций представлен ниже:
if(n = 0) return 0;
return fibonacciNaive(n - 1) + fibonacciNaive(n - 2);
};
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 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 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, то есть выполнения кода в обратном порядке для поиска выражений, послуживших источником ошибки или неправильных расчетов.
Мнение автора и редакции может не совпадать. Хотите написать колонку для "Нетологии"? Читайте наши условия публикации.