PageRenderTime 350ms CodeModel.GetById 101ms app.highlight 217ms RepoModel.GetById 14ms app.codeStats 1ms

/js/lib/Socket.IO-node/support/expresso/deps/jscoverage/instrument-js.cpp

http://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs
C++ | 1873 lines | 1629 code | 102 blank | 142 comment | 410 complexity | a85c79efb6b3ccffe4aa784a8140d131 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/*
   2    instrument-js.cpp - JavaScript instrumentation routines
   3    Copyright (C) 2007, 2008 siliconforks.com
   4
   5    This program is free software; you can redistribute it and/or modify
   6    it under the terms of the GNU General Public License as published by
   7    the Free Software Foundation; either version 2 of the License, or
   8    (at your option) any later version.
   9
  10    This program is distributed in the hope that it will be useful,
  11    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13    GNU General Public License for more details.
  14
  15    You should have received a copy of the GNU General Public License along
  16    with this program; if not, write to the Free Software Foundation, Inc.,
  17    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18*/
  19
  20#include <config.h>
  21
  22#include "instrument-js.h"
  23
  24#include <assert.h>
  25#include <math.h>
  26#include <stdlib.h>
  27#include <string.h>
  28
  29#include <jsapi.h>
  30#include <jsarena.h>
  31#include <jsatom.h>
  32#include <jsemit.h>
  33#include <jsexn.h>
  34#include <jsfun.h>
  35#include <jsinterp.h>
  36#include <jsiter.h>
  37#include <jsparse.h>
  38#include <jsregexp.h>
  39#include <jsscope.h>
  40#include <jsstr.h>
  41
  42#include "encoding.h"
  43#include "global.h"
  44#include "highlight.h"
  45#include "resource-manager.h"
  46#include "util.h"
  47
  48struct IfDirective {
  49  const jschar * condition_start;
  50  const jschar * condition_end;
  51  uint16_t start_line;
  52  uint16_t end_line;
  53  struct IfDirective * next;
  54};
  55
  56bool jscoverage_mozilla = false;
  57
  58static bool * exclusive_directives = NULL;
  59
  60static JSRuntime * runtime = NULL;
  61static JSContext * context = NULL;
  62static JSObject * global = NULL;
  63static JSVersion js_version = JSVERSION_ECMA_3;
  64
  65/*
  66JSParseNode objects store line numbers starting from 1.
  67The lines array stores line numbers starting from 0.
  68*/
  69static const char * file_id = NULL;
  70static char * lines = NULL;
  71static uint16_t num_lines = 0;
  72
  73void jscoverage_set_js_version(const char * version) {
  74  js_version = JS_StringToVersion(version);
  75  if (js_version != JSVERSION_UNKNOWN) {
  76    return;
  77  }
  78
  79  char * end;
  80  js_version = (JSVersion) strtol(version, &end, 10);
  81  if ((size_t) (end - version) != strlen(version)) {
  82    fatal("invalid version: %s", version);
  83  }
  84}
  85
  86void jscoverage_init(void) {
  87  runtime = JS_NewRuntime(8L * 1024L * 1024L);
  88  if (runtime == NULL) {
  89    fatal("cannot create runtime");
  90  }
  91
  92  context = JS_NewContext(runtime, 8192);
  93  if (context == NULL) {
  94    fatal("cannot create context");
  95  }
  96
  97  JS_SetVersion(context, js_version);
  98
  99  global = JS_NewObject(context, NULL, NULL, NULL);
 100  if (global == NULL) {
 101    fatal("cannot create global object");
 102  }
 103
 104  if (! JS_InitStandardClasses(context, global)) {
 105    fatal("cannot initialize standard classes");
 106  }
 107}
 108
 109void jscoverage_cleanup(void) {
 110  JS_DestroyContext(context);
 111  JS_DestroyRuntime(runtime);
 112}
 113
 114static void print_javascript(const jschar * characters, size_t num_characters, Stream * f) {
 115  for (size_t i = 0; i < num_characters; i++) {
 116    jschar c = characters[i];
 117    /*
 118    XXX does not handle no-break space, other unicode "space separator"
 119    */
 120    switch (c) {
 121    case 0x9:
 122    case 0xB:
 123    case 0xC:
 124      Stream_write_char(f, c);
 125      break;
 126    default:
 127      if (32 <= c && c <= 126) {
 128        Stream_write_char(f, c);
 129      }
 130      else {
 131        Stream_printf(f, "\\u%04x", c);
 132      }
 133      break;
 134    }
 135  }
 136}
 137
 138static void print_string(JSString * s, Stream * f) {
 139  size_t length = JSSTRING_LENGTH(s);
 140  jschar * characters = JSSTRING_CHARS(s);
 141  for (size_t i = 0; i < length; i++) {
 142    jschar c = characters[i];
 143    if (32 <= c && c <= 126) {
 144      switch (c) {
 145      case '"':
 146        Stream_write_string(f, "\\\"");
 147        break;
 148/*
 149      case '\'':
 150        Stream_write_string(f, "\\'");
 151        break;
 152*/
 153      case '\\':
 154        Stream_write_string(f, "\\\\");
 155        break;
 156      default:
 157        Stream_write_char(f, c);
 158        break;
 159      }
 160    }
 161    else {
 162      switch (c) {
 163      case 0x8:
 164        Stream_write_string(f, "\\b");
 165        break;
 166      case 0x9:
 167        Stream_write_string(f, "\\t");
 168        break;
 169      case 0xa:
 170        Stream_write_string(f, "\\n");
 171        break;
 172      /* IE doesn't support this */
 173      /*
 174      case 0xb:
 175        Stream_write_string(f, "\\v");
 176        break;
 177      */
 178      case 0xc:
 179        Stream_write_string(f, "\\f");
 180        break;
 181      case 0xd:
 182        Stream_write_string(f, "\\r");
 183        break;
 184      default:
 185        Stream_printf(f, "\\u%04x", c);
 186        break;
 187      }
 188    }
 189  }
 190}
 191
 192static void print_string_atom(JSAtom * atom, Stream * f) {
 193  assert(ATOM_IS_STRING(atom));
 194  JSString * s = ATOM_TO_STRING(atom);
 195  print_string(s, f);
 196}
 197
 198static void print_regex(jsval value, Stream * f) {
 199  assert(JSVAL_IS_STRING(value));
 200  JSString * s = JSVAL_TO_STRING(value);
 201  size_t length = JSSTRING_LENGTH(s);
 202  jschar * characters = JSSTRING_CHARS(s);
 203  for (size_t i = 0; i < length; i++) {
 204    jschar c = characters[i];
 205    if (32 <= c && c <= 126) {
 206      Stream_write_char(f, c);
 207    }
 208    else {
 209      Stream_printf(f, "\\u%04x", c);
 210    }
 211  }
 212}
 213
 214static void print_quoted_string_atom(JSAtom * atom, Stream * f) {
 215  assert(ATOM_IS_STRING(atom));
 216  JSString * s = ATOM_TO_STRING(atom);
 217  Stream_write_char(f, '"');
 218  print_string(s, f);
 219  Stream_write_char(f, '"');
 220}
 221
 222static const char * get_op(uint8 op) {
 223  switch(op) {
 224  case JSOP_OR:
 225    return "||";
 226  case JSOP_AND:
 227    return "&&";
 228  case JSOP_BITOR:
 229    return "|";
 230  case JSOP_BITXOR:
 231    return "^";
 232  case JSOP_BITAND:
 233    return "&";
 234  case JSOP_EQ:
 235    return "==";
 236  case JSOP_NE:
 237    return "!=";
 238  case JSOP_STRICTEQ:
 239    return "===";
 240  case JSOP_STRICTNE:
 241    return "!==";
 242  case JSOP_LT:
 243    return "<";
 244  case JSOP_LE:
 245    return "<=";
 246  case JSOP_GT:
 247    return ">";
 248  case JSOP_GE:
 249    return ">=";
 250  case JSOP_LSH:
 251    return "<<";
 252  case JSOP_RSH:
 253    return ">>";
 254  case JSOP_URSH:
 255    return ">>>";
 256  case JSOP_ADD:
 257    return "+";
 258  case JSOP_SUB:
 259    return "-";
 260  case JSOP_MUL:
 261    return "*";
 262  case JSOP_DIV:
 263    return "/";
 264  case JSOP_MOD:
 265    return "%";
 266  default:
 267    abort();
 268  }
 269}
 270
 271static void output_expression(JSParseNode * node, Stream * f, bool parenthesize_object_literals);
 272static void instrument_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if);
 273static void output_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if);
 274
 275enum FunctionType {
 276  FUNCTION_NORMAL,
 277  FUNCTION_GETTER_OR_SETTER
 278};
 279
 280static void output_for_in(JSParseNode * node, Stream * f) {
 281  assert(node->pn_type == TOK_FOR);
 282  assert(node->pn_arity == PN_BINARY);
 283  Stream_write_string(f, "for ");
 284  if (node->pn_iflags & JSITER_FOREACH) {
 285    Stream_write_string(f, "each ");
 286  }
 287  Stream_write_char(f, '(');
 288  output_expression(node->pn_left, f, false);
 289  Stream_write_char(f, ')');
 290}
 291
 292static void output_array_comprehension_or_generator_expression(JSParseNode * node, Stream * f) {
 293  assert(node->pn_type == TOK_LEXICALSCOPE);
 294  assert(node->pn_arity == PN_NAME);
 295  JSParseNode * for_node = node->pn_expr;
 296  assert(for_node->pn_type == TOK_FOR);
 297  assert(for_node->pn_arity == PN_BINARY);
 298  JSParseNode * p = for_node;
 299  while (p->pn_type == TOK_FOR) {
 300    p = p->pn_right;
 301  }
 302  JSParseNode * if_node = NULL;
 303  if (p->pn_type == TOK_IF) {
 304    if_node = p;
 305    assert(if_node->pn_arity == PN_TERNARY);
 306    p = if_node->pn_kid2;
 307  }
 308  switch (p->pn_arity) {
 309  case PN_UNARY:
 310    p = p->pn_kid;
 311    if (p->pn_type == TOK_YIELD) {
 312      /* for generator expressions */
 313      p = p->pn_kid;
 314    }
 315    output_expression(p, f, false);
 316    break;
 317  case PN_LIST:
 318    /*
 319    When the array comprehension contains "if (0)", it will be optimized away and
 320    the result will be an empty TOK_LC list.
 321    */
 322    assert(p->pn_type == TOK_LC);
 323    assert(p->pn_head == NULL);
 324    /* the "1" is arbitrary (since the list is empty) */
 325    Stream_write_char(f, '1');
 326    break;
 327  default:
 328    abort();
 329    break;
 330  }
 331
 332  p = for_node;
 333  while (p->pn_type == TOK_FOR) {
 334    Stream_write_char(f, ' ');
 335    output_for_in(p, f);
 336    p = p->pn_right;
 337  }
 338  if (p->pn_type == TOK_LC) {
 339    /* this is the optimized-away "if (0)" */
 340    Stream_write_string(f, " if (0)");
 341  }
 342  else if (if_node) {
 343    Stream_write_string(f, " if (");
 344    output_expression(if_node->pn_kid1, f, false);
 345    Stream_write_char(f, ')');
 346  }
 347}
 348
 349static void instrument_function(JSParseNode * node, Stream * f, int indent, enum FunctionType type) {
 350  assert(node->pn_type == TOK_FUNCTION);
 351  assert(node->pn_arity == PN_FUNC);
 352  JSObject * object = node->pn_funpob->object;
 353  assert(JS_ObjectIsFunction(context, object));
 354  JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);
 355  assert(function);
 356  assert(object == &function->object);
 357  Stream_printf(f, "%*s", indent, "");
 358  if (type == FUNCTION_NORMAL) {
 359    Stream_write_string(f, "function ");
 360  }
 361
 362  /* function name */
 363  if (function->atom) {
 364    print_string_atom(function->atom, f);
 365  }
 366
 367  /*
 368  function parameters - see JS_DecompileFunction in jsapi.cpp, which calls
 369  js_DecompileFunction in jsopcode.cpp
 370  */
 371  Stream_write_char(f, '(');
 372  JSArenaPool pool;
 373  JS_INIT_ARENA_POOL(&pool, "instrument_function", 256, 1, &context->scriptStackQuota);
 374  jsuword * local_names = NULL;
 375  if (JS_GET_LOCAL_NAME_COUNT(function)) {
 376    local_names = js_GetLocalNameArray(context, function, &pool);
 377    if (local_names == NULL) {
 378      fatal("out of memory");
 379    }
 380  }
 381  bool destructuring = false;
 382  for (int i = 0; i < function->nargs; i++) {
 383    if (i > 0) {
 384      Stream_write_string(f, ", ");
 385    }
 386    JSAtom * param = JS_LOCAL_NAME_TO_ATOM(local_names[i]);
 387    if (param == NULL) {
 388      destructuring = true;
 389      JSParseNode * expression = NULL;
 390      assert(node->pn_body->pn_type == TOK_LC || node->pn_body->pn_type == TOK_SEQ);
 391      JSParseNode * semi = node->pn_body->pn_head;
 392      assert(semi->pn_type == TOK_SEMI);
 393      JSParseNode * comma = semi->pn_kid;
 394      assert(comma->pn_type == TOK_COMMA);
 395      for (JSParseNode * p = comma->pn_head; p != NULL; p = p->pn_next) {
 396        assert(p->pn_type == TOK_ASSIGN);
 397        JSParseNode * rhs = p->pn_right;
 398        assert(JSSTRING_LENGTH(ATOM_TO_STRING(rhs->pn_atom)) == 0);
 399        if (rhs->pn_slot == i) {
 400          expression = p->pn_left;
 401          break;
 402        }
 403      }
 404      assert(expression != NULL);
 405      output_expression(expression, f, false);
 406    }
 407    else {
 408      print_string_atom(param, f);
 409    }
 410  }
 411  JS_FinishArenaPool(&pool);
 412  Stream_write_string(f, ") {\n");
 413
 414  /* function body */
 415  if (function->flags & JSFUN_EXPR_CLOSURE) {
 416    /* expression closure - use output_statement instead of instrument_statement */
 417    if (node->pn_body->pn_type == TOK_SEQ) {
 418      assert(node->pn_body->pn_arity == PN_LIST);
 419      assert(node->pn_body->pn_count == 2);
 420      output_statement(node->pn_body->pn_head->pn_next, f, indent + 2, false);
 421    }
 422    else {
 423      output_statement(node->pn_body, f, indent + 2, false);
 424    }
 425  }
 426  else {
 427    assert(node->pn_body->pn_type == TOK_LC);
 428    assert(node->pn_body->pn_arity == PN_LIST);
 429    JSParseNode * p = node->pn_body->pn_head;
 430    if (destructuring) {
 431      p = p->pn_next;
 432    }
 433    for (; p != NULL; p = p->pn_next) {
 434      instrument_statement(p, f, indent + 2, false);
 435    }
 436  }
 437
 438  Stream_write_char(f, '}');
 439}
 440
 441static void instrument_function_call(JSParseNode * node, Stream * f) {
 442  JSParseNode * function_node = node->pn_head;
 443  if (function_node->pn_type == TOK_FUNCTION) {
 444    JSObject * object = function_node->pn_funpob->object;
 445    assert(JS_ObjectIsFunction(context, object));
 446    JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);
 447    assert(function);
 448    assert(object == &function->object);
 449
 450    if (function_node->pn_flags & TCF_GENEXP_LAMBDA) {
 451      /* it's a generator expression */
 452      Stream_write_char(f, '(');
 453      output_array_comprehension_or_generator_expression(function_node->pn_body, f);
 454      Stream_write_char(f, ')');
 455      return;
 456    }
 457  }
 458  output_expression(function_node, f, false);
 459  Stream_write_char(f, '(');
 460  for (struct JSParseNode * p = function_node->pn_next; p != NULL; p = p->pn_next) {
 461    if (p != node->pn_head->pn_next) {
 462      Stream_write_string(f, ", ");
 463    }
 464    output_expression(p, f, false);
 465  }
 466  Stream_write_char(f, ')');
 467}
 468
 469static void instrument_declarations(JSParseNode * list, Stream * f) {
 470  assert(list->pn_arity == PN_LIST);
 471  for (JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) {
 472    if (p != list->pn_head) {
 473      Stream_write_string(f, ", ");
 474    }
 475    output_expression(p, f, false);
 476  }
 477}
 478
 479/*
 480See <Expressions> in jsparse.h.
 481TOK_FUNCTION is handled as a statement and as an expression.
 482TOK_DBLDOT is not handled (XML op).
 483TOK_DEFSHARP and TOK_USESHARP are not handled.
 484TOK_ANYNAME is not handled (XML op).
 485TOK_AT is not handled (XML op).
 486TOK_DBLCOLON is not handled.
 487TOK_XML* are not handled.
 488There seem to be some undocumented expressions:
 489TOK_INSTANCEOF  binary
 490TOK_IN          binary
 491*/
 492static void output_expression(JSParseNode * node, Stream * f, bool parenthesize_object_literals) {
 493  switch (node->pn_type) {
 494  case TOK_FUNCTION:
 495    Stream_write_char(f, '(');
 496    instrument_function(node, f, 0, FUNCTION_NORMAL);
 497    Stream_write_char(f, ')');
 498    break;
 499  case TOK_COMMA:
 500    for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
 501      if (p != node->pn_head) {
 502        Stream_write_string(f, ", ");
 503      }
 504      output_expression(p, f, parenthesize_object_literals);
 505    }
 506    break;
 507  case TOK_ASSIGN:
 508    output_expression(node->pn_left, f, parenthesize_object_literals);
 509    Stream_write_char(f, ' ');
 510    switch (node->pn_op) {
 511    case JSOP_ADD:
 512    case JSOP_SUB:
 513    case JSOP_MUL:
 514    case JSOP_MOD:
 515    case JSOP_LSH:
 516    case JSOP_RSH:
 517    case JSOP_URSH:
 518    case JSOP_BITAND:
 519    case JSOP_BITOR:
 520    case JSOP_BITXOR:
 521    case JSOP_DIV:
 522      Stream_printf(f, "%s", get_op(node->pn_op));
 523      break;
 524    default:
 525      /* do nothing - it must be a simple assignment */
 526      break;
 527    }
 528    Stream_write_string(f, "= ");
 529    output_expression(node->pn_right, f, false);
 530    break;
 531  case TOK_HOOK:
 532    output_expression(node->pn_kid1, f, parenthesize_object_literals);
 533    Stream_write_string(f, "? ");
 534    output_expression(node->pn_kid2, f, false);
 535    Stream_write_string(f, ": ");
 536    output_expression(node->pn_kid3, f, false);
 537    break;
 538  case TOK_OR:
 539  case TOK_AND:
 540  case TOK_BITOR:
 541  case TOK_BITXOR:
 542  case TOK_BITAND:
 543  case TOK_EQOP:
 544  case TOK_RELOP:
 545  case TOK_SHOP:
 546  case TOK_PLUS:
 547  case TOK_MINUS:
 548  case TOK_STAR:
 549  case TOK_DIVOP:
 550    switch (node->pn_arity) {
 551    case PN_BINARY:
 552      output_expression(node->pn_left, f, parenthesize_object_literals);
 553      Stream_printf(f, " %s ", get_op(node->pn_op));
 554      output_expression(node->pn_right, f, false);
 555      break;
 556    case PN_LIST:
 557      for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
 558        if (p == node->pn_head) {
 559          output_expression(p, f, parenthesize_object_literals);
 560        }
 561        else {
 562          Stream_printf(f, " %s ", get_op(node->pn_op));
 563          output_expression(p, f, false);
 564        }
 565      }
 566      break;
 567    default:
 568      abort();
 569    }
 570    break;
 571  case TOK_UNARYOP:
 572    switch (node->pn_op) {
 573    case JSOP_NEG:
 574      Stream_write_string(f, "- ");
 575      output_expression(node->pn_kid, f, false);
 576      break;
 577    case JSOP_POS:
 578      Stream_write_string(f, "+ ");
 579      output_expression(node->pn_kid, f, false);
 580      break;
 581    case JSOP_NOT:
 582      Stream_write_string(f, "! ");
 583      output_expression(node->pn_kid, f, false);
 584      break;
 585    case JSOP_BITNOT:
 586      Stream_write_string(f, "~ ");
 587      output_expression(node->pn_kid, f, false);
 588      break;
 589    case JSOP_TYPEOF:
 590      Stream_write_string(f, "typeof ");
 591      output_expression(node->pn_kid, f, false);
 592      break;
 593    case JSOP_VOID:
 594      Stream_write_string(f, "void ");
 595      output_expression(node->pn_kid, f, false);
 596      break;
 597    default:
 598      fatal_source(file_id, node->pn_pos.begin.lineno, "unknown operator (%d)", node->pn_op);
 599      break;
 600    }
 601    break;
 602  case TOK_INC:
 603  case TOK_DEC:
 604    /*
 605    This is not documented, but node->pn_op tells whether it is pre- or post-increment.
 606    */
 607    switch (node->pn_op) {
 608    case JSOP_INCNAME:
 609    case JSOP_INCPROP:
 610    case JSOP_INCELEM:
 611      Stream_write_string(f, "++");
 612      output_expression(node->pn_kid, f, false);
 613      break;
 614    case JSOP_DECNAME:
 615    case JSOP_DECPROP:
 616    case JSOP_DECELEM:
 617      Stream_write_string(f, "--");
 618      output_expression(node->pn_kid, f, false);
 619      break;
 620    case JSOP_NAMEINC:
 621    case JSOP_PROPINC:
 622    case JSOP_ELEMINC:
 623      output_expression(node->pn_kid, f, parenthesize_object_literals);
 624      Stream_write_string(f, "++");
 625      break;
 626    case JSOP_NAMEDEC:
 627    case JSOP_PROPDEC:
 628    case JSOP_ELEMDEC:
 629      output_expression(node->pn_kid, f, parenthesize_object_literals);
 630      Stream_write_string(f, "--");
 631      break;
 632    default:
 633      abort();
 634      break;
 635    }
 636    break;
 637  case TOK_NEW:
 638    Stream_write_string(f, "new ");
 639    instrument_function_call(node, f);
 640    break;
 641  case TOK_DELETE:
 642    Stream_write_string(f, "delete ");
 643    output_expression(node->pn_kid, f, false);
 644    break;
 645  case TOK_DOT:
 646    /* numeric literals must be parenthesized */
 647    switch (node->pn_expr->pn_type) {
 648    case TOK_NUMBER:
 649      Stream_write_char(f, '(');
 650      output_expression(node->pn_expr, f, false);
 651      Stream_write_char(f, ')');
 652      break;
 653    default:
 654      output_expression(node->pn_expr, f, true);
 655      break;
 656    }
 657    /*
 658    This may have originally been x['foo-bar'].  Because the string 'foo-bar'
 659    contains illegal characters, we have to use the subscript syntax instead of
 660    the dot syntax.
 661    */
 662    assert(ATOM_IS_STRING(node->pn_atom));
 663    {
 664      JSString * s = ATOM_TO_STRING(node->pn_atom);
 665      bool must_quote;
 666      if (JSSTRING_LENGTH(s) == 0) {
 667        must_quote = true;
 668      }
 669      else if (js_CheckKeyword(JSSTRING_CHARS(s), JSSTRING_LENGTH(s)) != TOK_EOF) {
 670        must_quote = true;
 671      }
 672      else if (! js_IsIdentifier(s)) {
 673        must_quote = true;
 674      }
 675      else {
 676        must_quote = false;
 677      }
 678      if (must_quote) {
 679        Stream_write_char(f, '[');
 680        print_quoted_string_atom(node->pn_atom, f);
 681        Stream_write_char(f, ']');
 682      }
 683      else {
 684        Stream_write_char(f, '.');
 685        print_string_atom(node->pn_atom, f);
 686      }
 687    }
 688    break;
 689  case TOK_LB:
 690    output_expression(node->pn_left, f, false);
 691    Stream_write_char(f, '[');
 692    output_expression(node->pn_right, f, false);
 693    Stream_write_char(f, ']');
 694    break;
 695  case TOK_LP:
 696    instrument_function_call(node, f);
 697    break;
 698  case TOK_RB:
 699    Stream_write_char(f, '[');
 700    for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
 701      if (p != node->pn_head) {
 702        Stream_write_string(f, ", ");
 703      }
 704      /* TOK_COMMA is a special case: a hole in the array */
 705      if (p->pn_type != TOK_COMMA) {
 706        output_expression(p, f, false);
 707      }
 708    }
 709    if (node->pn_extra == PNX_ENDCOMMA) {
 710      Stream_write_char(f, ',');
 711    }
 712    Stream_write_char(f, ']');
 713    break;
 714  case TOK_RC:
 715    if (parenthesize_object_literals) {
 716      Stream_write_char(f, '(');
 717    }
 718    Stream_write_char(f, '{');
 719    for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
 720      if (p->pn_type != TOK_COLON) {
 721        fatal_source(file_id, p->pn_pos.begin.lineno, "unsupported node type (%d)", p->pn_type);
 722      }
 723      if (p != node->pn_head) {
 724        Stream_write_string(f, ", ");
 725      }
 726
 727      /* check whether this is a getter or setter */
 728      switch (p->pn_op) {
 729      case JSOP_GETTER:
 730      case JSOP_SETTER:
 731        if (p->pn_op == JSOP_GETTER) {
 732          Stream_write_string(f, "get ");
 733        }
 734        else {
 735          Stream_write_string(f, "set ");
 736        }
 737        output_expression(p->pn_left, f, false);
 738        Stream_write_char(f, ' ');
 739        if (p->pn_right->pn_type != TOK_FUNCTION) {
 740          fatal_source(file_id, p->pn_pos.begin.lineno, "expected function");
 741        }
 742        instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER);
 743        break;
 744      default:
 745        output_expression(p->pn_left, f, false);
 746        Stream_write_string(f, ": ");
 747        output_expression(p->pn_right, f, false);
 748        break;
 749      }
 750    }
 751    Stream_write_char(f, '}');
 752    if (parenthesize_object_literals) {
 753      Stream_write_char(f, ')');
 754    }
 755    break;
 756  case TOK_RP:
 757    Stream_write_char(f, '(');
 758    output_expression(node->pn_kid, f, false);
 759    Stream_write_char(f, ')');
 760    break;
 761  case TOK_NAME:
 762    print_string_atom(node->pn_atom, f);
 763    if (node->pn_expr != NULL) {
 764      Stream_write_string(f, " = ");
 765      output_expression(node->pn_expr, f, false);
 766    }
 767    break;
 768  case TOK_STRING:
 769    print_quoted_string_atom(node->pn_atom, f);
 770    break;
 771  case TOK_REGEXP:
 772    assert(node->pn_op == JSOP_REGEXP);
 773    {
 774      JSObject * object = node->pn_pob->object;
 775      jsval result;
 776      js_regexp_toString(context, object, &result);
 777      print_regex(result, f);
 778    }
 779    break;
 780  case TOK_NUMBER:
 781    /*
 782    A 64-bit IEEE 754 floating point number has a 52-bit fraction.
 783    2^(-52) = 2.22 x 10^(-16)
 784    Thus there are 16 significant digits.
 785    To keep the output simple, special-case zero.
 786    */
 787    if (node->pn_dval == 0.0) {
 788      if (signbit(node->pn_dval)) {
 789        Stream_write_string(f, "-0");
 790      }
 791      else {
 792        Stream_write_string(f, "0");
 793      }
 794    }
 795    else if (node->pn_dval == INFINITY) {
 796      Stream_write_string(f, "Number.POSITIVE_INFINITY");
 797    }
 798    else if (node->pn_dval == -INFINITY) {
 799      Stream_write_string(f, "Number.NEGATIVE_INFINITY");
 800    }
 801    else if (isnan(node->pn_dval)) {
 802      Stream_write_string(f, "Number.NaN");
 803    }
 804    else {
 805      Stream_printf(f, "%.15g", node->pn_dval);
 806    }
 807    break;
 808  case TOK_PRIMARY:
 809    switch (node->pn_op) {
 810    case JSOP_TRUE:
 811      Stream_write_string(f, "true");
 812      break;
 813    case JSOP_FALSE:
 814      Stream_write_string(f, "false");
 815      break;
 816    case JSOP_NULL:
 817      Stream_write_string(f, "null");
 818      break;
 819    case JSOP_THIS:
 820      Stream_write_string(f, "this");
 821      break;
 822    /* jsscan.h mentions `super' ??? */
 823    default:
 824      abort();
 825    }
 826    break;
 827  case TOK_INSTANCEOF:
 828    output_expression(node->pn_left, f, parenthesize_object_literals);
 829    Stream_write_string(f, " instanceof ");
 830    output_expression(node->pn_right, f, false);
 831    break;
 832  case TOK_IN:
 833    output_expression(node->pn_left, f, false);
 834    Stream_write_string(f, " in ");
 835    output_expression(node->pn_right, f, false);
 836    break;
 837  case TOK_LEXICALSCOPE:
 838    assert(node->pn_arity == PN_NAME);
 839    assert(node->pn_expr->pn_type == TOK_LET);
 840    assert(node->pn_expr->pn_arity == PN_BINARY);
 841    Stream_write_string(f, "let(");
 842    assert(node->pn_expr->pn_left->pn_type == TOK_LP);
 843    assert(node->pn_expr->pn_left->pn_arity == PN_LIST);
 844    instrument_declarations(node->pn_expr->pn_left, f);
 845    Stream_write_string(f, ") ");
 846    output_expression(node->pn_expr->pn_right, f, true);
 847    break;
 848  case TOK_YIELD:
 849    assert(node->pn_arity == PN_UNARY);
 850    Stream_write_string(f, "yield");
 851    if (node->pn_kid != NULL) {
 852      Stream_write_char(f, ' ');
 853      output_expression(node->pn_kid, f, true);
 854    }
 855    break;
 856  case TOK_ARRAYCOMP:
 857    assert(node->pn_arity == PN_LIST);
 858    {
 859      JSParseNode * block_node;
 860      switch (node->pn_count) {
 861      case 1:
 862        block_node = node->pn_head;
 863        break;
 864      case 2:
 865        block_node = node->pn_head->pn_next;
 866        break;
 867      default:
 868        abort();
 869        break;
 870      }
 871      Stream_write_char(f, '[');
 872      output_array_comprehension_or_generator_expression(block_node, f);
 873      Stream_write_char(f, ']');
 874    }
 875    break;
 876  case TOK_VAR:
 877    assert(node->pn_arity == PN_LIST);
 878    Stream_write_string(f, "var ");
 879    instrument_declarations(node, f);
 880    break;
 881  case TOK_LET:
 882    assert(node->pn_arity == PN_LIST);
 883    Stream_write_string(f, "let ");
 884    instrument_declarations(node, f);
 885    break;
 886  default:
 887    fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%d)", node->pn_type);
 888  }
 889}
 890
 891static void output_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
 892  switch (node->pn_type) {
 893  case TOK_FUNCTION:
 894    instrument_function(node, f, indent, FUNCTION_NORMAL);
 895    Stream_write_char(f, '\n');
 896    break;
 897  case TOK_LC:
 898    assert(node->pn_arity == PN_LIST);
 899/*
 900    Stream_write_string(f, "{\n");
 901*/
 902    for (struct JSParseNode * p = node->pn_u.list.head; p != NULL; p = p->pn_next) {
 903      instrument_statement(p, f, indent, false);
 904    }
 905/*
 906    Stream_printf(f, "%*s", indent, "");
 907    Stream_write_string(f, "}\n");
 908*/
 909    break;
 910  case TOK_IF:
 911  {
 912    assert(node->pn_arity == PN_TERNARY);
 913
 914    uint16_t line = node->pn_pos.begin.lineno;
 915    if (! is_jscoverage_if) {
 916      if (line > num_lines) {
 917        fatal("file %s contains more than 65,535 lines", file_id);
 918      }
 919      if (line >= 2 && exclusive_directives[line - 2]) {
 920        is_jscoverage_if = true;
 921      }
 922    }
 923
 924    Stream_printf(f, "%*s", indent, "");
 925    Stream_write_string(f, "if (");
 926    output_expression(node->pn_kid1, f, false);
 927    Stream_write_string(f, ") {\n");
 928    if (is_jscoverage_if && node->pn_kid3) {
 929      uint16_t else_start = node->pn_kid3->pn_pos.begin.lineno;
 930      uint16_t else_end = node->pn_kid3->pn_pos.end.lineno + 1;
 931      Stream_printf(f, "%*s", indent + 2, "");
 932      Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, else_start, else_end);
 933    }
 934    instrument_statement(node->pn_kid2, f, indent + 2, false);
 935    Stream_printf(f, "%*s", indent, "");
 936    Stream_write_string(f, "}\n");
 937
 938    if (node->pn_kid3 || is_jscoverage_if) {
 939      Stream_printf(f, "%*s", indent, "");
 940      Stream_write_string(f, "else {\n");
 941
 942      if (is_jscoverage_if) {
 943        uint16_t if_start = node->pn_kid2->pn_pos.begin.lineno + 1;
 944        uint16_t if_end = node->pn_kid2->pn_pos.end.lineno + 1;
 945        Stream_printf(f, "%*s", indent + 2, "");
 946        Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_start, if_end);
 947      }
 948
 949      if (node->pn_kid3) {
 950        instrument_statement(node->pn_kid3, f, indent + 2, is_jscoverage_if);
 951      }
 952
 953      Stream_printf(f, "%*s", indent, "");
 954      Stream_write_string(f, "}\n");
 955    }
 956
 957    break;
 958  }
 959  case TOK_SWITCH:
 960    assert(node->pn_arity == PN_BINARY);
 961    Stream_printf(f, "%*s", indent, "");
 962    Stream_write_string(f, "switch (");
 963    output_expression(node->pn_left, f, false);
 964    Stream_write_string(f, ") {\n");
 965    {
 966      JSParseNode * list = node->pn_right;
 967      if (list->pn_type == TOK_LEXICALSCOPE) {
 968        list = list->pn_expr;
 969      }
 970      for (struct JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) {
 971        Stream_printf(f, "%*s", indent, "");
 972        switch (p->pn_type) {
 973        case TOK_CASE:
 974          Stream_write_string(f, "case ");
 975          output_expression(p->pn_left, f, false);
 976          Stream_write_string(f, ":\n");
 977          break;
 978        case TOK_DEFAULT:
 979          Stream_write_string(f, "default:\n");
 980          break;
 981        default:
 982          abort();
 983          break;
 984        }
 985        instrument_statement(p->pn_right, f, indent + 2, false);
 986      }
 987    }
 988    Stream_printf(f, "%*s", indent, "");
 989    Stream_write_string(f, "}\n");
 990    break;
 991  case TOK_CASE:
 992  case TOK_DEFAULT:
 993    abort();
 994    break;
 995  case TOK_WHILE:
 996    assert(node->pn_arity == PN_BINARY);
 997    Stream_printf(f, "%*s", indent, "");
 998    Stream_write_string(f, "while (");
 999    output_expression(node->pn_left, f, false);
1000    Stream_write_string(f, ") {\n");
1001    instrument_statement(node->pn_right, f, indent + 2, false);
1002    Stream_write_string(f, "}\n");
1003    break;
1004  case TOK_DO:
1005    assert(node->pn_arity == PN_BINARY);
1006    Stream_printf(f, "%*s", indent, "");
1007    Stream_write_string(f, "do {\n");
1008    instrument_statement(node->pn_left, f, indent + 2, false);
1009    Stream_write_string(f, "}\n");
1010    Stream_printf(f, "%*s", indent, "");
1011    Stream_write_string(f, "while (");
1012    output_expression(node->pn_right, f, false);
1013    Stream_write_string(f, ");\n");
1014    break;
1015  case TOK_FOR:
1016    assert(node->pn_arity == PN_BINARY);
1017    Stream_printf(f, "%*s", indent, "");
1018    switch (node->pn_left->pn_type) {
1019    case TOK_IN:
1020      /* for/in */
1021      assert(node->pn_left->pn_arity == PN_BINARY);
1022      output_for_in(node, f);
1023      break;
1024    case TOK_FORHEAD:
1025      /* for (;;) */
1026      assert(node->pn_left->pn_arity == PN_TERNARY);
1027      Stream_write_string(f, "for (");
1028      if (node->pn_left->pn_kid1) {
1029        output_expression(node->pn_left->pn_kid1, f, false);
1030      }
1031      Stream_write_string(f, ";");
1032      if (node->pn_left->pn_kid2) {
1033        Stream_write_char(f, ' ');
1034        output_expression(node->pn_left->pn_kid2, f, false);
1035      }
1036      Stream_write_string(f, ";");
1037      if (node->pn_left->pn_kid3) {
1038        Stream_write_char(f, ' ');
1039        output_expression(node->pn_left->pn_kid3, f, false);
1040      }
1041      Stream_write_char(f, ')');
1042      break;
1043    default:
1044      abort();
1045      break;
1046    }
1047    Stream_write_string(f, " {\n");
1048    instrument_statement(node->pn_right, f, indent + 2, false);
1049    Stream_write_string(f, "}\n");
1050    break;
1051  case TOK_THROW:
1052    assert(node->pn_arity == PN_UNARY);
1053    Stream_printf(f, "%*s", indent, "");
1054    Stream_write_string(f, "throw ");
1055    output_expression(node->pn_u.unary.kid, f, false);
1056    Stream_write_string(f, ";\n");
1057    break;
1058  case TOK_TRY:
1059    Stream_printf(f, "%*s", indent, "");
1060    Stream_write_string(f, "try {\n");
1061    instrument_statement(node->pn_kid1, f, indent + 2, false);
1062    Stream_printf(f, "%*s", indent, "");
1063    Stream_write_string(f, "}\n");
1064    if (node->pn_kid2) {
1065      assert(node->pn_kid2->pn_type == TOK_RESERVED);
1066      for (JSParseNode * scope = node->pn_kid2->pn_head; scope != NULL; scope = scope->pn_next) {
1067        assert(scope->pn_type == TOK_LEXICALSCOPE);
1068        JSParseNode * catch_node = scope->pn_expr;
1069        assert(catch_node->pn_type == TOK_CATCH);
1070        Stream_printf(f, "%*s", indent, "");
1071        Stream_write_string(f, "catch (");
1072        output_expression(catch_node->pn_kid1, f, false);
1073        if (catch_node->pn_kid2) {
1074          Stream_write_string(f, " if ");
1075          output_expression(catch_node->pn_kid2, f, false);
1076        }
1077        Stream_write_string(f, ") {\n");
1078        instrument_statement(catch_node->pn_kid3, f, indent + 2, false);
1079        Stream_printf(f, "%*s", indent, "");
1080        Stream_write_string(f, "}\n");
1081      }
1082    }
1083    if (node->pn_kid3) {
1084      Stream_printf(f, "%*s", indent, "");
1085      Stream_write_string(f, "finally {\n");
1086      instrument_statement(node->pn_kid3, f, indent + 2, false);
1087      Stream_printf(f, "%*s", indent, "");
1088      Stream_write_string(f, "}\n");
1089    }
1090    break;
1091  case TOK_CATCH:
1092    abort();
1093    break;
1094  case TOK_BREAK:
1095  case TOK_CONTINUE:
1096    assert(node->pn_arity == PN_NAME || node->pn_arity == PN_NULLARY);
1097    Stream_printf(f, "%*s", indent, "");
1098    Stream_write_string(f, node->pn_type == TOK_BREAK? "break": "continue");
1099    if (node->pn_atom != NULL) {
1100      Stream_write_char(f, ' ');
1101      print_string_atom(node->pn_atom, f);
1102    }
1103    Stream_write_string(f, ";\n");
1104    break;
1105  case TOK_WITH:
1106    assert(node->pn_arity == PN_BINARY);
1107    Stream_printf(f, "%*s", indent, "");
1108    Stream_write_string(f, "with (");
1109    output_expression(node->pn_left, f, false);
1110    Stream_write_string(f, ") {\n");
1111    instrument_statement(node->pn_right, f, indent + 2, false);
1112    Stream_printf(f, "%*s", indent, "");
1113    Stream_write_string(f, "}\n");
1114    break;
1115  case TOK_VAR:
1116    Stream_printf(f, "%*s", indent, "");
1117    output_expression(node, f, false);
1118    Stream_write_string(f, ";\n");
1119    break;
1120  case TOK_RETURN:
1121    assert(node->pn_arity == PN_UNARY);
1122    Stream_printf(f, "%*s", indent, "");
1123    Stream_write_string(f, "return");
1124    if (node->pn_kid != NULL) {
1125      Stream_write_char(f, ' ');
1126      output_expression(node->pn_kid, f, true);
1127    }
1128    Stream_write_string(f, ";\n");
1129    break;
1130  case TOK_SEMI:
1131    assert(node->pn_arity == PN_UNARY);
1132    Stream_printf(f, "%*s", indent, "");
1133    if (node->pn_kid != NULL) {
1134      output_expression(node->pn_kid, f, true);
1135    }
1136    Stream_write_string(f, ";\n");
1137    break;
1138  case TOK_COLON:
1139  {
1140    assert(node->pn_arity == PN_NAME);
1141    Stream_printf(f, "%*s", indent < 2? 0: indent - 2, "");
1142    print_string_atom(node->pn_atom, f);
1143    Stream_write_string(f, ":\n");
1144    JSParseNode * labelled = node->pn_expr;
1145    if (labelled->pn_type == TOK_LEXICALSCOPE) {
1146      labelled = labelled->pn_expr;
1147    }
1148    if (labelled->pn_type == TOK_LC) {
1149      /* labelled block */
1150      Stream_printf(f, "%*s", indent, "");
1151      Stream_write_string(f, "{\n");
1152      instrument_statement(labelled, f, indent + 2, false);
1153      Stream_printf(f, "%*s", indent, "");
1154      Stream_write_string(f, "}\n");
1155    }
1156    else {
1157      /*
1158      This one is tricky: can't output instrumentation between the label and the
1159      statement it's supposed to label, so use output_statement instead of
1160      instrument_statement.
1161      */
1162      output_statement(labelled, f, indent, false);
1163    }
1164    break;
1165  }
1166  case TOK_LEXICALSCOPE:
1167    /* let statement */
1168    assert(node->pn_arity == PN_NAME);
1169    switch (node->pn_expr->pn_type) {
1170    case TOK_LET:
1171      /* let statement */
1172      assert(node->pn_expr->pn_arity == PN_BINARY);
1173      instrument_statement(node->pn_expr, f, indent, false);
1174      break;
1175    case TOK_LC:
1176      /* block */
1177      Stream_printf(f, "%*s", indent, "");
1178      Stream_write_string(f, "{\n");
1179      instrument_statement(node->pn_expr, f, indent + 2, false);
1180      Stream_printf(f, "%*s", indent, "");
1181      Stream_write_string(f, "}\n");
1182      break;
1183    case TOK_FOR:
1184      instrument_statement(node->pn_expr, f, indent, false);
1185      break;
1186    default:
1187      abort();
1188      break;
1189    }
1190    break;
1191  case TOK_LET:
1192    switch (node->pn_arity) {
1193    case PN_BINARY:
1194      /* let statement */
1195      Stream_printf(f, "%*s", indent, "");
1196      Stream_write_string(f, "let (");
1197      assert(node->pn_left->pn_type == TOK_LP);
1198      assert(node->pn_left->pn_arity == PN_LIST);
1199      instrument_declarations(node->pn_left, f);
1200      Stream_write_string(f, ") {\n");
1201      instrument_statement(node->pn_right, f, indent + 2, false);
1202      Stream_printf(f, "%*s", indent, "");
1203      Stream_write_string(f, "}\n");
1204      break;
1205    case PN_LIST:
1206      /* let definition */
1207      Stream_printf(f, "%*s", indent, "");
1208      output_expression(node, f, false);
1209      Stream_write_string(f, ";\n");
1210      break;
1211    default:
1212      abort();
1213      break;
1214    }
1215    break;
1216  case TOK_DEBUGGER:
1217    Stream_printf(f, "%*s", indent, "");
1218    Stream_write_string(f, "debugger;\n");
1219    break;
1220  case TOK_SEQ:
1221    /*
1222    This occurs with the statement:
1223    for (var a = b in c) {}
1224    */
1225    assert(node->pn_arity == PN_LIST);
1226    for (JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
1227      instrument_statement(p, f, indent, false);
1228    }
1229    break;
1230  default:
1231    fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%d)", node->pn_type);
1232  }
1233}
1234
1235/*
1236See <Statements> in jsparse.h.
1237TOK_FUNCTION is handled as a statement and as an expression.
1238TOK_EXPORT, TOK_IMPORT are not handled.
1239*/
1240static void instrument_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
1241  if (node->pn_type != TOK_LC && node->pn_type != TOK_LEXICALSCOPE) {
1242    uint16_t line = node->pn_pos.begin.lineno;
1243    if (line > num_lines) {
1244      fatal("file %s contains more than 65,535 lines", file_id);
1245    }
1246
1247    /* the root node has line number 0 */
1248    if (line != 0) {
1249      Stream_printf(f, "%*s", indent, "");
1250      Stream_printf(f, "_$jscoverage['%s'][%d]++;\n", file_id, line);
1251      lines[line - 1] = 1;
1252    }
1253  }
1254  output_statement(node, f, indent, is_jscoverage_if);
1255}
1256
1257static bool characters_start_with(const jschar * characters, size_t line_start, size_t line_end, const char * prefix) {
1258  const jschar * characters_end = characters + line_end;
1259  const jschar * cp = characters + line_start;
1260  const char * bp = prefix;
1261  for (;;) {
1262    if (*bp == '\0') {
1263      return true;
1264    }
1265    else if (cp == characters_end) {
1266      return false;
1267    }
1268    else if (*cp != *bp) {
1269      return false;
1270    }
1271    bp++;
1272    cp++;
1273  }
1274}
1275
1276static bool characters_are_white_space(const jschar * characters, size_t line_start, size_t line_end) {
1277  /* XXX - other Unicode space */
1278  const jschar * end = characters + line_end;
1279  for (const jschar * p = characters + line_start; p < end; p++) {
1280    jschar c = *p;
1281    if (c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0) {
1282      continue;
1283    }
1284    else {
1285      return false;
1286    }
1287  }
1288  return true;
1289}
1290
1291static void error_reporter(JSContext * context, const char * message, JSErrorReport * report) {
1292  warn_source(file_id, report->lineno, "%s", message);
1293}
1294
1295void jscoverage_instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output) {
1296  file_id = id;
1297
1298  /* parse the javascript */
1299  JSParseContext parse_context;
1300  if (! js_InitParseContext(context, &parse_context, NULL, NULL, characters, num_characters, NULL, NULL, 1)) {
1301    fatal("cannot create token stream from file %s", file_id);
1302  }
1303  JSErrorReporter old_error_reporter = JS_SetErrorReporter(context, error_reporter);
1304  JSParseNode * node = js_ParseScript(context, global, &parse_context);
1305  if (node == NULL) {
1306    js_ReportUncaughtException(context);
1307    fatal("parse error in file %s", file_id);
1308  }
1309  JS_SetErrorReporter(context, old_error_reporter);
1310  num_lines = node->pn_pos.end.lineno;
1311  lines = (char *) xmalloc(num_lines);
1312  for (unsigned int i = 0; i < num_lines; i++) {
1313    lines[i] = 0;
1314  }
1315
1316  /* search code for conditionals */
1317  exclusive_directives = xnew(bool, num_lines);
1318  for (unsigned int i = 0; i < num_lines; i++) {
1319    exclusive_directives[i] = false;
1320  }
1321
1322  bool has_conditionals = false;
1323  struct IfDirective * if_directives = NULL;
1324  size_t line_number = 0;
1325  size_t i = 0;
1326  while (i < num_characters) {
1327    if (line_number == UINT16_MAX) {
1328      fatal("file %s contains more than 65,535 lines", file_id);
1329    }
1330    line_number++;
1331    size_t line_start = i;
1332    jschar c;
1333    bool done = false;
1334    while (! done && i < num_characters) {
1335      c = characters[i];
1336      switch (c) {
1337      case '\r':
1338      case '\n':
1339      case 0x2028:
1340      case 0x2029:
1341        done = true;
1342        break;
1343      default:
1344        i++;
1345      }
1346    }
1347    size_t line_end = i;
1348    if (i < num_characters) {
1349      i++;
1350      if (c == '\r' && i < num_characters && characters[i] == '\n') {
1351        i++;
1352      }
1353    }
1354
1355    if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_IF")) {
1356      has_conditionals = true;
1357
1358      if (characters_are_white_space(characters, line_start + 16, line_end)) {
1359        exclusive_directives[line_number - 1] = true;
1360      }
1361      else {
1362        struct IfDirective * if_directive = xnew(struct IfDirective, 1);
1363        if_directive->condition_start = characters + line_start + 16;
1364        if_directive->condition_end = characters + line_end;
1365        if_directive->start_line = line_number;
1366        if_directive->end_line = 0;
1367        if_directive->next = if_directives;
1368        if_directives = if_directive;
1369      }
1370    }
1371    else if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_ENDIF")) {
1372      for (struct IfDirective * p = if_directives; p != NULL; p = p->next) {
1373        if (p->end_line == 0) {
1374          p->end_line = line_number;
1375          break;
1376        }
1377      }
1378    }
1379  }
1380
1381  /*
1382  An instrumented JavaScript file has 4 sections:
1383  1. initialization
1384  2. instrumented source code
1385  3. conditionals
1386  4. original source code
1387  */
1388
1389  Stream * instrumented = Stream_new(0);
1390  instrument_statement(node, instrumented, 0, false);
1391  js_FinishParseContext(context, &parse_context);
1392
1393  /* write line number info to the output */
1394  Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n");
1395	Stream_write_string(output, "if (typeof _$jscoverage === 'undefined') _$jscoverage = {};\n");
1396  Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id);
1397  Stream_printf(output, "  _$jscoverage['%s'] = [];\n", file_id);
1398  for (int i = 0; i < num_lines; i++) {
1399    if (lines[i]) {
1400      Stream_printf(output, "  _$jscoverage['%s'][%d] = 0;\n", file_id, i + 1);
1401    }
1402  }
1403  Stream_write_string(output, "}\n");
1404  free(lines);
1405  lines = NULL;
1406  free(exclusive_directives);
1407  exclusive_directives = NULL;
1408
1409  /* conditionals */
1410  if (has_conditionals) {
1411    Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);
1412  }
1413
1414  /* copy the instrumented source code to the output */
1415  Stream_write(output, instrumented->data, instrumented->length);
1416
1417  /* conditionals */
1418  for (struct IfDirective * if_directive = if_directives; if_directive != NULL; if_directive = if_directive->next) {
1419    Stream_write_string(output, "if (!(");
1420    print_javascript(if_directive->condition_start, if_directive->condition_end - if_directive->condition_start, output);
1421    Stream_write_string(output, ")) {\n");
1422    Stream_printf(output, "  _$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_directive->start_line, if_directive->end_line);
1423    Stream_write_string(output, "}\n");
1424  }
1425
1426  /* free */
1427  while (if_directives != NULL) {
1428    struct IfDirective * if_directive = if_directives;
1429    if_directives = if_directives->next;
1430    free(if_directive);
1431  }
1432
1433  /* copy the original source to the output */
1434  Stream_printf(output, "_$jscoverage['%s'].source = ", file_id);
1435  jscoverage_write_source(id, characters, num_characters, output);
1436  Stream_printf(output, ";\n");
1437
1438  Stream_delete(instrumented);
1439
1440  file_id = NULL;
1441}
1442
1443void jscoverage_write_source(const char * id, const jschar * characters, size_t num_characters, Stream * output) {
1444  Stream_write_string(output, "[");
1445  if (jscoverage_highlight) {
1446    Stream * highlighted_stream = Stream_new(num_characters);
1447    jscoverage_highlight_js(context, id, characters, num_characters, highlighted_stream);
1448    size_t i = 0;
1449    while (i < highlighted_stream->length) {
1450      if (i > 0) {
1451        Stream_write_char(output, ',');
1452      }
1453
1454      Stream_write_char(output, '"');
1455      bool done = false;
1456      while (! done) {
1457        char c = highlighted_stream->data[i];
1458        switch (c) {
1459        case 0x8:
1460          /* backspace */
1461          Stream_write_string(output, "\\b");
1462          break;
1463        case 0x9:
1464          /* horizontal tab */
1465          Stream_write_string(output, "\\t");
1466          break;
1467        case 0xa:
1468          /* line feed (new line) */
1469          done = true;
1470          break;
1471        /* IE doesn't support this */
1472        /*
1473        case 0xb:
1474          Stream_write_string(output, "\\v");
1475          break;
1476        */
1477        case 0xc:
1478          /* form feed */
1479          Stream_write_string(output, "\\f");
1480          break;
1481        case 0xd:
1482          /* carriage return */
1483          done = true;
1484          if (i + 1 < highlighted_stream->length && highlighted_stream->data[i + 1] == '\n') {
1485            i++;
1486          }
1487          break;
1488        case '"':
1489          Stream_write_string(output, "\\\"");
1490          break;
1491        case '\\':
1492          Stream_write_string(output, "\\\\");
1493          break;
1494        default:
1495          Stream_write_char(output, c);
1496          break;
1497        }
1498        i++;
1499        if (i >= highlighted_stream->length) {
1500          done = true;
1501        }
1502      }
1503      Stream_write_char(output, '"');
1504    }
1505    Stream_delete(highlighted_stream);
1506  }
1507  else {
1508    size_t i = 0;
1509    while (i < num_characters) {
1510      if (i > 0) {
1511        Stream_write_char(output, ',');
1512      }
1513
1514      Stream_write_char(output, '"');
1515      bool done = false;
1516      while (! done) {
1517        jschar c = characters[i];
1518        switch (c) {
1519        case 0x8:
1520          /* backspace */
1521          Stream_write_string(output, "\\b");
1522          break;
1523        case 0x9:
1524          /* horizontal tab */
1525          Stream_write_string(output, "\\t");
1526          break;
1527        case 0xa:
1528          /* line feed (new line) */
1529          done = true;
1530          break;
1531        /* IE doesn't support this */
1532        /*
1533        case 0xb:
1534          Stream_write_string(output, "\\v");
1535          break;
1536        */
1537        case 0xc:
1538          /* form feed */
1539          Stream_write_string(output, "\\f");
1540          break;
1541        case 0xd:
1542          /* carriage return */
1543          done = true;
1544          if (i + 1 < num_characters && characters[i + 1] == '\n') {
1545            i++;
1546          }
1547          break;
1548        case '"':
1549          Stream_write_string(output, "\\\"");
1550          break;
1551        case '\\':
1552          Stream_write_string(output, "\\\\");
1553          break;
1554        case '&':
1555          Stream_write_string(output, "&amp;");
1556          break;
1557        case '<':
1558          Stream_write_string(output, "&lt;");
1559          break;
1560        case '>':
1561          Stream_write_string(output, "&gt;");
1562          break;
1563        case 0x2028:
1564        case 0x2029:
1565          done = true;
1566          break;
1567        default:
1568          if (32 <= c && c <= 126) {
1569            Stream_write_char(output, c);
1570          }
1571          else {
1572            Stream_printf(output, "&#%d;", c);
1573          }
1574          break;
1575        }
1576        i++;
1577        if (i >= num_characters) {
1578          done = true;
1579        }
1580      }
1581      Stream_write_char(output, '"');
1582    }
1583  }
1584  Stream_write_string(output, "]");
1585}
1586
1587void jscoverage_copy_resources(const char * destination_directory) {
1588  copy_resource("jscoverage.html", destination_directory);
1589  copy_resource("jscoverage.css", destination_directory);
1590  copy_resource("jscoverage.js", destination_directory);
1591  copy_resource("jscoverage-ie.css", destination_directory);
1592  copy_resource("jscoverage-throbber.gif", destination_directory);
1593  copy_resource("jscoverage-highlight.css", destination_directory);
1594}
1595
1596/*
1597coverage reports
1598*/
1599
1600struct FileCoverageList {
1601  FileCoverage * file_coverage;
1602  struct FileCoverageList * next;
1603};
1604
1605struct Coverage {
1606  JSHashTable * coverage_table;
1607  struct FileCoverageList * coverage_list;
1608};
1609
1610static int compare_strings(const void * p1, const void * p2) {
1611  return strcmp((const char *) p1, (const char *) p2) == 0;
1612}
1613
1614Coverage * Coverage_new(void) {
1615  Coverage * result = (Coverage *) xmalloc(sizeof(Coverage));
1616  result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL);
1617  if (result->coverage_table == NULL) {
1618    fatal("cannot create hash table");
1619  }
1620  result->coverage_list = NULL;
1621  return result;
1622}
1623
1624void Coverage_delete(Coverage * coverage) {
1625  JS_HashTableDestroy(coverage->coverage_table);
1626  struct FileCoverageList * p = coverage->coverage_list;
1627  while (p != NULL) {
1628    free(p->file_coverage->coverage_lines);
1629    if (p->file_coverage->source_lines != NULL) {
1630      for (uint32 i = 0; i < p->file_coverage->num_source_lines; i++) {
1631        free(p->file_coverage->source_lines[i]);
1632      }
1633      free(p->file_coverage->source_lines);
1634    }
1635    free(p->file_coverage->id);
1636    free(p->file_coverage);
1637    struct FileCoverageList * q = p;
1638    p = p->next;
1639    free(q);
1640  }
1641  free(coverage);
1642}
1643
1644struct EnumeratorArg {
1645  CoverageForeachFunction f;
1646  void * p;
1647};
1648
1649static intN enumerator(JSHashEntry * entry, intN i, void * arg) {
1650  struct EnumeratorArg * enumerator_arg = (struct EnumeratorArg *) arg;
1651  enumerator_arg->f((FileCoverage *) entry->value, i, enumerator_arg->p);
1652  return 0;
1653}
1654
1655void Coverage_foreach_file(Coverage * coverage, CoverageForeachFunction f, void * p) {
1656  struct EnumeratorArg enumerator_arg;
1657  enumerator_arg.f = f;
1658  enumerator_arg.p = p;
1659  JS_HashTableEnumerateEntries(coverage->coverage_table, enumerator, &enumerator_arg);
1660}
1661
1662int jscoverage_parse_json(Coverage * coverage, const uint8_t * json, size_t length) {
1663  int result = 0;
1664
1665  jschar * base = js_InflateString(context, (char *) json, &length);
1666  if (base == NULL) {
1667    fatal("out of memory");
1668  }
1669
1670  jschar * parenthesized_json = xnew(jschar, addst(length, 2));
1671  parenthesized_json[0] = '(';
1672  memcpy(parenthesized_json + 1, base, mulst(length, sizeof(jschar)));
1673  parenthesized_json[length + 1] = ')';
1674
1675  JS_free(context, base);
1676
1677  JSParseContext parse_context;
1678  if (! js_InitParseContext(context, &parse_context, NULL, NULL, parenthesized_json, length + 2, NULL, NULL, 1)) {
1679    free(parenthesized_json);
1680    return -1;
1681  }
1682  JSParseNode * root = js_ParseScript(context, global, &parse_context);
1683  free(parenthesized_json);
1684
1685  JSParseNode * semi = NULL;
1686  JSParseNode * object = NULL;
1687
1688  if (root == NULL) {
1689    result = -1;
1690    goto done;
1691  }
1692
1693  /* root node must be TOK_LC */
1694  if (root->pn_type != TOK_LC) {
1695    result = -1;
1696    goto done;
1697  }
1698  semi = root->pn_u.list.head;
1699
1700  /* the list must be TOK_SEMI and it must contain only one element */
1701  if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {
1702    result = -1;
1703    goto done;
1704  }
1705  object = semi->pn_kid;
1706
1707  /* this must be an object literal */
1708  if (object->pn_type != TOK_RC) {
1709    result = -1;
1710    goto done;
1711  }
1712
1713  for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {
1714    /* every element of this list must be TOK_COLON */
1715    if (p->pn_type != TOK_COLON) {
1716      result = -1;
1717      goto done;
1718    }
1719
1720    /* the key must be a string representing the file */
1721    JSParseNode * key = p->pn_left;
1722    if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {
1723      result = -1;
1724      goto done;
1725    }
1726    char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));
1727
1728    /* the value must be an object literal OR an array */
1729    JSParseNode * value = p->pn_right;

Large files files are truncated, but you can click here to view the full file