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

Diff of /trunk/instrument-js.cpp

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

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

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

  ViewVC Help
Powered by ViewVC 1.1.24