/[jscoverage]/trunk/instrument-js.c
ViewVC logotype

Diff of /trunk/instrument-js.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

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

Legend:
Removed from v.90  
changed lines
  Added in v.376

  ViewVC Help
Powered by ViewVC 1.1.24