/[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 87 by siliconforks, Mon May 5 20:05:27 2008 UTC revision 377 by siliconforks, Tue Oct 28 05:30:43 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      fprintf(f, ") {\n");      else {
387      free(params);        print_string_atom(param, f);
388        }
389      }
390      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, object literals must be parenthesized */
659        switch (node->pn_expr->pn_type) {
660        case TOK_NUMBER:
661        case TOK_RC:
662          Stream_write_char(f, '(');
663          instrument_expression(node->pn_expr, f);
664          Stream_write_char(f, ')');
665          break;
666        default:
667          instrument_expression(node->pn_expr, f);
668          break;
669        }
670      /*      /*
671      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'
672      contains illegal characters, we have to use the subscript syntax instead of      contains illegal characters, we have to use the subscript syntax instead of
673      the dot syntax.      the dot syntax.
674      */      */
     instrument_expression(node->pn_expr, f);  
675      assert(ATOM_IS_STRING(node->pn_atom));      assert(ATOM_IS_STRING(node->pn_atom));
676      {      {
677        JSString * s = ATOM_TO_STRING(node->pn_atom);        JSString * s = ATOM_TO_STRING(node->pn_atom);
678        /* XXX - semantics changed in 1.7 */        bool must_quote;
679        if (! ATOM_KEYWORD(node->pn_atom) && js_IsIdentifier(s)) {        if (JSSTRING_LENGTH(s) == 0) {
680          fputc('.', f);          must_quote = true;
681          print_string_atom(node->pn_atom, f);        }
682          else if (js_CheckKeyword(JSSTRING_CHARS(s), JSSTRING_LENGTH(s)) != TOK_EOF) {
683            must_quote = true;
684          }
685          else if (! js_IsIdentifier(s)) {
686            must_quote = true;
687        }        }
688        else {        else {
689          fputc('[', f);          must_quote = false;
690          }
691          if (must_quote) {
692            Stream_write_char(f, '[');
693          print_quoted_string_atom(node->pn_atom, f);          print_quoted_string_atom(node->pn_atom, f);
694          fputc(']', f);          Stream_write_char(f, ']');
695          }
696          else {
697            Stream_write_char(f, '.');
698            print_string_atom(node->pn_atom, f);
699        }        }
700      }      }
701      break;      break;
702    case TOK_LB:    case TOK_LB:
703      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
704      fputc('[', f);      Stream_write_char(f, '[');
705      instrument_expression(node->pn_right, f);      instrument_expression(node->pn_right, f);
706      fputc(']', f);      Stream_write_char(f, ']');
707      break;      break;
708    case TOK_LP:    case TOK_LP:
709      instrument_function_call(node, f);      instrument_function_call(node, f);
710      break;      break;
711    case TOK_RB:    case TOK_RB:
712      fputc('[', f);      Stream_write_char(f, '[');
713      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) {
714        if (p != node->pn_head) {        if (p != node->pn_head) {
715          fprintf(f, ", ");          Stream_write_string(f, ", ");
716        }        }
717        /* TOK_COMMA is a special case: a hole in the array */        /* TOK_COMMA is a special case: a hole in the array */
718        if (p->pn_type != TOK_COMMA) {        if (p->pn_type != TOK_COMMA) {
# Line 421  Line 720 
720        }        }
721      }      }
722      if (node->pn_extra == PNX_ENDCOMMA) {      if (node->pn_extra == PNX_ENDCOMMA) {
723        fputc(',', f);        Stream_write_char(f, ',');
724      }      }
725      fputc(']', f);      Stream_write_char(f, ']');
726      break;      break;
727    case TOK_RC:    case TOK_RC:
728      fputc('{', f);      Stream_write_char(f, '{');
729      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) {
730        assert(p->pn_type == TOK_COLON);        if (p->pn_type != TOK_COLON) {
731            fatal_source(file_id, p->pn_pos.begin.lineno, "unsupported node type (%d)", p->pn_type);
732          }
733        if (p != node->pn_head) {        if (p != node->pn_head) {
734          fprintf(f, ", ");          Stream_write_string(f, ", ");
735          }
736    
737          /* check whether this is a getter or setter */
738          switch (p->pn_op) {
739          case JSOP_GETTER:
740          case JSOP_SETTER:
741            if (p->pn_op == JSOP_GETTER) {
742              Stream_write_string(f, "get ");
743            }
744            else {
745              Stream_write_string(f, "set ");
746            }
747            instrument_expression(p->pn_left, f);
748            if (p->pn_right->pn_type != TOK_FUNCTION) {
749              fatal_source(file_id, p->pn_pos.begin.lineno, "expected function");
750            }
751            instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER);
752            break;
753          default:
754            instrument_expression(p->pn_left, f);
755            Stream_write_string(f, ": ");
756            instrument_expression(p->pn_right, f);
757            break;
758        }        }
       instrument_expression(p->pn_left, f);  
       fprintf(f, ": ");  
       instrument_expression(p->pn_right, f);  
759      }      }
760      fputc('}', f);      Stream_write_char(f, '}');
761      break;      break;
762    case TOK_RP:    case TOK_RP:
763      fputc('(', f);      Stream_write_char(f, '(');
764      instrument_expression(node->pn_kid, f);      instrument_expression(node->pn_kid, f);
765      fputc(')', f);      Stream_write_char(f, ')');
766      break;      break;
767    case TOK_NAME:    case TOK_NAME:
768      print_string_atom(node->pn_atom, f);      print_string_atom(node->pn_atom, f);
# Line 449  Line 770 
770    case TOK_STRING:    case TOK_STRING:
771      print_quoted_string_atom(node->pn_atom, f);      print_quoted_string_atom(node->pn_atom, f);
772      break;      break;
773    case TOK_OBJECT:    case TOK_REGEXP:
774      switch (node->pn_op) {      assert(node->pn_op == JSOP_REGEXP);
775      case JSOP_OBJECT:      {
776        /* I assume this is JSOP_REGEXP */        JSObject * object = node->pn_pob->object;
777        abort();        jsval result;
778        break;        js_regexp_toString(context, object, &result);
779      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;  
780      }      }
781      break;      break;
782    case TOK_NUMBER:    case TOK_NUMBER:
# Line 477  Line 787 
787      To keep the output simple, special-case zero.      To keep the output simple, special-case zero.
788      */      */
789      if (node->pn_dval == 0.0) {      if (node->pn_dval == 0.0) {
790        fprintf(f, "0");        Stream_write_string(f, "0");
791      }      }
792      else {      else {
793        fprintf(f, "%.15g", node->pn_dval);        Stream_printf(f, "%.15g", node->pn_dval);
794      }      }
795      break;      break;
796    case TOK_PRIMARY:    case TOK_PRIMARY:
797      switch (node->pn_op) {      switch (node->pn_op) {
798      case JSOP_TRUE:      case JSOP_TRUE:
799        fprintf(f, "true");        Stream_write_string(f, "true");
800        break;        break;
801      case JSOP_FALSE:      case JSOP_FALSE:
802        fprintf(f, "false");        Stream_write_string(f, "false");
803        break;        break;
804      case JSOP_NULL:      case JSOP_NULL:
805        fprintf(f, "null");        Stream_write_string(f, "null");
806        break;        break;
807      case JSOP_THIS:      case JSOP_THIS:
808        fprintf(f, "this");        Stream_write_string(f, "this");
809        break;        break;
810      /* jsscan.h mentions `super' ??? */      /* jsscan.h mentions `super' ??? */
811      default:      default:
# Line 504  Line 814 
814      break;      break;
815    case TOK_INSTANCEOF:    case TOK_INSTANCEOF:
816      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
817      fprintf(f, " instanceof ");      Stream_write_string(f, " instanceof ");
818      instrument_expression(node->pn_right, f);      instrument_expression(node->pn_right, f);
819      break;      break;
820    case TOK_IN:    case TOK_IN:
821      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
822      fprintf(f, " in ");      Stream_write_string(f, " in ");
823      instrument_expression(node->pn_right, f);      instrument_expression(node->pn_right, f);
824      break;      break;
825    default:    case TOK_LEXICALSCOPE:
826      fatal("unsupported node type in file %s: %d", file_id, node->pn_type);      assert(node->pn_arity == PN_NAME);
827    }      assert(node->pn_expr->pn_type == TOK_LET);
828  }      assert(node->pn_expr->pn_arity == PN_BINARY);
829        Stream_write_string(f, "let(");
830  static void instrument_var_statement(JSParseNode * node, FILE * f, int indent) {      assert(node->pn_expr->pn_left->pn_type == TOK_LP);
831    assert(node->pn_arity == PN_LIST);      assert(node->pn_expr->pn_left->pn_arity == PN_LIST);
832    fprintf(f, "%*s", indent, "");      instrument_declarations(node->pn_expr->pn_left, f);
833    fprintf(f, "var ");      Stream_write_string(f, ") ");
834    for (struct JSParseNode * p = node->pn_u.list.head; p != NULL; p = p->pn_next) {      instrument_expression(node->pn_expr->pn_right, f);
835      assert(p->pn_type == TOK_NAME);      break;
836      assert(p->pn_arity == PN_NAME);    case TOK_YIELD:
837      if (p != node->pn_head) {      assert(node->pn_arity == PN_UNARY);
838        fprintf(f, ", ");      Stream_write_string(f, "yield");
839        if (node->pn_kid != NULL) {
840          Stream_write_char(f, ' ');
841          instrument_expression(node->pn_kid, f);
842      }      }
843      print_string_atom(p->pn_atom, f);      break;
844      if (p->pn_expr != NULL) {    case TOK_ARRAYCOMP:
845        fprintf(f, " = ");      assert(node->pn_arity == PN_LIST);
846        instrument_expression(p->pn_expr, f);      {
847          JSParseNode * block_node;
848          switch (node->pn_count) {
849          case 1:
850            block_node = node->pn_head;
851            break;
852          case 2:
853            block_node = node->pn_head->pn_next;
854            break;
855          default:
856            abort();
857            break;
858          }
859          Stream_write_char(f, '[');
860          output_array_comprehension_or_generator_expression(block_node, f);
861          Stream_write_char(f, ']');
862      }      }
863        break;
864      case TOK_VAR:
865        assert(node->pn_arity == PN_LIST);
866        Stream_write_string(f, "var ");
867        instrument_declarations(node, f);
868        break;
869      case TOK_LET:
870        assert(node->pn_arity == PN_LIST);
871        Stream_write_string(f, "let ");
872        instrument_declarations(node, f);
873        break;
874      default:
875        fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%d)", node->pn_type);
876    }    }
877  }  }
878    
879  static void output_statement(JSParseNode * node, FILE * f, int indent) {  static void output_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
880    switch (node->pn_type) {    switch (node->pn_type) {
881    case TOK_FUNCTION:    case TOK_FUNCTION:
882      instrument_function(node, f, indent);      instrument_function(node, f, indent, FUNCTION_NORMAL);
883      break;      break;
884    case TOK_LC:    case TOK_LC:
885      assert(node->pn_arity == PN_LIST);      assert(node->pn_arity == PN_LIST);
886  /*  /*
887      fprintf(f, "{\n");      Stream_write_string(f, "{\n");
888  */  */
889      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) {
890        instrument_statement(p, f, indent);        instrument_statement(p, f, indent, false);
891      }      }
892  /*  /*
893      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
894      fprintf(f, "}\n");      Stream_write_string(f, "}\n");
895  */  */
896      break;      break;
897    case TOK_IF:    case TOK_IF:
898      {
899      assert(node->pn_arity == PN_TERNARY);      assert(node->pn_arity == PN_TERNARY);
900      fprintf(f, "%*s", indent, "");  
901      fprintf(f, "if (");      uint16_t line = node->pn_pos.begin.lineno;
902        if (! is_jscoverage_if) {
903          if (line > num_lines) {
904            fatal("file %s contains more than 65,535 lines", file_id);
905          }
906          if (line >= 2 && exclusive_directives[line - 2]) {
907            is_jscoverage_if = true;
908          }
909        }
910    
911        Stream_printf(f, "%*s", indent, "");
912        Stream_write_string(f, "if (");
913      instrument_expression(node->pn_kid1, f);      instrument_expression(node->pn_kid1, f);
914      fprintf(f, ") {\n");      Stream_write_string(f, ") {\n");
915      instrument_statement(node->pn_kid2, f, indent + 2);      if (is_jscoverage_if && node->pn_kid3) {
916      fprintf(f, "%*s", indent, "");        uint16_t else_start = node->pn_kid3->pn_pos.begin.lineno;
917      fprintf(f, "}\n");        uint16_t else_end = node->pn_kid3->pn_pos.end.lineno + 1;
918      if (node->pn_kid3) {        Stream_printf(f, "%*s", indent + 2, "");
919        fprintf(f, "%*s", indent, "");        Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, else_start, else_end);
920        fprintf(f, "else {\n");      }
921        instrument_statement(node->pn_kid3, f, indent + 2);      instrument_statement(node->pn_kid2, f, indent + 2, false);
922        fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
923        fprintf(f, "}\n");      Stream_write_string(f, "}\n");
924    
925        if (node->pn_kid3 || is_jscoverage_if) {
926          Stream_printf(f, "%*s", indent, "");
927          Stream_write_string(f, "else {\n");
928    
929          if (is_jscoverage_if) {
930            uint16_t if_start = node->pn_kid2->pn_pos.begin.lineno + 1;
931            uint16_t if_end = node->pn_kid2->pn_pos.end.lineno + 1;
932            Stream_printf(f, "%*s", indent + 2, "");
933            Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_start, if_end);
934          }
935    
936          if (node->pn_kid3) {
937            instrument_statement(node->pn_kid3, f, indent + 2, is_jscoverage_if);
938          }
939    
940          Stream_printf(f, "%*s", indent, "");
941          Stream_write_string(f, "}\n");
942      }      }
943    
944      break;      break;
945      }
946    case TOK_SWITCH:    case TOK_SWITCH:
947      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
948      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
949      fprintf(f, "switch (");      Stream_write_string(f, "switch (");
950      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
951      fprintf(f, ") {\n");      Stream_write_string(f, ") {\n");
952      for (struct JSParseNode * p = node->pn_right->pn_head; p != NULL; p = p->pn_next) {      {
953        fprintf(f, "%*s", indent, "");        JSParseNode * list = node->pn_right;
954        switch (p->pn_type) {        if (list->pn_type == TOK_LEXICALSCOPE) {
955        case TOK_CASE:          list = list->pn_expr;
956          fprintf(f, "case ");        }
957          instrument_expression(p->pn_left, f);        for (struct JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) {
958          fprintf(f, ":\n");          Stream_printf(f, "%*s", indent, "");
959          break;          switch (p->pn_type) {
960        case TOK_DEFAULT:          case TOK_CASE:
961          fprintf(f, "default:\n");            Stream_write_string(f, "case ");
962          break;            instrument_expression(p->pn_left, f);
963        default:            Stream_write_string(f, ":\n");
964          abort();            break;
965          break;          case TOK_DEFAULT:
966              Stream_write_string(f, "default:\n");
967              break;
968            default:
969              abort();
970              break;
971            }
972            instrument_statement(p->pn_right, f, indent + 2, false);
973        }        }
       instrument_statement(p->pn_right, f, indent + 2);  
974      }      }
975      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
976      fprintf(f, "}\n");      Stream_write_string(f, "}\n");
977      break;      break;
978    case TOK_CASE:    case TOK_CASE:
979    case TOK_DEFAULT:    case TOK_DEFAULT:
# Line 602  Line 981 
981      break;      break;
982    case TOK_WHILE:    case TOK_WHILE:
983      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
984      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
985      fprintf(f, "while (");      Stream_write_string(f, "while (");
986      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
987      fprintf(f, ") {\n");      Stream_write_string(f, ") {\n");
988      instrument_statement(node->pn_right, f, indent + 2);      instrument_statement(node->pn_right, f, indent + 2, false);
989      fprintf(f, "}\n");      Stream_write_string(f, "}\n");
990      break;      break;
991    case TOK_DO:    case TOK_DO:
992      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
993      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
994      fprintf(f, "do {\n");      Stream_write_string(f, "do {\n");
995      instrument_statement(node->pn_left, f, indent + 2);      instrument_statement(node->pn_left, f, indent + 2, false);
996      fprintf(f, "}\n");      Stream_write_string(f, "}\n");
997      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
998      fprintf(f, "while (");      Stream_write_string(f, "while (");
999      instrument_expression(node->pn_right, f);      instrument_expression(node->pn_right, f);
1000      fprintf(f, ");\n");      Stream_write_string(f, ");\n");
1001      break;      break;
1002    case TOK_FOR:    case TOK_FOR:
1003      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
1004      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
     fprintf(f, "for (");  
1005      switch (node->pn_left->pn_type) {      switch (node->pn_left->pn_type) {
1006      case TOK_IN:      case TOK_IN:
1007        /* for/in */        /* for/in */
1008        assert(node->pn_left->pn_arity == PN_BINARY);        assert(node->pn_left->pn_arity == PN_BINARY);
1009        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);  
1010        break;        break;
1011      case TOK_RESERVED:      case TOK_RESERVED:
1012        /* for (;;) */        /* for (;;) */
1013        assert(node->pn_left->pn_arity == PN_TERNARY);        assert(node->pn_left->pn_arity == PN_TERNARY);
1014          Stream_write_string(f, "for (");
1015        if (node->pn_left->pn_kid1) {        if (node->pn_left->pn_kid1) {
1016          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);  
         }  
1017        }        }
1018        fprintf(f, ";");        Stream_write_string(f, ";");
1019        if (node->pn_left->pn_kid2) {        if (node->pn_left->pn_kid2) {
1020          fputc(' ', f);          Stream_write_char(f, ' ');
1021          instrument_expression(node->pn_left->pn_kid2, f);          instrument_expression(node->pn_left->pn_kid2, f);
1022        }        }
1023        fprintf(f, ";");        Stream_write_string(f, ";");
1024        if (node->pn_left->pn_kid3) {        if (node->pn_left->pn_kid3) {
1025          fputc(' ', f);          Stream_write_char(f, ' ');
1026          instrument_expression(node->pn_left->pn_kid3, f);          instrument_expression(node->pn_left->pn_kid3, f);
1027        }        }
1028          Stream_write_char(f, ')');
1029        break;        break;
1030      default:      default:
1031        abort();        abort();
1032        break;        break;
1033      }      }
1034      fprintf(f, ") {\n");      Stream_write_string(f, " {\n");
1035      instrument_statement(node->pn_right, f, indent + 2);      instrument_statement(node->pn_right, f, indent + 2, false);
1036      fprintf(f, "}\n");      Stream_write_string(f, "}\n");
1037      break;      break;
1038    case TOK_THROW:    case TOK_THROW:
1039      assert(node->pn_arity == PN_UNARY);      assert(node->pn_arity == PN_UNARY);
1040      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1041      fprintf(f, "throw ");      Stream_write_string(f, "throw ");
1042      instrument_expression(node->pn_u.unary.kid, f);      instrument_expression(node->pn_u.unary.kid, f);
1043      fprintf(f, ";\n");      Stream_write_string(f, ";\n");
1044      break;      break;
1045    case TOK_TRY:    case TOK_TRY:
1046      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1047      fprintf(f, "try {\n");      Stream_write_string(f, "try {\n");
1048      instrument_statement(node->pn_kid1, f, indent + 2);      instrument_statement(node->pn_kid1, f, indent + 2, false);
1049      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1050      fprintf(f, "}\n");      Stream_write_string(f, "}\n");
1051      {      if (node->pn_kid2) {
1052        for (JSParseNode * catch = node->pn_kid2; catch != NULL; catch = catch->pn_kid2) {        assert(node->pn_kid2->pn_type == TOK_RESERVED);
1053          for (JSParseNode * scope = node->pn_kid2->pn_head; scope != NULL; scope = scope->pn_next) {
1054            assert(scope->pn_type == TOK_LEXICALSCOPE);
1055            JSParseNode * catch = scope->pn_expr;
1056          assert(catch->pn_type == TOK_CATCH);          assert(catch->pn_type == TOK_CATCH);
1057          fprintf(f, "%*s", indent, "");          Stream_printf(f, "%*s", indent, "");
1058          fprintf(f, "catch (");          Stream_write_string(f, "catch (");
1059            /* this may not be a name - destructuring assignment */
1060            /*
1061          assert(catch->pn_kid1->pn_arity == PN_NAME);          assert(catch->pn_kid1->pn_arity == PN_NAME);
1062          print_string_atom(catch->pn_kid1->pn_atom, f);          print_string_atom(catch->pn_kid1->pn_atom, f);
1063          if (catch->pn_kid1->pn_expr) {          */
1064            fprintf(f, " if ");          instrument_expression(catch->pn_kid1, f);
1065            instrument_expression(catch->pn_kid1->pn_expr, f);          if (catch->pn_kid2) {
1066          }            Stream_write_string(f, " if ");
1067          fprintf(f, ") {\n");            instrument_expression(catch->pn_kid2, f);
1068          instrument_statement(catch->pn_kid3, f, indent + 2);          }
1069          fprintf(f, "%*s", indent, "");          Stream_write_string(f, ") {\n");
1070          fprintf(f, "}\n");          instrument_statement(catch->pn_kid3, f, indent + 2, false);
1071            Stream_printf(f, "%*s", indent, "");
1072            Stream_write_string(f, "}\n");
1073        }        }
1074      }      }
1075      if (node->pn_kid3) {      if (node->pn_kid3) {
1076        fprintf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
1077        fprintf(f, "finally {\n");        Stream_write_string(f, "finally {\n");
1078        instrument_statement(node->pn_kid3, f, indent + 2);        instrument_statement(node->pn_kid3, f, indent + 2, false);
1079        fprintf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
1080        fprintf(f, "}\n");        Stream_write_string(f, "}\n");
1081      }      }
1082      break;      break;
1083    case TOK_CATCH:    case TOK_CATCH:
# Line 723  Line 1086 
1086    case TOK_BREAK:    case TOK_BREAK:
1087    case TOK_CONTINUE:    case TOK_CONTINUE:
1088      assert(node->pn_arity == PN_NAME || node->pn_arity == PN_NULLARY);      assert(node->pn_arity == PN_NAME || node->pn_arity == PN_NULLARY);
1089      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1090      fputs(node->pn_type == TOK_BREAK? "break": "continue", f);      Stream_write_string(f, node->pn_type == TOK_BREAK? "break": "continue");
1091      JSAtom * atom = node->pn_u.name.atom;      JSAtom * atom = node->pn_u.name.atom;
1092      if (atom != NULL) {      if (atom != NULL) {
1093        fputc(' ', f);        Stream_write_char(f, ' ');
1094        print_string_atom(node->pn_atom, f);        print_string_atom(node->pn_atom, f);
1095      }      }
1096      fprintf(f, ";\n");      Stream_write_string(f, ";\n");
1097      break;      break;
1098    case TOK_WITH:    case TOK_WITH:
1099      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
1100      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1101      fprintf(f, "with (");      Stream_write_string(f, "with (");
1102      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
1103      fprintf(f, ") {\n");      Stream_write_string(f, ") {\n");
1104      instrument_statement(node->pn_right, f, indent + 2);      instrument_statement(node->pn_right, f, indent + 2, false);
1105      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1106      fprintf(f, "}\n");      Stream_write_string(f, "}\n");
1107      break;      break;
1108    case TOK_VAR:    case TOK_VAR:
1109      instrument_var_statement(node, f, indent);      Stream_printf(f, "%*s", indent, "");
1110      fprintf(f, ";\n");      instrument_expression(node, f);
1111        Stream_write_string(f, ";\n");
1112      break;      break;
1113    case TOK_RETURN:    case TOK_RETURN:
1114      assert(node->pn_arity == PN_UNARY);      assert(node->pn_arity == PN_UNARY);
1115      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1116      fprintf(f, "return");      Stream_write_string(f, "return");
1117      if (node->pn_kid != NULL) {      if (node->pn_kid != NULL) {
1118        fprintf(f, " ");        Stream_write_char(f, ' ');
1119        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
1120      }      }
1121      fprintf(f, ";\n");      Stream_write_string(f, ";\n");
1122      break;      break;
1123    case TOK_SEMI:    case TOK_SEMI:
1124      assert(node->pn_arity == PN_UNARY);      assert(node->pn_arity == PN_UNARY);
1125      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1126      if (node->pn_kid != NULL) {      if (node->pn_kid != NULL) {
1127        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
1128      }      }
1129      fprintf(f, ";\n");      Stream_write_string(f, ";\n");
1130      break;      break;
1131    case TOK_COLON:    case TOK_COLON:
1132      {
1133      assert(node->pn_arity == PN_NAME);      assert(node->pn_arity == PN_NAME);
1134      /*      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, "");  
1135      print_string_atom(node->pn_atom, f);      print_string_atom(node->pn_atom, f);
1136      fprintf(f, ":\n");      Stream_write_string(f, ":\n");
1137      /*      JSParseNode * labelled = node->pn_expr;
1138      ... use output_statement instead of instrument_statement.      if (labelled->pn_type == TOK_LEXICALSCOPE) {
1139      */        labelled = labelled->pn_expr;
1140      output_statement(node->pn_expr, f, indent);      }
1141        if (labelled->pn_type == TOK_LC) {
1142          /* labelled block */
1143          Stream_printf(f, "%*s", indent, "");
1144          Stream_write_string(f, "{\n");
1145          instrument_statement(labelled, f, indent + 2, false);
1146          Stream_printf(f, "%*s", indent, "");
1147          Stream_write_string(f, "}\n");
1148        }
1149        else {
1150          /*
1151          This one is tricky: can't output instrumentation between the label and the
1152          statement it's supposed to label, so use output_statement instead of
1153          instrument_statement.
1154          */
1155          output_statement(labelled, f, indent, false);
1156        }
1157        break;
1158      }
1159      case TOK_LEXICALSCOPE:
1160        /* let statement */
1161        assert(node->pn_arity == PN_NAME);
1162        switch (node->pn_expr->pn_type) {
1163        case TOK_LET:
1164          /* let statement */
1165          assert(node->pn_expr->pn_arity == PN_BINARY);
1166          instrument_statement(node->pn_expr, f, indent, false);
1167          break;
1168        case TOK_LC:
1169          /* block */
1170          Stream_printf(f, "%*s", indent, "");
1171          Stream_write_string(f, "{\n");
1172          instrument_statement(node->pn_expr, f, indent + 2, false);
1173          Stream_printf(f, "%*s", indent, "");
1174          Stream_write_string(f, "}\n");
1175          break;
1176        case TOK_FOR:
1177          instrument_statement(node->pn_expr, f, indent, false);
1178          break;
1179        default:
1180          abort();
1181          break;
1182        }
1183        break;
1184      case TOK_LET:
1185        switch (node->pn_arity) {
1186        case PN_BINARY:
1187          /* let statement */
1188          Stream_printf(f, "%*s", indent, "");
1189          Stream_write_string(f, "let (");
1190          assert(node->pn_left->pn_type == TOK_LP);
1191          assert(node->pn_left->pn_arity == PN_LIST);
1192          instrument_declarations(node->pn_left, f);
1193          Stream_write_string(f, ") {\n");
1194          instrument_statement(node->pn_right, f, indent + 2, false);
1195          Stream_printf(f, "%*s", indent, "");
1196          Stream_write_string(f, "}\n");
1197          break;
1198        case PN_LIST:
1199          /* let definition */
1200          Stream_printf(f, "%*s", indent, "");
1201          instrument_expression(node, f);
1202          Stream_write_string(f, ";\n");
1203          break;
1204        default:
1205          abort();
1206          break;
1207        }
1208        break;
1209      case TOK_DEBUGGER:
1210        Stream_printf(f, "%*s", indent, "");
1211        Stream_write_string(f, "debugger;\n");
1212      break;      break;
1213    default:    default:
1214      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);
1215    }    }
1216  }  }
1217    
# Line 788  Line 1220 
1220  TOK_FUNCTION is handled as a statement and as an expression.  TOK_FUNCTION is handled as a statement and as an expression.
1221  TOK_EXPORT, TOK_IMPORT are not handled.  TOK_EXPORT, TOK_IMPORT are not handled.
1222  */  */
1223  static void instrument_statement(JSParseNode * node, FILE * f, int indent) {  static void instrument_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
1224    if (node->pn_type != TOK_LC) {    if (node->pn_type != TOK_LC && node->pn_type != TOK_LEXICALSCOPE) {
1225      int line = node->pn_pos.begin.lineno;      uint16_t line = node->pn_pos.begin.lineno;
1226        if (line > num_lines) {
1227          fatal("file %s contains more than 65,535 lines", file_id);
1228        }
1229    
1230      /* the root node has line number 0 */      /* the root node has line number 0 */
1231      if (line != 0) {      if (line != 0) {
1232        fprintf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
1233        fprintf(f, "_$jscoverage['%s'][%d]++;\n", file_id, line);        Stream_printf(f, "_$jscoverage['%s'][%d]++;\n", file_id, line);
1234        lines[line - 1] = 1;        lines[line - 1] = 1;
1235      }      }
1236    }    }
1237    output_statement(node, f, indent);    output_statement(node, f, indent, is_jscoverage_if);
1238  }  }
1239    
1240  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) {
1241    file_id = id;    const jschar * characters_end = characters + line_end;
1242      const jschar * cp = characters + line_start;
1243      const char * bp = prefix;
1244      for (;;) {
1245        if (*bp == '\0') {
1246          return true;
1247        }
1248        else if (cp == characters_end) {
1249          return false;
1250        }
1251        else if (*cp != *bp) {
1252          return false;
1253        }
1254        bp++;
1255        cp++;
1256      }
1257    }
1258    
1259    /* scan the javascript */  static bool characters_are_white_space(const jschar * characters, size_t line_start, size_t line_end) {
1260    JSTokenStream * token_stream = js_NewFileTokenStream(context, NULL, input);    /* XXX - other Unicode space */
1261    if (token_stream == NULL) {    const jschar * end = characters + line_end;
1262      fatal("cannot create token stream from file: %s", file_id);    for (const jschar * p = characters + line_start; p < end; p++) {
1263        jschar c = *p;
1264        if (c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0) {
1265          continue;
1266        }
1267        else {
1268          return false;
1269        }
1270    }    }
1271      return true;
1272    }
1273    
1274    static void error_reporter(JSContext * context, const char * message, JSErrorReport * report) {
1275      warn_source(file_id, report->lineno, "%s", message);
1276    }
1277    
1278    void jscoverage_instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output) {
1279      file_id = id;
1280    
1281    /* parse the javascript */    /* parse the javascript */
1282    JSParseNode * node = js_ParseTokenStream(context, global, token_stream);    JSParseContext parse_context;
1283      if (! js_InitParseContext(context, &parse_context, NULL, NULL, characters, num_characters, NULL, NULL, 1)) {
1284        fatal("cannot create token stream from file %s", file_id);
1285      }
1286      JSErrorReporter old_error_reporter = JS_SetErrorReporter(context, error_reporter);
1287      JSParseNode * node = js_ParseScript(context, global, &parse_context);
1288    if (node == NULL) {    if (node == NULL) {
1289      fatal("parse error in file: %s", file_id);      js_ReportUncaughtException(context);
1290        fatal("parse error in file %s", file_id);
1291    }    }
1292    int num_lines = node->pn_pos.end.lineno;    JS_SetErrorReporter(context, old_error_reporter);
1293      num_lines = node->pn_pos.end.lineno;
1294    lines = xmalloc(num_lines);    lines = xmalloc(num_lines);
1295    for (int i = 0; i < num_lines; i++) {    for (unsigned int i = 0; i < num_lines; i++) {
1296      lines[i] = 0;      lines[i] = 0;
1297    }    }
1298    
1299      /* search code for conditionals */
1300      exclusive_directives = xnew(bool, num_lines);
1301      for (unsigned int i = 0; i < num_lines; i++) {
1302        exclusive_directives[i] = false;
1303      }
1304    
1305      bool has_conditionals = false;
1306      struct IfDirective * if_directives = NULL;
1307      size_t line_number = 0;
1308      size_t i = 0;
1309      while (i < num_characters) {
1310        if (line_number == UINT16_MAX) {
1311          fatal("file %s contains more than 65,535 lines", file_id);
1312        }
1313        line_number++;
1314        size_t line_start = i;
1315        jschar c;
1316        bool done = false;
1317        while (! done && i < num_characters) {
1318          c = characters[i];
1319          switch (c) {
1320          case '\r':
1321          case '\n':
1322          case 0x2028:
1323          case 0x2029:
1324            done = true;
1325            break;
1326          default:
1327            i++;
1328          }
1329        }
1330        size_t line_end = i;
1331        if (i < num_characters) {
1332          i++;
1333          if (c == '\r' && i < num_characters && characters[i] == '\n') {
1334            i++;
1335          }
1336        }
1337    
1338        if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_IF")) {
1339          has_conditionals = true;
1340    
1341          if (characters_are_white_space(characters, line_start + 16, line_end)) {
1342            exclusive_directives[line_number - 1] = true;
1343          }
1344          else {
1345            struct IfDirective * if_directive = xnew(struct IfDirective, 1);
1346            if_directive->condition_start = characters + line_start + 16;
1347            if_directive->condition_end = characters + line_end;
1348            if_directive->start_line = line_number;
1349            if_directive->end_line = 0;
1350            if_directive->next = if_directives;
1351            if_directives = if_directive;
1352          }
1353        }
1354        else if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_ENDIF")) {
1355          for (struct IfDirective * p = if_directives; p != NULL; p = p->next) {
1356            if (p->end_line == 0) {
1357              p->end_line = line_number;
1358              break;
1359            }
1360          }
1361        }
1362      }
1363    
1364    /*    /*
1365    Create a temporary file - we can't write directly to the output because we    An instrumented JavaScript file has 4 sections:
1366    need to know the line number info first.    1. initialization
1367      2. instrumented source code
1368      3. conditionals
1369      4. original source code
1370    */    */
   FILE * temporary = fopen(temporary_file_name, "w+");  
   if (temporary == NULL) {  
     fatal("cannot create temporary file for script: %s", file_id);  
   }  
1371    
1372    /* write instrumented javascript to the temporary */    Stream * instrumented = Stream_new(0);
1373    instrument_statement(node, temporary, 0);    instrument_statement(node, instrumented, 0, false);
1374      js_FinishParseContext(context, &parse_context);
1375    
1376    /* write line number info to the output */    /* write line number info to the output */
1377    fprintf(output, "/* automatically generated by JSCoverage - do not edit */\n");    Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n");
1378    fprintf(output, "if (! top._$jscoverage) {\n  top._$jscoverage = {};\n}\n");    if (jscoverage_mozilla) {
1379    fprintf(output, "var _$jscoverage = top._$jscoverage;\n");      Stream_write_string(output, "try {\n");
1380    fprintf(output, "if (! _$jscoverage['%s']) {\n", file_id);      Stream_write_string(output, "  Components.utils.import('resource://gre/modules/jscoverage.jsm');\n");
1381    fprintf(output, "  _$jscoverage['%s'] = [];\n", file_id);      Stream_printf(output, "  dump('%s: successfully imported jscoverage module\\n');\n", id);
1382        Stream_write_string(output, "}\n");
1383        Stream_write_string(output, "catch (e) {\n");
1384        Stream_write_string(output, "  _$jscoverage = {};\n");
1385        Stream_printf(output, "  dump('%s: failed to import jscoverage module - coverage not available for this file\\n');\n", id);
1386        Stream_write_string(output, "}\n");
1387      }
1388      else {
1389        Stream_write_string(output, "if (! top._$jscoverage) {\n  top._$jscoverage = {};\n}\n");
1390        Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n");
1391      }
1392      Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id);
1393      Stream_printf(output, "  _$jscoverage['%s'] = [];\n", file_id);
1394    for (int i = 0; i < num_lines; i++) {    for (int i = 0; i < num_lines; i++) {
1395      if (lines[i]) {      if (lines[i]) {
1396        fprintf(output, "  _$jscoverage['%s'][%d] = 0;\n", file_id, i + 1);        Stream_printf(output, "  _$jscoverage['%s'][%d] = 0;\n", file_id, i + 1);
1397      }      }
1398    }    }
1399    fprintf(output, "}\n");    Stream_write_string(output, "}\n");
1400      free(lines);
1401    lines = NULL;    lines = NULL;
1402      free(exclusive_directives);
1403      exclusive_directives = NULL;
1404    
1405    /* copy the temporary to the output */    /* conditionals */
1406    fseek(temporary, 0, SEEK_SET);    if (has_conditionals) {
1407    copy_stream(temporary, output);      Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);
1408      }
1409    
1410      /* copy the instrumented source code to the output */
1411      Stream_write(output, instrumented->data, instrumented->length);
1412    
1413      /* conditionals */
1414      for (struct IfDirective * if_directive = if_directives; if_directive != NULL; if_directive = if_directive->next) {
1415        Stream_write_string(output, "if (!(");
1416        print_javascript(if_directive->condition_start, if_directive->condition_end - if_directive->condition_start, output);
1417        Stream_write_string(output, ")) {\n");
1418        Stream_printf(output, "  _$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_directive->start_line, if_directive->end_line);
1419        Stream_write_string(output, "}\n");
1420      }
1421    
1422      /* free */
1423      while (if_directives != NULL) {
1424        struct IfDirective * if_directive = if_directives;
1425        if_directives = if_directives->next;
1426        free(if_directive);
1427      }
1428    
1429    fclose(temporary);    /* copy the original source to the output */
1430      Stream_printf(output, "_$jscoverage['%s'].source = ", file_id);
1431      jscoverage_write_source(id, characters, num_characters, output);
1432      Stream_printf(output, ";\n");
1433    
1434      Stream_delete(instrumented);
1435    
1436    file_id = NULL;    file_id = NULL;
1437  }  }
1438    
1439  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) {
1440    instrument_js_stream(id, 0, input, output, temporary_file_name);    Stream_write_string(output, "[");
1441      if (jscoverage_highlight) {
1442        Stream * highlighted_stream = Stream_new(num_characters);
1443        jscoverage_highlight_js(context, id, characters, num_characters, highlighted_stream);
1444        size_t i = 0;
1445        while (i < highlighted_stream->length) {
1446          if (i > 0) {
1447            Stream_write_char(output, ',');
1448          }
1449    
1450          Stream_write_char(output, '"');
1451          bool done = false;
1452          while (! done) {
1453            char c = highlighted_stream->data[i];
1454            switch (c) {
1455            case 0x8:
1456              /* backspace */
1457              Stream_write_string(output, "\\b");
1458              break;
1459            case 0x9:
1460              /* horizontal tab */
1461              Stream_write_string(output, "\\t");
1462              break;
1463            case 0xa:
1464              /* line feed (new line) */
1465              done = true;
1466              break;
1467            /* IE doesn't support this */
1468            /*
1469            case 0xb:
1470              Stream_write_string(output, "\\v");
1471              break;
1472            */
1473            case 0xc:
1474              /* form feed */
1475              Stream_write_string(output, "\\f");
1476              break;
1477            case 0xd:
1478              /* carriage return */
1479              done = true;
1480              if (i + 1 < highlighted_stream->length && highlighted_stream->data[i + 1] == '\n') {
1481                i++;
1482              }
1483              break;
1484            case '"':
1485              Stream_write_string(output, "\\\"");
1486              break;
1487            case '\\':
1488              Stream_write_string(output, "\\\\");
1489              break;
1490            default:
1491              Stream_write_char(output, c);
1492              break;
1493            }
1494            i++;
1495            if (i >= highlighted_stream->length) {
1496              done = true;
1497            }
1498          }
1499          Stream_write_char(output, '"');
1500        }
1501        Stream_delete(highlighted_stream);
1502      }
1503      else {
1504        size_t i = 0;
1505        while (i < num_characters) {
1506          if (i > 0) {
1507            Stream_write_char(output, ',');
1508          }
1509    
1510          Stream_write_char(output, '"');
1511          bool done = false;
1512          while (! done) {
1513            jschar c = characters[i];
1514            switch (c) {
1515            case 0x8:
1516              /* backspace */
1517              Stream_write_string(output, "\\b");
1518              break;
1519            case 0x9:
1520              /* horizontal tab */
1521              Stream_write_string(output, "\\t");
1522              break;
1523            case 0xa:
1524              /* line feed (new line) */
1525              done = true;
1526              break;
1527            /* IE doesn't support this */
1528            /*
1529            case 0xb:
1530              Stream_write_string(output, "\\v");
1531              break;
1532            */
1533            case 0xc:
1534              /* form feed */
1535              Stream_write_string(output, "\\f");
1536              break;
1537            case 0xd:
1538              /* carriage return */
1539              done = true;
1540              if (i + 1 < num_characters && characters[i + 1] == '\n') {
1541                i++;
1542              }
1543              break;
1544            case '"':
1545              Stream_write_string(output, "\\\"");
1546              break;
1547            case '\\':
1548              Stream_write_string(output, "\\\\");
1549              break;
1550            case '&':
1551              Stream_write_string(output, "&amp;");
1552              break;
1553            case '<':
1554              Stream_write_string(output, "&lt;");
1555              break;
1556            case '>':
1557              Stream_write_string(output, "&gt;");
1558              break;
1559            case 0x2028:
1560            case 0x2029:
1561              done = true;
1562              break;
1563            default:
1564              if (32 <= c && c <= 126) {
1565                Stream_write_char(output, c);
1566              }
1567              else {
1568                Stream_printf(output, "&#%d;", c);
1569              }
1570              break;
1571            }
1572            i++;
1573            if (i >= num_characters) {
1574              done = true;
1575            }
1576          }
1577          Stream_write_char(output, '"');
1578        }
1579      }
1580      Stream_write_string(output, "]");
1581    }
1582    
1583    void jscoverage_copy_resources(const char * destination_directory) {
1584      copy_resource("jscoverage.html", destination_directory);
1585      copy_resource("jscoverage.css", destination_directory);
1586      copy_resource("jscoverage.js", destination_directory);
1587      copy_resource("jscoverage-ie.css", destination_directory);
1588      copy_resource("jscoverage-throbber.gif", destination_directory);
1589      copy_resource("jscoverage-highlight.css", destination_directory);
1590    }
1591    
1592    /*
1593    coverage reports
1594    */
1595    
1596    struct FileCoverageList {
1597      FileCoverage * file_coverage;
1598      struct FileCoverageList * next;
1599    };
1600    
1601    struct Coverage {
1602      JSHashTable * coverage_table;
1603      struct FileCoverageList * coverage_list;
1604    };
1605    
1606    static int compare_strings(const void * p1, const void * p2) {
1607      return strcmp(p1, p2) == 0;
1608    }
1609    
1610    Coverage * Coverage_new(void) {
1611      Coverage * result = xmalloc(sizeof(Coverage));
1612      result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL);
1613      if (result->coverage_table == NULL) {
1614        fatal("cannot create hash table");
1615      }
1616      result->coverage_list = NULL;
1617      return result;
1618    }
1619    
1620    void Coverage_delete(Coverage * coverage) {
1621      JS_HashTableDestroy(coverage->coverage_table);
1622      struct FileCoverageList * p = coverage->coverage_list;
1623      while (p != NULL) {
1624        free(p->file_coverage->coverage_lines);
1625        if (p->file_coverage->source_lines != NULL) {
1626          for (uint32 i = 0; i < p->file_coverage->num_source_lines; i++) {
1627            free(p->file_coverage->source_lines[i]);
1628          }
1629          free(p->file_coverage->source_lines);
1630        }
1631        free(p->file_coverage->id);
1632        free(p->file_coverage);
1633        struct FileCoverageList * q = p;
1634        p = p->next;
1635        free(q);
1636      }
1637      free(coverage);
1638    }
1639    
1640    struct EnumeratorArg {
1641      CoverageForeachFunction f;
1642      void * p;
1643    };
1644    
1645    static intN enumerator(JSHashEntry * entry, intN i, void * arg) {
1646      struct EnumeratorArg * enumerator_arg = arg;
1647      enumerator_arg->f(entry->value, i, enumerator_arg->p);
1648      return 0;
1649    }
1650    
1651    void Coverage_foreach_file(Coverage * coverage, CoverageForeachFunction f, void * p) {
1652      struct EnumeratorArg enumerator_arg;
1653      enumerator_arg.f = f;
1654      enumerator_arg.p = p;
1655      JS_HashTableEnumerateEntries(coverage->coverage_table, enumerator, &enumerator_arg);
1656    }
1657    
1658    int jscoverage_parse_json(Coverage * coverage, const uint8_t * json, size_t length) {
1659      int result = 0;
1660    
1661      jschar * base = js_InflateString(context, (char *) json, &length);
1662      if (base == NULL) {
1663        fatal("out of memory");
1664      }
1665    
1666      jschar * parenthesized_json = xnew(jschar, addst(length, 2));
1667      parenthesized_json[0] = '(';
1668      memcpy(parenthesized_json + 1, base, mulst(length, sizeof(jschar)));
1669      parenthesized_json[length + 1] = ')';
1670    
1671      JS_free(context, base);
1672    
1673      JSParseContext parse_context;
1674      if (! js_InitParseContext(context, &parse_context, NULL, NULL, parenthesized_json, length + 2, NULL, NULL, 1)) {
1675        free(parenthesized_json);
1676        return -1;
1677      }
1678      JSParseNode * root = js_ParseScript(context, global, &parse_context);
1679      free(parenthesized_json);
1680      if (root == NULL) {
1681        result = -1;
1682        goto done;
1683      }
1684    
1685      /* root node must be TOK_LC */
1686      if (root->pn_type != TOK_LC) {
1687        result = -1;
1688        goto done;
1689      }
1690      JSParseNode * semi = root->pn_u.list.head;
1691    
1692      /* the list must be TOK_SEMI and it must contain only one element */
1693      if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {
1694        result = -1;
1695        goto done;
1696      }
1697      JSParseNode * parenthesized = semi->pn_kid;
1698    
1699      /* this must be a parenthesized expression */
1700      if (parenthesized->pn_type != TOK_RP) {
1701        result = -1;
1702        goto done;
1703      }
1704      JSParseNode * object = parenthesized->pn_kid;
1705    
1706      /* this must be an object literal */
1707      if (object->pn_type != TOK_RC) {
1708        result = -1;
1709        goto done;
1710      }
1711    
1712      for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {
1713        /* every element of this list must be TOK_COLON */
1714        if (p->pn_type != TOK_COLON) {
1715          result = -1;
1716          goto done;
1717        }
1718    
1719        /* the key must be a string representing the file */
1720        JSParseNode * key = p->pn_left;
1721        if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {
1722          result = -1;
1723          goto done;
1724        }
1725        char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));
1726    
1727        /* the value must be an object literal OR an array */
1728        JSParseNode * value = p->pn_right;
1729        if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {
1730          result = -1;
1731          goto done;
1732        }
1733    
1734        JSParseNode * array = NULL;
1735        JSParseNode * source = NULL;
1736        if (value->pn_type == TOK_RB) {
1737          /* an array */
1738          array = value;
1739        }
1740        else if (value->pn_type == TOK_RC) {
1741          /* an object literal */
1742          if (value->pn_count != 2) {
1743            result = -1;
1744            goto done;
1745          }
1746          for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {
1747            if (element->pn_type != TOK_COLON) {
1748              result = -1;
1749              goto done;
1750            }
1751            JSParseNode * left = element->pn_left;
1752            if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {
1753              result = -1;
1754              goto done;
1755            }
1756            const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));
1757            if (strcmp(s, "coverage") == 0) {
1758              array = element->pn_right;
1759              if (array->pn_type != TOK_RB) {
1760                result = -1;
1761                goto done;
1762              }
1763            }
1764            else if (strcmp(s, "source") == 0) {
1765              source = element->pn_right;
1766              if (source->pn_type != TOK_RB) {
1767                result = -1;
1768                goto done;
1769              }
1770            }
1771            else {
1772              result = -1;
1773              goto done;
1774            }
1775          }
1776        }
1777        else {
1778          result = -1;
1779          goto done;
1780        }
1781    
1782        if (array == NULL) {
1783          result = -1;
1784          goto done;
1785        }
1786    
1787        /* look up the file in the coverage table */
1788        FileCoverage * file_coverage = JS_HashTableLookup(coverage->coverage_table, id_bytes);
1789        if (file_coverage == NULL) {
1790          /* not there: create a new one */
1791          char * id = xstrdup(id_bytes);
1792          file_coverage = xmalloc(sizeof(FileCoverage));
1793          file_coverage->id = id;
1794          file_coverage->num_coverage_lines = array->pn_count;
1795          file_coverage->coverage_lines = xnew(int, array->pn_count);
1796          file_coverage->source_lines = NULL;
1797    
1798          /* set coverage for all lines */
1799          uint32 i = 0;
1800          for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1801            if (element->pn_type == TOK_NUMBER) {
1802              file_coverage->coverage_lines[i] = (int) element->pn_dval;
1803            }
1804            else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1805              file_coverage->coverage_lines[i] = -1;
1806            }
1807            else {
1808              result = -1;
1809              goto done;
1810            }
1811          }
1812          assert(i == array->pn_count);
1813    
1814          /* add to the hash table */
1815          JS_HashTableAdd(coverage->coverage_table, id, file_coverage);
1816          struct FileCoverageList * coverage_list = xmalloc(sizeof(struct FileCoverageList));
1817          coverage_list->file_coverage = file_coverage;
1818          coverage_list->next = coverage->coverage_list;
1819          coverage->coverage_list = coverage_list;
1820        }
1821        else {
1822          /* sanity check */
1823          assert(strcmp(file_coverage->id, id_bytes) == 0);
1824          if (file_coverage->num_coverage_lines != array->pn_count) {
1825            result = -2;
1826            goto done;
1827          }
1828    
1829          /* merge the coverage */
1830          uint32 i = 0;
1831          for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1832            if (element->pn_type == TOK_NUMBER) {
1833              if (file_coverage->coverage_lines[i] == -1) {
1834                result = -2;
1835                goto done;
1836              }
1837              file_coverage->coverage_lines[i] += (int) element->pn_dval;
1838            }
1839            else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1840              if (file_coverage->coverage_lines[i] != -1) {
1841                result = -2;
1842                goto done;
1843              }
1844            }
1845            else {
1846              result = -1;
1847              goto done;
1848            }
1849          }
1850          assert(i == array->pn_count);
1851        }
1852    
1853        /* if this JSON file has source, use it */
1854        if (file_coverage->source_lines == NULL && source != NULL) {
1855          file_coverage->num_source_lines = source->pn_count;
1856          file_coverage->source_lines = xnew(char *, source->pn_count);
1857          uint32 i = 0;
1858          for (JSParseNode * element = source->pn_head; element != NULL; element = element->pn_next, i++) {
1859            if (element->pn_type != TOK_STRING) {
1860              result = -1;
1861              goto done;
1862            }
1863            file_coverage->source_lines[i] = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(element->pn_atom)));
1864          }
1865          assert(i == source->pn_count);
1866        }
1867      }
1868    
1869    done:
1870      js_FinishParseContext(context, &parse_context);
1871      return result;
1872  }  }

Legend:
Removed from v.87  
changed lines
  Added in v.377

  ViewVC Help
Powered by ViewVC 1.1.24