JavaScript: безопасный код


Давно собирался написать нечто подобное, да всё руки не доходили. Толчком послужил краткий пост на аналогичную тему у моего знакомого.

Как известно, JavaScript — язык c динамической (для классов с утиной) типизацией, с очень плохо развитой системой типов. Стандартный интерпретатор даже в браузерах Mozilla с расширениями разработчика отлавливает минимум ошибок. Практически все эти ошибки — синтаксические. В результате, писать надёжный код на JavaScript предельно сложно. Положение усугубляет фактическая невозможность запуска программы без использования браузера, т.е. нет никакой песочницы для тестов.

В этой статье я попытаюсь описать некоторые технологии, которые помогут знакомому с Ocaml человеку существенно сократить время написания безопасного, стабильного и более-менее компактного JS-кода. Впрочем, описываемые техники есть и для некоторых других языков с развитой системой типов (например, Haskell).

Ocamljs: компилятор Ocaml ⇒ JavaScript

Скачать: http://code.google.com/p/ocamljs/

Всё просто: вы пишете client-side код вашего сайта на Ocaml, а с помощью этого компилятора получаете готовый для использования в браузере JavaScript. Для обеспечения работы, непосредственно к вашему скрипту дописывается около 40KB дополнительного кода. Например. Ни к чему не обязывающий код:

type tree =
	| Leaf of int
	| Node of tree list
 
let l : tree list = []
 
let _ =
	match l with
	| [] -> Dom.window#alert "empty list"
	| a :: [] -> ()
	| a :: b :: [] -> ()
	| _ -> Dom.window#alert "unexpected list"

Компилируем:

ocamljs -o test.js -I /usr/local/lib/ocaml/3.10.2/dom/ dom.cmjsa test.ml

Получаем вот такой код:

// .....
var oc$Dom$ = function () { var window$717 = window;
                            var document$718 = document;
                            return $(window$717, document$718); }();
var oc$1$ =
  function () {
    var l$63 = 0;
    if (l$63)
    {
      var match$71 = l$63[1];
      if (match$71)
      if (match$71[1]) (function () { var v$74 = oc$Dom$[0];
                                      return _m(v$74.alert, v$74, ["unexpected list"]); }()); else ;
      else ;
    }
    else (function () { var v$73 = oc$Dom$[0];
                        return _m(v$73.alert, v$73, ["empty list"]); }());
    return $(l$63);
  }();
var oc$Std_exit$ = (_(oc$Pervasives$[80], [0]), $());
return caml_named_value;
})();

JavaScript compressor

Как я уже сказал, код получается достаточно объёмным. Частично эту проблему решает gzip (при условии, что и сервер, и браузер имеют его поддержку). Однако, код можно существенно сократить за счёт оптимизации по размеру. Для этого есть ряд готовых online-утилит. Используемые техники:

  • Удаление комментариев
  • Удаление не несущих синтаксической нагрузки пробелов и переносов строк
  • Назначение локальным переменным функций более коротких имён
  • Вынесение длинных повторяющихся констант в переменную с коротким именем

Наилучшие результаты показывает Packer (http://dean.edwards.name/packer/), однако ocamljs компилирует не полностью корректный код — иногда забывает точки с запятой — поэтому Packer генерирует не работающий код. С учётом некорректности оригинального кода, наилучший результат показал Dojo ShrinkSafe (http://www.dojotoolkit.org/docs/shrinksafe). Рекомендую прогнать код через валидатор (http://jslint.com/), это в любом случае неплохая идея. Пример кода, получающегося с помощью Dojo ShrinkSafe:

// .....
var _213=function(){
var l$63=0;
if(l$63){
var _215=l$63[1];
if(_215){
if(_215[1]){
(function(){
var v$74=_210[0];
return _m(v$74.alert,v$74,["unexpected list"]);
}());
}else{
}
}else{
}
}else{
(function(){
var v$73=_210[0];
return _m(v$73.alert,v$73,["empty list"]);
}());
}
return $(l$63);
}();
var _218=(_(_106[80],[0]),$());
return _9d;
})();

Код получается практически не читаемым, зато размер сокращается в 2-4 раза.

Быстро сравнить различные компрессоры можно на сайте: http://compressorrater.thruhere.net/

O’Browser: виртуальная машина на JavaScript

Скачать: http://ocsigen.org/obrowser/

Ваш код будет скомпилирован в байт-код, закодированный в UUE. Для вызова кода, достаточно вызвать его в виртуальной машине:

<script src="vm.js" type="text/javascript"></script>
<script type="text/javascript">
  /* <![CDATA[ */
    window.onload = function () {
      exec_caml ("tutorial.exe.uue") ;
    }
  /* ]]> */
</script>

Байт-код получается достаточно объёмным, да и сжатию поддаётся только за счёт слоя gzip. Впрочем, код самой виртуальной машины вполне поддаётся обработке JavaScript компрессоров. Валидность кода получается даже выше, чем у ocamljs. Декомпиляция байт-кода в нормальный JavaScript становится нерешаемой проблемой :)

, , , ,

  1. #1 by yumaa on October 7th, 2009

    имею некоторый опыт по написанию скриптов на javascript, и имхо валидность кода ещё пол-дела. и если код на ocaml очарователен по виду, но получившийся в результате ocamljs скрипт – страшный как моя жизнь. логическую ошибку искать в таком… при одной мысли в дрожь бросает :) или ещё чего хуже, под разные браузеры править, чтобы работало одинаково.

  2. #2 by John Lepikhin on October 7th, 2009

    Теперь сюрприз: сделать логическую ошибку на языке с хорошо развитой системой типов очень сложно :) С использованием OUnit (система для написания юнит-тестов) ошибку допустить предельно сложно. С проектированием алгоритмов в каком-нибудь Coq и вовсе невозможно.

    К слову, в oProxy было за всю историю лишь 2-3 логических ошибки(!). И то, саппорт их не видел. Их заметил только я т.к. они были минорными. А то, из-за чего имел проблемы саппорт — это не логические ошибки, а ошибки в моём ДНК:
    1. Апрельская проблема: не надо использовать POSIX-потоки. Они очень тяжёлые.
    2. Сентябрьские дауны: плохо давать процессу ничем не ограниченное количество файлов. В драке за FD процесса и ОС всегда почему-то побеждает ОС.

    Согласись, обе ошибки к алгоритмической логике отношения не имеют.

    Касаетельно браузерной совместимости. Так ведь и на окамле точно так же всё можно описать. Тем более, что таких узких мест на большой скрипт обычно очень мало: бОльшая часть скрипта всё равно будет алгоритмом, RPC и т.д.

  3. #3 by RedChrom on October 9th, 2009

    Всё-таки с ocamljs есть проблемы с типами, точнее в его интерфейсе к внешнему миру. Ибо некоторые js функции могут вернуть null, вместо element и это дело нужно явно чекать. Тем не менее если обвернуть более высокоуровневый и безопасный интерфейс – жить можно.
    >бОльшая часть скрипта всё равно будет алгоритмом, RPC и т.д.
    Вот главная минус всяких jQuery, это предположение, что весь DOM свёрстан ручками, а надо только свистелки и перделки добавлять по идентификаторам. У меня весь DOM из js строиться и еботни со этим DOM дофига. Манипуляции с DOM из ocaml очень не привычны, но привыкаешь и ошибок действительно получается меньше.

  4. #4 by RedChrom on October 9th, 2009

    Btw, если кто собирётся писать что-то серьёзное на ocamljs то ко мне можно обращаться за патчами, ибо хуй знает когда в апстриме их закоммитят :)

  5. #5 by John Lepikhin on October 9th, 2009

    Что патчил?

  6. #6 by RedChrom on October 9th, 2009

    Ocamljs, там не хватает некторых dom properties, проблемы с юникодом и так по мелочи.

(will not be published)

  1. No trackbacks yet.