javascript

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

  • пятница, 26 января 2018 г. в 03:14:18
https://habrahabr.ru/company/netologyru/blog/347516/
  • Разработка веб-сайтов
  • JavaScript
  • Блог компании Нетология


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

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


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

image

В языке 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 позволяет увидеть это наглядно:


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

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

Благодаря отображению актуальных значений аргументов и переменных, код можно отладить в процессе выполнения без добавления синтетических 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 также может использовать для создания функциональных тестов код без необходимости его предварительной модификации для экспорта значений. На основе Iroh.js можно замерять производительность проблемных участков кода и даже реализовывать online IDE-среду разработки с возможностью пошаговой отладки и time-travelling debug, то есть выполнения кода в обратном порядке для поиска выражений, послуживших источником ошибки или неправильных расчетов.

От редакции


Курсы «Нетологии» по теме: