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

Diff of /trunk/instrument-js.cpp

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

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

Legend:
Removed from v.84  
changed lines
  Added in v.402

  ViewVC Help
Powered by ViewVC 1.1.24