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

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

  ViewVC Help
Powered by ViewVC 1.1.24