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

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

  ViewVC Help
Powered by ViewVC 1.1.24