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

05.02.2018
1391
Подпишитесь, чтобы получать новые статьи на почту

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


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

Владислав Власов
Инженер-программист Developer Soft
Университет интернет-профессий
Мы используем файлы cookie
Чтобы улучшить работу сайта и предоставить вам больше возможностей для обучения. Продолжая использовать сайт, вы соглашаетесь с условиями использования файлов cookie.