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

Diff of /trunk/instrument-js.c

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

revision 169 by siliconforks, Mon Sep 15 16:22:41 2008 UTC revision 379 by siliconforks, Tue Oct 28 17:50:42 2008 UTC
# Line 26  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"  #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 47  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 59  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 74  Line 110 
110    JS_DestroyRuntime(runtime);    JS_DestroyRuntime(runtime);
111  }  }
112    
113    static void print_javascript(const jschar * characters, size_t num_characters, Stream * f) {
114      for (size_t i = 0; i < num_characters; i++) {
115        jschar c = characters[i];
116        /*
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) {  static void print_string(JSString * s, Stream * f) {
138    for (size_t i = 0; i < s->length; i++) {    size_t length = JSSTRING_LENGTH(s);
139      char c = s->chars[i];    jschar * characters = JSSTRING_CHARS(s);
140      Stream_write_char(f, c);    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    
# Line 87  Line 194 
194    print_string(s, f);    print_string(s, f);
195  }  }
196    
197  static void print_string_jsval(jsval value, Stream * 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, Stream * 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 112  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 145  Line 267 
267    }    }
268  }  }
269    
270  static void instrument_expression(JSParseNode * node, Stream * f);  static void output_expression(JSParseNode * node, Stream * f, bool parenthesize_object_literals);
271  static void instrument_statement(JSParseNode * node, Stream * 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 {  enum FunctionType {
275    FUNCTION_NORMAL,    FUNCTION_NORMAL,
276    FUNCTION_GETTER_OR_SETTER    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 output_array_comprehension_or_generator_expression(JSParseNode * node, Stream * f) {
292      assert(node->pn_type == TOK_LEXICALSCOPE);
293      assert(node->pn_arity == PN_NAME);
294      JSParseNode * for_node = node->pn_expr;
295      assert(for_node->pn_type == TOK_FOR);
296      assert(for_node->pn_arity == PN_BINARY);
297      JSParseNode * p = for_node;
298      while (p->pn_type == TOK_FOR) {
299        p = p->pn_right;
300      }
301      JSParseNode * if_node = NULL;
302      if (p->pn_type == TOK_IF) {
303        if_node = p;
304        assert(if_node->pn_arity == PN_TERNARY);
305        p = if_node->pn_kid2;
306      }
307      assert(p->pn_arity == PN_UNARY);
308      p = p->pn_kid;
309      if (p->pn_type == TOK_YIELD) {
310        /* for generator expressions */
311        p = p->pn_kid;
312      }
313    
314      output_expression(p, f, false);
315      p = for_node;
316      while (p->pn_type == TOK_FOR) {
317        Stream_write_char(f, ' ');
318        output_for_in(p, f);
319        p = p->pn_right;
320      }
321      if (if_node) {
322        Stream_write_string(f, " if (");
323        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) {  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);    assert(node->pn_arity == PN_FUNC);
331    assert(ATOM_IS_OBJECT(node->pn_funAtom));    JSObject * object = node->pn_funpob->object;
   JSObject * object = ATOM_TO_OBJECT(node->pn_funAtom);  
332    assert(JS_ObjectIsFunction(context, object));    assert(JS_ObjectIsFunction(context, object));
333    JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);    JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);
334    assert(function);    assert(function);
335    assert(object == function->object);    assert(object == &function->object);
336    Stream_printf(f, "%*s", indent, "");    Stream_printf(f, "%*s", indent, "");
337    if (type == FUNCTION_NORMAL) {    if (type == FUNCTION_NORMAL) {
338      Stream_write_string(f, "function");      Stream_write_string(f, "function ");
339    }    }
340    
341    /* function name */    /* function name */
342    if (function->atom) {    if (function->atom) {
     Stream_write_char(f, ' ');  
343      print_string_atom(function->atom, f);      print_string_atom(function->atom, f);
344    }    }
345    
346    /* function parameters */    /*
347    Stream_write_string(f, "(");    function parameters - see JS_DecompileFunction in jsapi.cpp, which calls
348    JSAtom ** params = xnew(JSAtom *, function->nargs);    js_DecompileFunction in jsopcode.cpp
349    for (int i = 0; i < function->nargs; i++) {    */
350      /* initialize to NULL for sanity check */    Stream_write_char(f, '(');
351      params[i] = NULL;    JSArenaPool pool;
352    }    JS_INIT_ARENA_POOL(&pool, "instrument_function", 256, 1, &context->scriptStackQuota);
353    JSScope * scope = OBJ_SCOPE(object);    jsuword * local_names = NULL;
354    for (JSScopeProperty * scope_property = SCOPE_LAST_PROP(scope); scope_property != NULL; scope_property = scope_property->parent) {    if (JS_GET_LOCAL_NAME_COUNT(function)) {
355      if (scope_property->getter != js_GetArgument) {      local_names = js_GetLocalNameArray(context, function, &pool);
356        continue;      if (local_names == NULL) {
357          fatal("out of memory");
358      }      }
     assert(scope_property->flags & SPROP_HAS_SHORTID);  
     assert((uint16) scope_property->shortid < function->nargs);  
     assert(JSID_IS_ATOM(scope_property->id));  
     params[(uint16) scope_property->shortid] = JSID_TO_ATOM(scope_property->id);  
359    }    }
360      bool destructuring = false;
361    for (int i = 0; i < function->nargs; i++) {    for (int i = 0; i < function->nargs; i++) {
     assert(params[i] != NULL);  
362      if (i > 0) {      if (i > 0) {
363        Stream_write_string(f, ", ");        Stream_write_string(f, ", ");
364      }      }
365      if (ATOM_IS_STRING(params[i])) {      JSAtom * param = JS_LOCAL_NAME_TO_ATOM(local_names[i]);
366        print_string_atom(params[i], f);      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        else {
387          print_string_atom(param, f);
388      }      }
389    }    }
390      JS_FinishArenaPool(&pool);
391    Stream_write_string(f, ") {\n");    Stream_write_string(f, ") {\n");
   free(params);  
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    Stream_write_string(f, "}\n");    Stream_write_string(f, "}\n");
418  }  }
419    
420  static void instrument_function_call(JSParseNode * node, Stream * f) {  static void instrument_function_call(JSParseNode * node, Stream * f) {
421    instrument_expression(node->pn_head, f);    JSParseNode * function_node = node->pn_head;
422      if (function_node->pn_type == TOK_FUNCTION) {
423        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, '(');    Stream_write_char(f, '(');
446    for (struct JSParseNode * p = node->pn_head->pn_next; p != NULL; p = p->pn_next) {    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        Stream_write_string(f, ", ");        Stream_write_string(f, ", ");
449      }      }
450      instrument_expression(p, f);      output_expression(p, f, false);
451    }    }
452    Stream_write_char(f, ')');    Stream_write_char(f, ')');
453  }  }
454    
455    static void instrument_declarations(JSParseNode * list, Stream * f) {
456      assert(list->pn_arity == PN_LIST);
457      for (JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) {
458        if (p != list->pn_head) {
459          Stream_write_string(f, ", ");
460        }
461        output_expression(p, f, false);
462      }
463    }
464    
465  /*  /*
466  See <Expressions> in jsparse.h.  See <Expressions> in jsparse.h.
467  TOK_FUNCTION is handled as a statement and as an expression.  TOK_FUNCTION is handled as a statement and as an expression.
# Line 232  Line 475 
475  TOK_INSTANCEOF  binary  TOK_INSTANCEOF  binary
476  TOK_IN          binary  TOK_IN          binary
477  */  */
478  static void instrument_expression(JSParseNode * node, Stream * 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, FUNCTION_NORMAL);      instrument_function(node, f, 0, FUNCTION_NORMAL);
# Line 242  Line 485 
485        if (p != node->pn_head) {        if (p != node->pn_head) {
486          Stream_write_string(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      Stream_write_char(f, ' ');      Stream_write_char(f, ' ');
494      switch (node->pn_op) {      switch (node->pn_op) {
495      case JSOP_ADD:      case JSOP_ADD:
# Line 267  Line 510 
510        break;        break;
511      }      }
512      Stream_write_string(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      Stream_write_string(f, "? ");      Stream_write_string(f, "? ");
518      instrument_expression(node->pn_kid2, f);      output_expression(node->pn_kid2, f, false);
519      Stream_write_string(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);  
     Stream_write_string(f, " || ");  
     instrument_expression(node->pn_right, f);  
     break;  
523    case TOK_AND:    case TOK_AND:
     instrument_expression(node->pn_left, f);  
     Stream_write_string(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 298  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        Stream_printf(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              output_expression(p, f, parenthesize_object_literals);
544            }
545            else {
546            Stream_printf(f, " %s ", get_op(node->pn_op));            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 318  Line 556 
556      switch (node->pn_op) {      switch (node->pn_op) {
557      case JSOP_NEG:      case JSOP_NEG:
558        Stream_write_char(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        Stream_write_char(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        Stream_write_char(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        Stream_write_char(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        Stream_write_string(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        Stream_write_string(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 355  Line 593 
593      case JSOP_INCPROP:      case JSOP_INCPROP:
594      case JSOP_INCELEM:      case JSOP_INCELEM:
595        Stream_write_string(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        Stream_write_string(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        Stream_write_string(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        Stream_write_string(f, "--");        Stream_write_string(f, "--");
615        break;        break;
616      default:      default:
# Line 386  Line 624 
624      break;      break;
625    case TOK_DELETE:    case TOK_DELETE:
626      Stream_write_string(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          Stream_write_char(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            must_quote = false;
661          }
662          if (must_quote) {
663          Stream_write_char(f, '[');          Stream_write_char(f, '[');
664          print_quoted_string_atom(node->pn_atom, f);          print_quoted_string_atom(node->pn_atom, f);
665          Stream_write_char(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      Stream_write_char(f, '[');      Stream_write_char(f, '[');
676      instrument_expression(node->pn_right, f);      output_expression(node->pn_right, f, false);
677      Stream_write_char(f, ']');      Stream_write_char(f, ']');
678      break;      break;
679    case TOK_LP:    case TOK_LP:
# Line 427  Line 687 
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) {
# Line 436  Line 696 
696      Stream_write_char(f, ']');      Stream_write_char(f, ']');
697      break;      break;
698    case TOK_RC:    case TOK_RC:
699        if (parenthesize_object_literals) {
700          Stream_write_char(f, '(');
701        }
702      Stream_write_char(f, '{');      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          Stream_write_string(f, ", ");          Stream_write_string(f, ", ");
709        }        }
# Line 446  Line 711 
711        /* check whether this is a getter or setter */        /* check whether this is a getter or setter */
712        switch (p->pn_op) {        switch (p->pn_op) {
713        case JSOP_GETTER:        case JSOP_GETTER:
         Stream_write_string(f, "get ");  
         instrument_expression(p->pn_left, f);  
         instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER);  
         break;  
714        case JSOP_SETTER:        case JSOP_SETTER:
715          Stream_write_string(f, "set ");          if (p->pn_op == JSOP_GETTER) {
716          instrument_expression(p->pn_left, f);            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);          instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER);
726          break;          break;
727        default:        default:
728          instrument_expression(p->pn_left, f);          output_expression(p->pn_left, f, false);
729          Stream_write_string(f, ": ");          Stream_write_string(f, ": ");
730          instrument_expression(p->pn_right, f);          output_expression(p->pn_right, f, false);
731          break;          break;
732        }        }
733      }      }
734      Stream_write_char(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      Stream_write_char(f, '(');      Stream_write_char(f, '(');
741      instrument_expression(node->pn_kid, f);      output_expression(node->pn_kid, f, false);
742      Stream_write_char(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 529  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      Stream_write_string(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      Stream_write_string(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, Stream * 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    Stream_printf(f, "%*s", indent, "");      instrument_declarations(node->pn_expr->pn_left, f);
814    Stream_write_string(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        Stream_write_string(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        Stream_write_string(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, Stream * 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, FUNCTION_NORMAL);      instrument_function(node, f, indent, FUNCTION_NORMAL);
# Line 572  Line 868 
868      Stream_write_string(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      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
# Line 580  Line 876 
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    
882        uint16_t line = node->pn_pos.begin.lineno;
883        if (! is_jscoverage_if) {
884          if (line > num_lines) {
885            fatal("file %s contains more than 65,535 lines", file_id);
886          }
887          if (line >= 2 && exclusive_directives[line - 2]) {
888            is_jscoverage_if = true;
889          }
890        }
891    
892      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
893      Stream_write_string(f, "if (");      Stream_write_string(f, "if (");
894      instrument_expression(node->pn_kid1, f);      output_expression(node->pn_kid1, f, false);
895      Stream_write_string(f, ") {\n");      Stream_write_string(f, ") {\n");
896      instrument_statement(node->pn_kid2, f, indent + 2);      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, "");      Stream_printf(f, "%*s", indent, "");
904      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
905      if (node->pn_kid3) {  
906        if (node->pn_kid3 || is_jscoverage_if) {
907        Stream_printf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
908        Stream_write_string(f, "else {\n");        Stream_write_string(f, "else {\n");
909        instrument_statement(node->pn_kid3, f, indent + 2);  
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, "");        Stream_printf(f, "%*s", indent, "");
922        Stream_write_string(f, "}\n");        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      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
930      Stream_write_string(f, "switch (");      Stream_write_string(f, "switch (");
931      instrument_expression(node->pn_left, f);      output_expression(node->pn_left, f, false);
932      Stream_write_string(f, ") {\n");      Stream_write_string(f, ") {\n");
933      for (struct JSParseNode * p = node->pn_right->pn_head; p != NULL; p = p->pn_next) {      {
934        Stream_printf(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          Stream_write_string(f, "case ");        }
938          instrument_expression(p->pn_left, f);        for (struct JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) {
939          Stream_write_string(f, ":\n");          Stream_printf(f, "%*s", indent, "");
940          break;          switch (p->pn_type) {
941        case TOK_DEFAULT:          case TOK_CASE:
942          Stream_write_string(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      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
957      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
# Line 630  Line 964 
964      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
965      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
966      Stream_write_string(f, "while (");      Stream_write_string(f, "while (");
967      instrument_expression(node->pn_left, f);      output_expression(node->pn_left, f, false);
968      Stream_write_string(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      Stream_write_string(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      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
975      Stream_write_string(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      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
978      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
979      Stream_write_string(f, "while (");      Stream_write_string(f, "while (");
980      instrument_expression(node->pn_right, f);      output_expression(node->pn_right, f, false);
981      Stream_write_string(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      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
     Stream_write_string(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;  
 */  
       }  
       Stream_write_string(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        Stream_write_string(f, ";");        Stream_write_string(f, ";");
1000        if (node->pn_left->pn_kid2) {        if (node->pn_left->pn_kid2) {
1001          Stream_write_char(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        Stream_write_string(f, ";");        Stream_write_string(f, ";");
1005        if (node->pn_left->pn_kid3) {        if (node->pn_left->pn_kid3) {
1006          Stream_write_char(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      Stream_write_string(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      Stream_write_string(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      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1022      Stream_write_string(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      Stream_write_string(f, ";\n");      Stream_write_string(f, ";\n");
1025      break;      break;
1026    case TOK_TRY:    case TOK_TRY:
1027      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1028      Stream_write_string(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      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1031      Stream_write_string(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          Stream_printf(f, "%*s", indent, "");          Stream_printf(f, "%*s", indent, "");
1039          Stream_write_string(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) {
         if (catch->pn_kid1->pn_expr) {  
1042            Stream_write_string(f, " if ");            Stream_write_string(f, " if ");
1043            instrument_expression(catch->pn_kid1->pn_expr, f);            output_expression(catch->pn_kid2, f, false);
1044          }          }
1045          Stream_write_string(f, ") {\n");          Stream_write_string(f, ") {\n");
1046          instrument_statement(catch->pn_kid3, f, indent + 2);          instrument_statement(catch->pn_kid3, f, indent + 2, false);
1047          Stream_printf(f, "%*s", indent, "");          Stream_printf(f, "%*s", indent, "");
1048          Stream_write_string(f, "}\n");          Stream_write_string(f, "}\n");
1049        }        }
# Line 738  Line 1051 
1051      if (node->pn_kid3) {      if (node->pn_kid3) {
1052        Stream_printf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
1053        Stream_write_string(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        Stream_printf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
1056        Stream_write_string(f, "}\n");        Stream_write_string(f, "}\n");
1057      }      }
# Line 762  Line 1075 
1075      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
1076      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1077      Stream_write_string(f, "with (");      Stream_write_string(f, "with (");
1078      instrument_expression(node->pn_left, f);      output_expression(node->pn_left, f, false);
1079      Stream_write_string(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      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1082      Stream_write_string(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        output_expression(node, f, false);
1087      Stream_write_string(f, ";\n");      Stream_write_string(f, ";\n");
1088      break;      break;
1089    case TOK_RETURN:    case TOK_RETURN:
# Line 778  Line 1092 
1092      Stream_write_string(f, "return");      Stream_write_string(f, "return");
1093      if (node->pn_kid != NULL) {      if (node->pn_kid != NULL) {
1094        Stream_write_char(f, ' ');        Stream_write_char(f, ' ');
1095        instrument_expression(node->pn_kid, f);        output_expression(node->pn_kid, f, true);
1096      }      }
1097      Stream_write_string(f, ";\n");      Stream_write_string(f, ";\n");
1098      break;      break;
# Line 786  Line 1100 
1100      assert(node->pn_arity == PN_UNARY);      assert(node->pn_arity == PN_UNARY);
1101      Stream_printf(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      Stream_write_string(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);
     /*  
     This one is tricky: can't output instrumentation between the label and the  
     statement it's supposed to label ...  
     */  
1110      Stream_printf(f, "%*s", indent < 2? 0: indent - 2, "");      Stream_printf(f, "%*s", indent < 2? 0: indent - 2, "");
1111      print_string_atom(node->pn_atom, f);      print_string_atom(node->pn_atom, f);
1112      Stream_write_string(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 814  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, Stream * 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        Stream_printf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
# Line 824  Line 1210 
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  void jscoverage_instrument_js(const char * id, Stream * input, Stream * output) {  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    /* scan the javascript */    const char * bp = prefix;
1220    size_t input_length = input->length;    for (;;) {
1221    jschar * base = js_InflateString(context, (char *) input->data, &input_length);      if (*bp == '\0') {
1222    if (base == NULL) {        return true;
1223      fatal("out of memory");      }
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    JSTokenStream * token_stream = js_NewTokenStream(context, base, input_length, NULL, 1, NULL);  }
1234    if (token_stream == NULL) {  
1235      fatal("cannot create token stream from file: %s", file_id);  static bool characters_are_white_space(const jschar * characters, size_t line_start, size_t line_end) {
1236      /* XXX - other Unicode space */
1237      const jschar * end = characters + line_end;
1238      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    An instrumented JavaScript file has 3 sections:    An instrumented JavaScript file has 4 sections:
1342    1. initialization    1. initialization
1343    2. instrumented source code    2. instrumented source code
1344    3. original source code    3. conditionals
1345      4. original source code
1346    */    */
1347    
1348    Stream * instrumented = Stream_new(0);    Stream * instrumented = Stream_new(0);
1349    instrument_statement(node, instrumented, 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    Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n");    Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n");
1354    Stream_write_string(output, "if (! top._$jscoverage) {\n  top._$jscoverage = {};\n}\n");    if (jscoverage_mozilla) {
1355    Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n");      Stream_write_string(output, "try {\n");
1356        Stream_write_string(output, "  Components.utils.import('resource://gre/modules/jscoverage.jsm');\n");
1357        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);    Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id);
1369    Stream_printf(output, "  _$jscoverage['%s'] = [];\n", file_id);    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++) {
# Line 876  Line 1375 
1375    Stream_write_string(output, "}\n");    Stream_write_string(output, "}\n");
1376    free(lines);    free(lines);
1377    lines = NULL;    lines = NULL;
1378      free(exclusive_directives);
1379      exclusive_directives = NULL;
1380    
1381      /* conditionals */
1382      if (has_conditionals) {
1383        Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);
1384      }
1385    
1386    /* copy the instrumented source code to the output */    /* copy the instrumented source code to the output */
1387    Stream_write(output, instrumented->data, instrumented->length);    Stream_write(output, instrumented->data, instrumented->length);
   Stream_write_char(output, '\n');  
1388    
1389    /* conditionals */    /* conditionals */
1390    bool has_conditionals = false;    for (struct IfDirective * if_directive = if_directives; if_directive != NULL; if_directive = if_directive->next) {
1391    size_t line_number = 0;      Stream_write_string(output, "if (!(");
1392    size_t i = 0;      print_javascript(if_directive->condition_start, if_directive->condition_end - if_directive->condition_start, output);
1393    while (i < input_length) {      Stream_write_string(output, ")) {\n");
1394      line_number++;      Stream_printf(output, "  _$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_directive->start_line, if_directive->end_line);
1395      size_t line_start = i;      Stream_write_string(output, "}\n");
1396      while (i < input_length && base[i] != '\r' && base[i] != '\n') {    }
1397        i++;  
1398      }    /* free */
1399      size_t line_end = i;    while (if_directives != NULL) {
1400      if (i < input_length) {      struct IfDirective * if_directive = if_directives;
1401        if (base[i] == '\r') {      if_directives = if_directives->next;
1402          line_end = i;      free(if_directive);
         i++;  
         if (i < input_length && base[i] == '\n') {  
           i++;  
         }  
       }  
       else if (base[i] == '\n') {  
         line_end = i;  
         i++;  
       }  
       else {  
         abort();  
       }  
     }  
     char * line = js_DeflateString(context, base + line_start, line_end - line_start);  
     if (str_starts_with(line, "//#JSCOVERAGE_IF")) {  
       if (! has_conditionals) {  
         has_conditionals = true;  
         Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);  
       }  
       Stream_printf(output, "if (!%s) {\n", line + 16);  
       Stream_printf(output, "  _$jscoverage['%s'].conditionals[%d] = ", file_id, line_number);  
     }  
     else if (str_starts_with(line, "//#JSCOVERAGE_ENDIF")) {  
       Stream_printf(output, "%d;\n", line_number);  
       Stream_printf(output, "}\n");  
     }  
     JS_free(context, line);  
1403    }    }
1404    
1405    /* copy the original source to the output */    /* copy the original source to the output */
1406    i = 0;    Stream_printf(output, "_$jscoverage['%s'].source = ", file_id);
1407    while (i < input_length) {    jscoverage_write_source(id, characters, num_characters, output);
1408      Stream_write_string(output, "// ");    Stream_printf(output, ";\n");
     size_t line_start = i;  
     while (i < input_length && base[i] != '\r' && base[i] != '\n') {  
       i++;  
     }  
1409    
1410      size_t line_end = i;    Stream_delete(instrumented);
1411      if (i < input_length) {  
1412        if (base[i] == '\r') {    file_id = NULL;
1413          line_end = i;  }
1414    
1415    void jscoverage_write_source(const char * id, const jschar * characters, size_t num_characters, Stream * output) {
1416      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++;          i++;
1471          if (i < input_length && base[i] == '\n') {          if (i >= highlighted_stream->length) {
1472            i++;            done = true;
1473          }          }
1474        }        }
1475        else if (base[i] == '\n') {        Stream_write_char(output, '"');
1476          line_end = i;      }
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++;          i++;
1549            if (i >= num_characters) {
1550              done = true;
1551            }
1552        }        }
1553        else {        Stream_write_char(output, '"');
         abort();  
       }  
1554      }      }
   
     char * line = js_DeflateString(context, base + line_start, line_end - line_start);  
     Stream_write_string(output, line);  
     Stream_write_char(output, '\n');  
     JS_free(context, line);  
1555    }    }
1556      Stream_write_string(output, "]");
   Stream_delete(instrumented);  
   
   JS_free(context, base);  
   
   file_id = NULL;  
1557  }  }
1558    
1559  void jscoverage_copy_resources(const char * destination_directory) {  void jscoverage_copy_resources(const char * destination_directory) {
# Line 970  Line 1562 
1562    copy_resource("jscoverage.js", destination_directory);    copy_resource("jscoverage.js", destination_directory);
1563    copy_resource("jscoverage-ie.css", destination_directory);    copy_resource("jscoverage-ie.css", destination_directory);
1564    copy_resource("jscoverage-throbber.gif", destination_directory);    copy_resource("jscoverage-throbber.gif", destination_directory);
1565    copy_resource("jscoverage-sh_main.js", destination_directory);    copy_resource("jscoverage-highlight.css", destination_directory);
   copy_resource("jscoverage-sh_javascript.js", destination_directory);  
   copy_resource("jscoverage-sh_nedit.css", destination_directory);  
1566  }  }
1567    
1568  /*  /*
# Line 1007  Line 1597 
1597    JS_HashTableDestroy(coverage->coverage_table);    JS_HashTableDestroy(coverage->coverage_table);
1598    struct FileCoverageList * p = coverage->coverage_list;    struct FileCoverageList * p = coverage->coverage_list;
1599    while (p != NULL) {    while (p != NULL) {
1600      free(p->file_coverage->lines);      free(p->file_coverage->coverage_lines);
1601      free(p->file_coverage->source);      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);      free(p->file_coverage->id);
1608      free(p->file_coverage);      free(p->file_coverage);
1609      struct FileCoverageList * q = p;      struct FileCoverageList * q = p;
# Line 1037  Line 1632 
1632  }  }
1633    
1634  int jscoverage_parse_json(Coverage * coverage, const uint8_t * json, size_t length) {  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);    jschar * base = js_InflateString(context, (char *) json, &length);
1638    if (base == NULL) {    if (base == NULL) {
1639      fatal("out of memory");      fatal("out of memory");
# Line 1049  Line 1646 
1646    
1647    JS_free(context, base);    JS_free(context, base);
1648    
1649    JSTokenStream * token_stream = js_NewTokenStream(context, parenthesized_json, length + 2, NULL, 1, NULL);    JSParseContext parse_context;
1650    if (token_stream == NULL) {    if (! js_InitParseContext(context, &parse_context, NULL, NULL, parenthesized_json, length + 2, NULL, NULL, 1)) {
1651      fatal("cannot create token stream");      free(parenthesized_json);
1652        return -1;
1653    }    }
1654      JSParseNode * root = js_ParseScript(context, global, &parse_context);
   JSParseNode * root = js_ParseTokenStream(context, global, token_stream);  
1655    free(parenthesized_json);    free(parenthesized_json);
1656    if (root == NULL) {    if (root == NULL) {
1657      return -1;      result = -1;
1658        goto done;
1659    }    }
1660    
1661    /* root node must be TOK_LC */    /* root node must be TOK_LC */
1662    if (root->pn_type != TOK_LC) {    if (root->pn_type != TOK_LC) {
1663      return -1;      result = -1;
1664        goto done;
1665    }    }
1666    JSParseNode * semi = root->pn_u.list.head;    JSParseNode * semi = root->pn_u.list.head;
1667    
1668    /* the list must be TOK_SEMI and it must contain only one element */    /* the list must be TOK_SEMI and it must contain only one element */
1669    if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {    if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {
1670      return -1;      result = -1;
1671        goto done;
1672    }    }
1673    JSParseNode * parenthesized = semi->pn_kid;    JSParseNode * parenthesized = semi->pn_kid;
1674    
1675    /* this must be a parenthesized expression */    /* this must be a parenthesized expression */
1676    if (parenthesized->pn_type != TOK_RP) {    if (parenthesized->pn_type != TOK_RP) {
1677      return -1;      result = -1;
1678        goto done;
1679    }    }
1680    JSParseNode * object = parenthesized->pn_kid;    JSParseNode * object = parenthesized->pn_kid;
1681    
1682    /* this must be an object literal */    /* this must be an object literal */
1683    if (object->pn_type != TOK_RC) {    if (object->pn_type != TOK_RC) {
1684      return -1;      result = -1;
1685        goto done;
1686    }    }
1687    
1688    for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {    for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {
1689      /* every element of this list must be TOK_COLON */      /* every element of this list must be TOK_COLON */
1690      if (p->pn_type != TOK_COLON) {      if (p->pn_type != TOK_COLON) {
1691        return -1;        result = -1;
1692          goto done;
1693      }      }
1694    
1695      /* the key must be a string representing the file */      /* the key must be a string representing the file */
1696      JSParseNode * key = p->pn_left;      JSParseNode * key = p->pn_left;
1697      if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {      if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {
1698        return -1;        result = -1;
1699          goto done;
1700      }      }
1701      char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));      char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));
1702    
1703      /* the value must be an object literal OR an array */      /* the value must be an object literal OR an array */
1704      JSParseNode * value = p->pn_right;      JSParseNode * value = p->pn_right;
1705      if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {      if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {
1706        return -1;        result = -1;
1707          goto done;
1708      }      }
1709    
1710      JSParseNode * array = NULL;      JSParseNode * array = NULL;
# Line 1111  Line 1716 
1716      else if (value->pn_type == TOK_RC) {      else if (value->pn_type == TOK_RC) {
1717        /* an object literal */        /* an object literal */
1718        if (value->pn_count != 2) {        if (value->pn_count != 2) {
1719          return -1;          result = -1;
1720            goto done;
1721        }        }
1722        for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {        for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {
1723          if (element->pn_type != TOK_COLON) {          if (element->pn_type != TOK_COLON) {
1724            return -1;            result = -1;
1725              goto done;
1726          }          }
1727          JSParseNode * left = element->pn_left;          JSParseNode * left = element->pn_left;
1728          if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {          if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {
1729            return -1;            result = -1;
1730              goto done;
1731          }          }
1732          const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));          const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));
1733          if (strcmp(s, "coverage") == 0) {          if (strcmp(s, "coverage") == 0) {
1734            array = element->pn_right;            array = element->pn_right;
1735            if (array->pn_type != TOK_RB) {            if (array->pn_type != TOK_RB) {
1736              return -1;              result = -1;
1737                goto done;
1738            }            }
1739          }          }
1740          else if (strcmp(s, "source") == 0) {          else if (strcmp(s, "source") == 0) {
1741            source = element->pn_right;            source = element->pn_right;
1742            if (source->pn_type != TOK_STRING || ! ATOM_IS_STRING(source->pn_atom)) {            if (source->pn_type != TOK_RB) {
1743              return -1;              result = -1;
1744                goto done;
1745            }            }
1746          }          }
1747          else {          else {
1748            return -1;            result = -1;
1749              goto done;
1750          }          }
1751        }        }
1752      }      }
1753      else {      else {
1754        return -1;        result = -1;
1755          goto done;
1756      }      }
1757    
1758      if (array == NULL) {      if (array == NULL) {
1759        return -1;        result = -1;
1760          goto done;
1761      }      }
1762    
1763      /* look up the file in the coverage table */      /* look up the file in the coverage table */
# Line 1154  Line 1767 
1767        char * id = xstrdup(id_bytes);        char * id = xstrdup(id_bytes);
1768        file_coverage = xmalloc(sizeof(FileCoverage));        file_coverage = xmalloc(sizeof(FileCoverage));
1769        file_coverage->id = id;        file_coverage->id = id;
1770        file_coverage->num_lines = array->pn_count - 1;        file_coverage->num_coverage_lines = array->pn_count;
1771        file_coverage->lines = xnew(int, array->pn_count);        file_coverage->coverage_lines = xnew(int, array->pn_count);
1772        if (source == NULL) {        file_coverage->source_lines = NULL;
         file_coverage->source = NULL;  
       }  
       else {  
         file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom)));  
       }  
1773    
1774        /* set coverage for all lines */        /* set coverage for all lines */
1775        uint32 i = 0;        uint32 i = 0;
1776        for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {        for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1777          if (element->pn_type == TOK_NUMBER) {          if (element->pn_type == TOK_NUMBER) {
1778            file_coverage->lines[i] = (int) element->pn_dval;            file_coverage->coverage_lines[i] = (int) element->pn_dval;
1779          }          }
1780          else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {          else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1781            file_coverage->lines[i] = -1;            file_coverage->coverage_lines[i] = -1;
1782          }          }
1783          else {          else {
1784            return -1;            result = -1;
1785              goto done;
1786          }          }
1787        }        }
1788        assert(i == array->pn_count);        assert(i == array->pn_count);
# Line 1188  Line 1797 
1797      else {      else {
1798        /* sanity check */        /* sanity check */
1799        assert(strcmp(file_coverage->id, id_bytes) == 0);        assert(strcmp(file_coverage->id, id_bytes) == 0);
1800        if (file_coverage->num_lines != array->pn_count - 1) {        if (file_coverage->num_coverage_lines != array->pn_count) {
1801          return -2;          result = -2;
1802            goto done;
1803        }        }
1804    
1805        /* merge the coverage */        /* merge the coverage */
1806        uint32 i = 0;        uint32 i = 0;
1807        for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {        for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1808          if (element->pn_type == TOK_NUMBER) {          if (element->pn_type == TOK_NUMBER) {
1809            if (file_coverage->lines[i] == -1) {            if (file_coverage->coverage_lines[i] == -1) {
1810              return -2;              result = -2;
1811                goto done;
1812            }            }
1813            file_coverage->lines[i] += (int) element->pn_dval;            file_coverage->coverage_lines[i] += (int) element->pn_dval;
1814          }          }
1815          else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {          else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1816            if (file_coverage->lines[i] != -1) {            if (file_coverage->coverage_lines[i] != -1) {
1817              return -2;              result = -2;
1818                goto done;
1819            }            }
1820          }          }
1821          else {          else {
1822            return -1;            result = -1;
1823              goto done;
1824          }          }
1825        }        }
1826        assert(i == array->pn_count);        assert(i == array->pn_count);
1827        }
1828    
1829        /* if this JSON file has source, use it */      /* if this JSON file has source, use it */
1830        if (file_coverage->source == NULL && source != NULL) {      if (file_coverage->source_lines == NULL && source != NULL) {
1831          file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom)));        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    return 0;  done:
1846      js_FinishParseContext(context, &parse_context);
1847      return result;
1848  }  }

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

  ViewVC Help
Powered by ViewVC 1.1.24