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

Legend:
Removed from v.116  
changed lines
  Added in v.390

  ViewVC Help
Powered by ViewVC 1.1.24