/[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 190 by siliconforks, Tue Sep 23 03:49:01 2008 UTC revision 377 by siliconforks, Tue Oct 28 05:30:43 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>
# Line 40  Line 44 
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 50  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 62  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 77  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    size_t length = JSSTRING_LENGTH(s);    size_t length = JSSTRING_LENGTH(s);
139    jschar * characters = JSSTRING_CHARS(s);    jschar * characters = JSSTRING_CHARS(s);
# Line 111  Line 168 
168        case 0xa:        case 0xa:
169          Stream_write_string(f, "\\n");          Stream_write_string(f, "\\n");
170          break;          break;
171          /* IE doesn't support this */
172          /*
173        case 0xb:        case 0xb:
174          Stream_write_string(f, "\\v");          Stream_write_string(f, "\\v");
175          break;          break;
176          */
177        case 0xc:        case 0xc:
178          Stream_write_string(f, "\\f");          Stream_write_string(f, "\\f");
179          break;          break;
# Line 160  Line 220 
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 170  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 204  Line 268 
268  }  }
269    
270  static void instrument_expression(JSParseNode * node, Stream * f);  static void instrument_expression(JSParseNode * node, Stream * f);
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      instrument_expression(node->pn_left, f);
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      instrument_expression(p, f);
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        instrument_expression(if_node->pn_kid1, f);
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          instrument_expression(expression, f);
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          instrument_expression(function_node, f);
439          Stream_write_char(f, ')');
440        }
441      }
442      else {
443        instrument_expression(function_node, f);
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      }      }
# Line 277  Line 452 
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        switch (p->pn_type) {
459        case TOK_NAME:
460          assert(p->pn_arity == PN_NAME);
461          if (p != list->pn_head) {
462            Stream_write_string(f, ", ");
463          }
464          print_string_atom(p->pn_atom, f);
465          if (p->pn_expr != NULL) {
466            Stream_write_string(f, " = ");
467            instrument_expression(p->pn_expr, f);
468          }
469          break;
470        case TOK_ASSIGN:
471          /* destructuring */
472          instrument_expression(p->pn_left, f);
473          Stream_write_string(f, " = ");
474          instrument_expression(p->pn_right, f);
475          break;
476        case TOK_RB:
477        case TOK_RC:
478          /* destructuring */
479          instrument_expression(p, f);
480          break;
481        default:
482          abort();
483          break;
484        }
485      }
486    }
487    
488  /*  /*
489  See <Expressions> in jsparse.h.  See <Expressions> in jsparse.h.
490  TOK_FUNCTION is handled as a statement and as an expression.  TOK_FUNCTION is handled as a statement and as an expression.
# Line 304  Line 512 
512      }      }
513      break;      break;
514    case TOK_ASSIGN:    case TOK_ASSIGN:
515      instrument_expression(node->pn_left, f);      if (node->pn_left->pn_type == TOK_RC) {
516          /* destructuring assignment with object literal must be in parentheses */
517          Stream_write_char(f, '(');
518          instrument_expression(node->pn_left, f);
519          Stream_write_char(f, ')');
520        }
521        else {
522          instrument_expression(node->pn_left, f);
523        }
524      Stream_write_char(f, ' ');      Stream_write_char(f, ' ');
525      switch (node->pn_op) {      switch (node->pn_op) {
526      case JSOP_ADD:      case JSOP_ADD:
# Line 335  Line 551 
551      instrument_expression(node->pn_kid3, f);      instrument_expression(node->pn_kid3, f);
552      break;      break;
553    case TOK_OR:    case TOK_OR:
     instrument_expression(node->pn_left, f);  
     Stream_write_string(f, " || ");  
     instrument_expression(node->pn_right, f);  
     break;  
554    case TOK_AND:    case TOK_AND:
     instrument_expression(node->pn_left, f);  
     Stream_write_string(f, " && ");  
     instrument_expression(node->pn_right, f);  
     break;  
555    case TOK_BITOR:    case TOK_BITOR:
556    case TOK_BITXOR:    case TOK_BITXOR:
557    case TOK_BITAND:    case TOK_BITAND:
# Line 399  Line 607 
607        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
608        break;        break;
609      default:      default:
610        abort();        fatal_source(file_id, node->pn_pos.begin.lineno, "unknown operator (%d)", node->pn_op);
611        break;        break;
612      }      }
613      break;      break;
# Line 447  Line 655 
655      instrument_expression(node->pn_kid, f);      instrument_expression(node->pn_kid, f);
656      break;      break;
657    case TOK_DOT:    case TOK_DOT:
658        /* numeric literals, object literals must be parenthesized */
659        switch (node->pn_expr->pn_type) {
660        case TOK_NUMBER:
661        case TOK_RC:
662          Stream_write_char(f, '(');
663          instrument_expression(node->pn_expr, f);
664          Stream_write_char(f, ')');
665          break;
666        default:
667          instrument_expression(node->pn_expr, f);
668          break;
669        }
670      /*      /*
671      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'
672      contains illegal characters, we have to use the subscript syntax instead of      contains illegal characters, we have to use the subscript syntax instead of
673      the dot syntax.      the dot syntax.
674      */      */
     instrument_expression(node->pn_expr, f);  
675      assert(ATOM_IS_STRING(node->pn_atom));      assert(ATOM_IS_STRING(node->pn_atom));
676      {      {
677        JSString * s = ATOM_TO_STRING(node->pn_atom);        JSString * s = ATOM_TO_STRING(node->pn_atom);
678        /* XXX - semantics changed in 1.7 */        bool must_quote;
679        if (! ATOM_KEYWORD(node->pn_atom) && js_IsIdentifier(s)) {        if (JSSTRING_LENGTH(s) == 0) {
680          Stream_write_char(f, '.');          must_quote = true;
681          print_string_atom(node->pn_atom, f);        }
682          else if (js_CheckKeyword(JSSTRING_CHARS(s), JSSTRING_LENGTH(s)) != TOK_EOF) {
683            must_quote = true;
684          }
685          else if (! js_IsIdentifier(s)) {
686            must_quote = true;
687        }        }
688        else {        else {
689            must_quote = false;
690          }
691          if (must_quote) {
692          Stream_write_char(f, '[');          Stream_write_char(f, '[');
693          print_quoted_string_atom(node->pn_atom, f);          print_quoted_string_atom(node->pn_atom, f);
694          Stream_write_char(f, ']');          Stream_write_char(f, ']');
695        }        }
696          else {
697            Stream_write_char(f, '.');
698            print_string_atom(node->pn_atom, f);
699          }
700      }      }
701      break;      break;
702    case TOK_LB:    case TOK_LB:
# Line 496  Line 727 
727    case TOK_RC:    case TOK_RC:
728      Stream_write_char(f, '{');      Stream_write_char(f, '{');
729      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) {
730        assert(p->pn_type == TOK_COLON);        if (p->pn_type != TOK_COLON) {
731            fatal_source(file_id, p->pn_pos.begin.lineno, "unsupported node type (%d)", p->pn_type);
732          }
733        if (p != node->pn_head) {        if (p != node->pn_head) {
734          Stream_write_string(f, ", ");          Stream_write_string(f, ", ");
735        }        }
# Line 504  Line 737 
737        /* check whether this is a getter or setter */        /* check whether this is a getter or setter */
738        switch (p->pn_op) {        switch (p->pn_op) {
739        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;  
740        case JSOP_SETTER:        case JSOP_SETTER:
741          Stream_write_string(f, "set ");          if (p->pn_op == JSOP_GETTER) {
742              Stream_write_string(f, "get ");
743            }
744            else {
745              Stream_write_string(f, "set ");
746            }
747          instrument_expression(p->pn_left, f);          instrument_expression(p->pn_left, f);
748            if (p->pn_right->pn_type != TOK_FUNCTION) {
749              fatal_source(file_id, p->pn_pos.begin.lineno, "expected function");
750            }
751          instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER);          instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER);
752          break;          break;
753        default:        default:
# Line 533  Line 770 
770    case TOK_STRING:    case TOK_STRING:
771      print_quoted_string_atom(node->pn_atom, f);      print_quoted_string_atom(node->pn_atom, f);
772      break;      break;
773    case TOK_OBJECT:    case TOK_REGEXP:
774      switch (node->pn_op) {      assert(node->pn_op == JSOP_REGEXP);
775      case JSOP_OBJECT:      {
776        /* I assume this is JSOP_REGEXP */        JSObject * object = node->pn_pob->object;
777        abort();        jsval result;
778        break;        js_regexp_toString(context, object, &result);
779      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_regex(result, f);  
       }  
       break;  
     default:  
       abort();  
       break;  
780      }      }
781      break;      break;
782    case TOK_NUMBER:    case TOK_NUMBER:
# Line 596  Line 822 
822      Stream_write_string(f, " in ");      Stream_write_string(f, " in ");
823      instrument_expression(node->pn_right, f);      instrument_expression(node->pn_right, f);
824      break;      break;
825    default:    case TOK_LEXICALSCOPE:
826      fatal("unsupported node type in file %s: %d", file_id, node->pn_type);      assert(node->pn_arity == PN_NAME);
827    }      assert(node->pn_expr->pn_type == TOK_LET);
828  }      assert(node->pn_expr->pn_arity == PN_BINARY);
829        Stream_write_string(f, "let(");
830  static void instrument_var_statement(JSParseNode * node, Stream * f, int indent) {      assert(node->pn_expr->pn_left->pn_type == TOK_LP);
831    assert(node->pn_arity == PN_LIST);      assert(node->pn_expr->pn_left->pn_arity == PN_LIST);
832    Stream_printf(f, "%*s", indent, "");      instrument_declarations(node->pn_expr->pn_left, f);
833    Stream_write_string(f, "var ");      Stream_write_string(f, ") ");
834    for (struct JSParseNode * p = node->pn_u.list.head; p != NULL; p = p->pn_next) {      instrument_expression(node->pn_expr->pn_right, f);
835      assert(p->pn_type == TOK_NAME);      break;
836      assert(p->pn_arity == PN_NAME);    case TOK_YIELD:
837      if (p != node->pn_head) {      assert(node->pn_arity == PN_UNARY);
838        Stream_write_string(f, ", ");      Stream_write_string(f, "yield");
839        if (node->pn_kid != NULL) {
840          Stream_write_char(f, ' ');
841          instrument_expression(node->pn_kid, f);
842      }      }
843      print_string_atom(p->pn_atom, f);      break;
844      if (p->pn_expr != NULL) {    case TOK_ARRAYCOMP:
845        Stream_write_string(f, " = ");      assert(node->pn_arity == PN_LIST);
846        instrument_expression(p->pn_expr, f);      {
847          JSParseNode * block_node;
848          switch (node->pn_count) {
849          case 1:
850            block_node = node->pn_head;
851            break;
852          case 2:
853            block_node = node->pn_head->pn_next;
854            break;
855          default:
856            abort();
857            break;
858          }
859          Stream_write_char(f, '[');
860          output_array_comprehension_or_generator_expression(block_node, f);
861          Stream_write_char(f, ']');
862      }      }
863        break;
864      case TOK_VAR:
865        assert(node->pn_arity == PN_LIST);
866        Stream_write_string(f, "var ");
867        instrument_declarations(node, f);
868        break;
869      case TOK_LET:
870        assert(node->pn_arity == PN_LIST);
871        Stream_write_string(f, "let ");
872        instrument_declarations(node, f);
873        break;
874      default:
875        fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%d)", node->pn_type);
876    }    }
877  }  }
878    
879  static void output_statement(JSParseNode * node, Stream * f, int indent) {  static void output_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
880    switch (node->pn_type) {    switch (node->pn_type) {
881    case TOK_FUNCTION:    case TOK_FUNCTION:
882      instrument_function(node, f, indent, FUNCTION_NORMAL);      instrument_function(node, f, indent, FUNCTION_NORMAL);
# Line 630  Line 887 
887      Stream_write_string(f, "{\n");      Stream_write_string(f, "{\n");
888  */  */
889      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) {
890        instrument_statement(p, f, indent);        instrument_statement(p, f, indent, false);
891      }      }
892  /*  /*
893      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
# Line 638  Line 895 
895  */  */
896      break;      break;
897    case TOK_IF:    case TOK_IF:
898      {
899      assert(node->pn_arity == PN_TERNARY);      assert(node->pn_arity == PN_TERNARY);
900    
901        uint16_t line = node->pn_pos.begin.lineno;
902        if (! is_jscoverage_if) {
903          if (line > num_lines) {
904            fatal("file %s contains more than 65,535 lines", file_id);
905          }
906          if (line >= 2 && exclusive_directives[line - 2]) {
907            is_jscoverage_if = true;
908          }
909        }
910    
911      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
912      Stream_write_string(f, "if (");      Stream_write_string(f, "if (");
913      instrument_expression(node->pn_kid1, f);      instrument_expression(node->pn_kid1, f);
914      Stream_write_string(f, ") {\n");      Stream_write_string(f, ") {\n");
915      instrument_statement(node->pn_kid2, f, indent + 2);      if (is_jscoverage_if && node->pn_kid3) {
916          uint16_t else_start = node->pn_kid3->pn_pos.begin.lineno;
917          uint16_t else_end = node->pn_kid3->pn_pos.end.lineno + 1;
918          Stream_printf(f, "%*s", indent + 2, "");
919          Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, else_start, else_end);
920        }
921        instrument_statement(node->pn_kid2, f, indent + 2, false);
922      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
923      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
924      if (node->pn_kid3) {  
925        if (node->pn_kid3 || is_jscoverage_if) {
926        Stream_printf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
927        Stream_write_string(f, "else {\n");        Stream_write_string(f, "else {\n");
928        instrument_statement(node->pn_kid3, f, indent + 2);  
929          if (is_jscoverage_if) {
930            uint16_t if_start = node->pn_kid2->pn_pos.begin.lineno + 1;
931            uint16_t if_end = node->pn_kid2->pn_pos.end.lineno + 1;
932            Stream_printf(f, "%*s", indent + 2, "");
933            Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_start, if_end);
934          }
935    
936          if (node->pn_kid3) {
937            instrument_statement(node->pn_kid3, f, indent + 2, is_jscoverage_if);
938          }
939    
940        Stream_printf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
941        Stream_write_string(f, "}\n");        Stream_write_string(f, "}\n");
942      }      }
943    
944      break;      break;
945      }
946    case TOK_SWITCH:    case TOK_SWITCH:
947      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
948      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
949      Stream_write_string(f, "switch (");      Stream_write_string(f, "switch (");
950      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
951      Stream_write_string(f, ") {\n");      Stream_write_string(f, ") {\n");
952      for (struct JSParseNode * p = node->pn_right->pn_head; p != NULL; p = p->pn_next) {      {
953        Stream_printf(f, "%*s", indent, "");        JSParseNode * list = node->pn_right;
954        switch (p->pn_type) {        if (list->pn_type == TOK_LEXICALSCOPE) {
955        case TOK_CASE:          list = list->pn_expr;
956          Stream_write_string(f, "case ");        }
957          instrument_expression(p->pn_left, f);        for (struct JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) {
958          Stream_write_string(f, ":\n");          Stream_printf(f, "%*s", indent, "");
959          break;          switch (p->pn_type) {
960        case TOK_DEFAULT:          case TOK_CASE:
961          Stream_write_string(f, "default:\n");            Stream_write_string(f, "case ");
962          break;            instrument_expression(p->pn_left, f);
963        default:            Stream_write_string(f, ":\n");
964          abort();            break;
965          break;          case TOK_DEFAULT:
966              Stream_write_string(f, "default:\n");
967              break;
968            default:
969              abort();
970              break;
971            }
972            instrument_statement(p->pn_right, f, indent + 2, false);
973        }        }
       instrument_statement(p->pn_right, f, indent + 2);  
974      }      }
975      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
976      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
# Line 690  Line 985 
985      Stream_write_string(f, "while (");      Stream_write_string(f, "while (");
986      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
987      Stream_write_string(f, ") {\n");      Stream_write_string(f, ") {\n");
988      instrument_statement(node->pn_right, f, indent + 2);      instrument_statement(node->pn_right, f, indent + 2, false);
989      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
990      break;      break;
991    case TOK_DO:    case TOK_DO:
992      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
993      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
994      Stream_write_string(f, "do {\n");      Stream_write_string(f, "do {\n");
995      instrument_statement(node->pn_left, f, indent + 2);      instrument_statement(node->pn_left, f, indent + 2, false);
996      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
997      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
998      Stream_write_string(f, "while (");      Stream_write_string(f, "while (");
# Line 707  Line 1002 
1002    case TOK_FOR:    case TOK_FOR:
1003      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
1004      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
     Stream_write_string(f, "for (");  
1005      switch (node->pn_left->pn_type) {      switch (node->pn_left->pn_type) {
1006      case TOK_IN:      case TOK_IN:
1007        /* for/in */        /* for/in */
1008        assert(node->pn_left->pn_arity == PN_BINARY);        assert(node->pn_left->pn_arity == PN_BINARY);
1009        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);  
1010        break;        break;
1011      case TOK_RESERVED:      case TOK_RESERVED:
1012        /* for (;;) */        /* for (;;) */
1013        assert(node->pn_left->pn_arity == PN_TERNARY);        assert(node->pn_left->pn_arity == PN_TERNARY);
1014          Stream_write_string(f, "for (");
1015        if (node->pn_left->pn_kid1) {        if (node->pn_left->pn_kid1) {
1016          if (node->pn_left->pn_kid1->pn_type == TOK_VAR) {          instrument_expression(node->pn_left->pn_kid1, f);
           instrument_var_statement(node->pn_left->pn_kid1, f, 0);  
         }  
         else {  
           instrument_expression(node->pn_left->pn_kid1, f);  
         }  
1017        }        }
1018        Stream_write_string(f, ";");        Stream_write_string(f, ";");
1019        if (node->pn_left->pn_kid2) {        if (node->pn_left->pn_kid2) {
# Line 754  Line 1025 
1025          Stream_write_char(f, ' ');          Stream_write_char(f, ' ');
1026          instrument_expression(node->pn_left->pn_kid3, f);          instrument_expression(node->pn_left->pn_kid3, f);
1027        }        }
1028          Stream_write_char(f, ')');
1029        break;        break;
1030      default:      default:
1031        abort();        abort();
1032        break;        break;
1033      }      }
1034      Stream_write_string(f, ") {\n");      Stream_write_string(f, " {\n");
1035      instrument_statement(node->pn_right, f, indent + 2);      instrument_statement(node->pn_right, f, indent + 2, false);
1036      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
1037      break;      break;
1038    case TOK_THROW:    case TOK_THROW:
# Line 773  Line 1045 
1045    case TOK_TRY:    case TOK_TRY:
1046      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1047      Stream_write_string(f, "try {\n");      Stream_write_string(f, "try {\n");
1048      instrument_statement(node->pn_kid1, f, indent + 2);      instrument_statement(node->pn_kid1, f, indent + 2, false);
1049      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1050      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
1051      {      if (node->pn_kid2) {
1052        for (JSParseNode * catch = node->pn_kid2; catch != NULL; catch = catch->pn_kid2) {        assert(node->pn_kid2->pn_type == TOK_RESERVED);
1053          for (JSParseNode * scope = node->pn_kid2->pn_head; scope != NULL; scope = scope->pn_next) {
1054            assert(scope->pn_type == TOK_LEXICALSCOPE);
1055            JSParseNode * catch = scope->pn_expr;
1056          assert(catch->pn_type == TOK_CATCH);          assert(catch->pn_type == TOK_CATCH);
1057          Stream_printf(f, "%*s", indent, "");          Stream_printf(f, "%*s", indent, "");
1058          Stream_write_string(f, "catch (");          Stream_write_string(f, "catch (");
1059            /* this may not be a name - destructuring assignment */
1060            /*
1061          assert(catch->pn_kid1->pn_arity == PN_NAME);          assert(catch->pn_kid1->pn_arity == PN_NAME);
1062          print_string_atom(catch->pn_kid1->pn_atom, f);          print_string_atom(catch->pn_kid1->pn_atom, f);
1063          if (catch->pn_kid1->pn_expr) {          */
1064            instrument_expression(catch->pn_kid1, f);
1065            if (catch->pn_kid2) {
1066            Stream_write_string(f, " if ");            Stream_write_string(f, " if ");
1067            instrument_expression(catch->pn_kid1->pn_expr, f);            instrument_expression(catch->pn_kid2, f);
1068          }          }
1069          Stream_write_string(f, ") {\n");          Stream_write_string(f, ") {\n");
1070          instrument_statement(catch->pn_kid3, f, indent + 2);          instrument_statement(catch->pn_kid3, f, indent + 2, false);
1071          Stream_printf(f, "%*s", indent, "");          Stream_printf(f, "%*s", indent, "");
1072          Stream_write_string(f, "}\n");          Stream_write_string(f, "}\n");
1073        }        }
# Line 796  Line 1075 
1075      if (node->pn_kid3) {      if (node->pn_kid3) {
1076        Stream_printf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
1077        Stream_write_string(f, "finally {\n");        Stream_write_string(f, "finally {\n");
1078        instrument_statement(node->pn_kid3, f, indent + 2);        instrument_statement(node->pn_kid3, f, indent + 2, false);
1079        Stream_printf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
1080        Stream_write_string(f, "}\n");        Stream_write_string(f, "}\n");
1081      }      }
# Line 822  Line 1101 
1101      Stream_write_string(f, "with (");      Stream_write_string(f, "with (");
1102      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
1103      Stream_write_string(f, ") {\n");      Stream_write_string(f, ") {\n");
1104      instrument_statement(node->pn_right, f, indent + 2);      instrument_statement(node->pn_right, f, indent + 2, false);
1105      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1106      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
1107      break;      break;
1108    case TOK_VAR:    case TOK_VAR:
1109      instrument_var_statement(node, f, indent);      Stream_printf(f, "%*s", indent, "");
1110        instrument_expression(node, f);
1111      Stream_write_string(f, ";\n");      Stream_write_string(f, ";\n");
1112      break;      break;
1113    case TOK_RETURN:    case TOK_RETURN:
# Line 849  Line 1129 
1129      Stream_write_string(f, ";\n");      Stream_write_string(f, ";\n");
1130      break;      break;
1131    case TOK_COLON:    case TOK_COLON:
1132      {
1133      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 ...  
     */  
1134      Stream_printf(f, "%*s", indent < 2? 0: indent - 2, "");      Stream_printf(f, "%*s", indent < 2? 0: indent - 2, "");
1135      print_string_atom(node->pn_atom, f);      print_string_atom(node->pn_atom, f);
1136      Stream_write_string(f, ":\n");      Stream_write_string(f, ":\n");
1137      /*      JSParseNode * labelled = node->pn_expr;
1138      ... use output_statement instead of instrument_statement.      if (labelled->pn_type == TOK_LEXICALSCOPE) {
1139      */        labelled = labelled->pn_expr;
1140      output_statement(node->pn_expr, f, indent);      }
1141        if (labelled->pn_type == TOK_LC) {
1142          /* labelled block */
1143          Stream_printf(f, "%*s", indent, "");
1144          Stream_write_string(f, "{\n");
1145          instrument_statement(labelled, f, indent + 2, false);
1146          Stream_printf(f, "%*s", indent, "");
1147          Stream_write_string(f, "}\n");
1148        }
1149        else {
1150          /*
1151          This one is tricky: can't output instrumentation between the label and the
1152          statement it's supposed to label, so use output_statement instead of
1153          instrument_statement.
1154          */
1155          output_statement(labelled, f, indent, false);
1156        }
1157        break;
1158      }
1159      case TOK_LEXICALSCOPE:
1160        /* let statement */
1161        assert(node->pn_arity == PN_NAME);
1162        switch (node->pn_expr->pn_type) {
1163        case TOK_LET:
1164          /* let statement */
1165          assert(node->pn_expr->pn_arity == PN_BINARY);
1166          instrument_statement(node->pn_expr, f, indent, false);
1167          break;
1168        case TOK_LC:
1169          /* block */
1170          Stream_printf(f, "%*s", indent, "");
1171          Stream_write_string(f, "{\n");
1172          instrument_statement(node->pn_expr, f, indent + 2, false);
1173          Stream_printf(f, "%*s", indent, "");
1174          Stream_write_string(f, "}\n");
1175          break;
1176        case TOK_FOR:
1177          instrument_statement(node->pn_expr, f, indent, false);
1178          break;
1179        default:
1180          abort();
1181          break;
1182        }
1183        break;
1184      case TOK_LET:
1185        switch (node->pn_arity) {
1186        case PN_BINARY:
1187          /* let statement */
1188          Stream_printf(f, "%*s", indent, "");
1189          Stream_write_string(f, "let (");
1190          assert(node->pn_left->pn_type == TOK_LP);
1191          assert(node->pn_left->pn_arity == PN_LIST);
1192          instrument_declarations(node->pn_left, f);
1193          Stream_write_string(f, ") {\n");
1194          instrument_statement(node->pn_right, f, indent + 2, false);
1195          Stream_printf(f, "%*s", indent, "");
1196          Stream_write_string(f, "}\n");
1197          break;
1198        case PN_LIST:
1199          /* let definition */
1200          Stream_printf(f, "%*s", indent, "");
1201          instrument_expression(node, f);
1202          Stream_write_string(f, ";\n");
1203          break;
1204        default:
1205          abort();
1206          break;
1207        }
1208        break;
1209      case TOK_DEBUGGER:
1210        Stream_printf(f, "%*s", indent, "");
1211        Stream_write_string(f, "debugger;\n");
1212      break;      break;
1213    default:    default:
1214      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);
1215    }    }
1216  }  }
1217    
# Line 872  Line 1220 
1220  TOK_FUNCTION is handled as a statement and as an expression.  TOK_FUNCTION is handled as a statement and as an expression.
1221  TOK_EXPORT, TOK_IMPORT are not handled.  TOK_EXPORT, TOK_IMPORT are not handled.
1222  */  */
1223  static void instrument_statement(JSParseNode * node, Stream * f, int indent) {  static void instrument_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
1224    if (node->pn_type != TOK_LC) {    if (node->pn_type != TOK_LC && node->pn_type != TOK_LEXICALSCOPE) {
1225      int line = node->pn_pos.begin.lineno;      uint16_t line = node->pn_pos.begin.lineno;
1226        if (line > num_lines) {
1227          fatal("file %s contains more than 65,535 lines", file_id);
1228        }
1229    
1230      /* the root node has line number 0 */      /* the root node has line number 0 */
1231      if (line != 0) {      if (line != 0) {
1232        Stream_printf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
# Line 882  Line 1234 
1234        lines[line - 1] = 1;        lines[line - 1] = 1;
1235      }      }
1236    }    }
1237    output_statement(node, f, indent);    output_statement(node, f, indent, is_jscoverage_if);
1238  }  }
1239    
1240  static bool characters_start_with(const jschar * characters, size_t line_start, size_t line_end, const char * prefix) {  static bool characters_start_with(const jschar * characters, size_t line_start, size_t line_end, const char * prefix) {
# Line 904  Line 1256 
1256    }    }
1257  }  }
1258    
1259    static bool characters_are_white_space(const jschar * characters, size_t line_start, size_t line_end) {
1260      /* XXX - other Unicode space */
1261      const jschar * end = characters + line_end;
1262      for (const jschar * p = characters + line_start; p < end; p++) {
1263        jschar c = *p;
1264        if (c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0) {
1265          continue;
1266        }
1267        else {
1268          return false;
1269        }
1270      }
1271      return true;
1272    }
1273    
1274    static void error_reporter(JSContext * context, const char * message, JSErrorReport * report) {
1275      warn_source(file_id, report->lineno, "%s", message);
1276    }
1277    
1278  void jscoverage_instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output) {  void jscoverage_instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output) {
1279    file_id = id;    file_id = id;
1280    
   /* scan the javascript */  
   JSTokenStream * token_stream = js_NewTokenStream(context, characters, num_characters, NULL, 1, NULL);  
   if (token_stream == NULL) {  
     fatal("cannot create token stream from file: %s", file_id);  
   }  
   
1281    /* parse the javascript */    /* parse the javascript */
1282    JSParseNode * node = js_ParseTokenStream(context, global, token_stream);    JSParseContext parse_context;
1283      if (! js_InitParseContext(context, &parse_context, NULL, NULL, characters, num_characters, NULL, NULL, 1)) {
1284        fatal("cannot create token stream from file %s", file_id);
1285      }
1286      JSErrorReporter old_error_reporter = JS_SetErrorReporter(context, error_reporter);
1287      JSParseNode * node = js_ParseScript(context, global, &parse_context);
1288    if (node == NULL) {    if (node == NULL) {
1289      fatal("parse error in file: %s", file_id);      js_ReportUncaughtException(context);
1290        fatal("parse error in file %s", file_id);
1291    }    }
1292    int num_lines = node->pn_pos.end.lineno;    JS_SetErrorReporter(context, old_error_reporter);
1293      num_lines = node->pn_pos.end.lineno;
1294    lines = xmalloc(num_lines);    lines = xmalloc(num_lines);
1295    for (int i = 0; i < num_lines; i++) {    for (unsigned int i = 0; i < num_lines; i++) {
1296      lines[i] = 0;      lines[i] = 0;
1297    }    }
1298    
1299    /*    /* search code for conditionals */
1300    An instrumented JavaScript file has 3 sections:    exclusive_directives = xnew(bool, num_lines);
1301    1. initialization    for (unsigned int i = 0; i < num_lines; i++) {
1302    2. instrumented source code      exclusive_directives[i] = false;
   3. original source code  
   */  
   
   Stream * instrumented = Stream_new(0);  
   instrument_statement(node, instrumented, 0);  
   
   /* write line number info to the output */  
   Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n");  
   Stream_write_string(output, "if (! top._$jscoverage) {\n  top._$jscoverage = {};\n}\n");  
   Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n");  
   Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id);  
   Stream_printf(output, "  _$jscoverage['%s'] = [];\n", file_id);  
   for (int i = 0; i < num_lines; i++) {  
     if (lines[i]) {  
       Stream_printf(output, "  _$jscoverage['%s'][%d] = 0;\n", file_id, i + 1);  
     }  
1303    }    }
   Stream_write_string(output, "}\n");  
   free(lines);  
   lines = NULL;  
   
   /* copy the instrumented source code to the output */  
   Stream_write(output, instrumented->data, instrumented->length);  
1304    
   /* conditionals */  
1305    bool has_conditionals = false;    bool has_conditionals = false;
1306      struct IfDirective * if_directives = NULL;
1307    size_t line_number = 0;    size_t line_number = 0;
1308    size_t i = 0;    size_t i = 0;
1309    while (i < num_characters) {    while (i < num_characters) {
1310        if (line_number == UINT16_MAX) {
1311          fatal("file %s contains more than 65,535 lines", file_id);
1312        }
1313      line_number++;      line_number++;
1314      size_t line_start = i;      size_t line_start = i;
1315      jschar c;      jschar c;
# Line 972  Line 1325 
1325          break;          break;
1326        default:        default:
1327          i++;          i++;
         break;  
1328        }        }
1329      }      }
1330      size_t line_end = i;      size_t line_end = i;
# Line 982  Line 1334 
1334          i++;          i++;
1335        }        }
1336      }      }
1337    
1338      if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_IF")) {      if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_IF")) {
1339        if (! has_conditionals) {        has_conditionals = true;
1340          has_conditionals = true;  
1341          Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);        if (characters_are_white_space(characters, line_start + 16, line_end)) {
1342        }          exclusive_directives[line_number - 1] = true;
1343        Stream_write_string(output, "if (!(");        }
1344        for (size_t j = line_start + 16; j < line_end; j++) {        else {
1345          jschar c = characters[j];          struct IfDirective * if_directive = xnew(struct IfDirective, 1);
1346          if (c == '\t' || (32 <= c && c <= 126)) {          if_directive->condition_start = characters + line_start + 16;
1347            Stream_write_char(output, c);          if_directive->condition_end = characters + line_end;
1348          }          if_directive->start_line = line_number;
1349          else {          if_directive->end_line = 0;
1350            Stream_printf(output, "\\u%04x", c);          if_directive->next = if_directives;
1351          }          if_directives = if_directive;
1352        }        }
       Stream_write_string(output, ")) {\n");  
       Stream_printf(output, "  _$jscoverage['%s'].conditionals[%d] = ", file_id, line_number);  
1353      }      }
1354      else if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_ENDIF")) {      else if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_ENDIF")) {
1355        Stream_printf(output, "%d;\n", line_number);        for (struct IfDirective * p = if_directives; p != NULL; p = p->next) {
1356        Stream_printf(output, "}\n");          if (p->end_line == 0) {
1357              p->end_line = line_number;
1358              break;
1359            }
1360          }
1361        }
1362      }
1363    
1364      /*
1365      An instrumented JavaScript file has 4 sections:
1366      1. initialization
1367      2. instrumented source code
1368      3. conditionals
1369      4. original source code
1370      */
1371    
1372      Stream * instrumented = Stream_new(0);
1373      instrument_statement(node, instrumented, 0, false);
1374      js_FinishParseContext(context, &parse_context);
1375    
1376      /* write line number info to the output */
1377      Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n");
1378      if (jscoverage_mozilla) {
1379        Stream_write_string(output, "try {\n");
1380        Stream_write_string(output, "  Components.utils.import('resource://gre/modules/jscoverage.jsm');\n");
1381        Stream_printf(output, "  dump('%s: successfully imported jscoverage module\\n');\n", id);
1382        Stream_write_string(output, "}\n");
1383        Stream_write_string(output, "catch (e) {\n");
1384        Stream_write_string(output, "  _$jscoverage = {};\n");
1385        Stream_printf(output, "  dump('%s: failed to import jscoverage module - coverage not available for this file\\n');\n", id);
1386        Stream_write_string(output, "}\n");
1387      }
1388      else {
1389        Stream_write_string(output, "if (! top._$jscoverage) {\n  top._$jscoverage = {};\n}\n");
1390        Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n");
1391      }
1392      Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id);
1393      Stream_printf(output, "  _$jscoverage['%s'] = [];\n", file_id);
1394      for (int i = 0; i < num_lines; i++) {
1395        if (lines[i]) {
1396          Stream_printf(output, "  _$jscoverage['%s'][%d] = 0;\n", file_id, i + 1);
1397      }      }
1398    }    }
1399      Stream_write_string(output, "}\n");
1400      free(lines);
1401      lines = NULL;
1402      free(exclusive_directives);
1403      exclusive_directives = NULL;
1404    
1405      /* conditionals */
1406      if (has_conditionals) {
1407        Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);
1408      }
1409    
1410      /* copy the instrumented source code to the output */
1411      Stream_write(output, instrumented->data, instrumented->length);
1412    
1413      /* conditionals */
1414      for (struct IfDirective * if_directive = if_directives; if_directive != NULL; if_directive = if_directive->next) {
1415        Stream_write_string(output, "if (!(");
1416        print_javascript(if_directive->condition_start, if_directive->condition_end - if_directive->condition_start, output);
1417        Stream_write_string(output, ")) {\n");
1418        Stream_printf(output, "  _$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_directive->start_line, if_directive->end_line);
1419        Stream_write_string(output, "}\n");
1420      }
1421    
1422      /* free */
1423      while (if_directives != NULL) {
1424        struct IfDirective * if_directive = if_directives;
1425        if_directives = if_directives->next;
1426        free(if_directive);
1427      }
1428    
1429    /* copy the original source to the output */    /* copy the original source to the output */
1430    Stream_printf(output, "_$jscoverage['%s'].source = ", file_id);    Stream_printf(output, "_$jscoverage['%s'].source = ", file_id);
# Line 1044  Line 1464 
1464            /* line feed (new line) */            /* line feed (new line) */
1465            done = true;            done = true;
1466            break;            break;
1467            /* IE doesn't support this */
1468            /*
1469          case 0xb:          case 0xb:
           /* vertical tab */  
1470            Stream_write_string(output, "\\v");            Stream_write_string(output, "\\v");
1471            break;            break;
1472            */
1473          case 0xc:          case 0xc:
1474            /* form feed */            /* form feed */
1475            Stream_write_string(output, "\\f");            Stream_write_string(output, "\\f");
# Line 1102  Line 1524 
1524            /* line feed (new line) */            /* line feed (new line) */
1525            done = true;            done = true;
1526            break;            break;
1527            /* IE doesn't support this */
1528            /*
1529          case 0xb:          case 0xb:
           /* vertical tab */  
1530            Stream_write_string(output, "\\v");            Stream_write_string(output, "\\v");
1531            break;            break;
1532            */
1533          case 0xc:          case 0xc:
1534            /* form feed */            /* form feed */
1535            Stream_write_string(output, "\\f");            Stream_write_string(output, "\\f");
# Line 1232  Line 1656 
1656  }  }
1657    
1658  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) {
1659      int result = 0;
1660    
1661    jschar * base = js_InflateString(context, (char *) json, &length);    jschar * base = js_InflateString(context, (char *) json, &length);
1662    if (base == NULL) {    if (base == NULL) {
1663      fatal("out of memory");      fatal("out of memory");
# Line 1244  Line 1670 
1670    
1671    JS_free(context, base);    JS_free(context, base);
1672    
1673    JSTokenStream * token_stream = js_NewTokenStream(context, parenthesized_json, length + 2, NULL, 1, NULL);    JSParseContext parse_context;
1674    if (token_stream == NULL) {    if (! js_InitParseContext(context, &parse_context, NULL, NULL, parenthesized_json, length + 2, NULL, NULL, 1)) {
1675      fatal("cannot create token stream");      free(parenthesized_json);
1676        return -1;
1677    }    }
1678      JSParseNode * root = js_ParseScript(context, global, &parse_context);
   JSParseNode * root = js_ParseTokenStream(context, global, token_stream);  
1679    free(parenthesized_json);    free(parenthesized_json);
1680    if (root == NULL) {    if (root == NULL) {
1681      return -1;      result = -1;
1682        goto done;
1683    }    }
1684    
1685    /* root node must be TOK_LC */    /* root node must be TOK_LC */
1686    if (root->pn_type != TOK_LC) {    if (root->pn_type != TOK_LC) {
1687      return -1;      result = -1;
1688        goto done;
1689    }    }
1690    JSParseNode * semi = root->pn_u.list.head;    JSParseNode * semi = root->pn_u.list.head;
1691    
1692    /* 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 */
1693    if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {    if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {
1694      return -1;      result = -1;
1695        goto done;
1696    }    }
1697    JSParseNode * parenthesized = semi->pn_kid;    JSParseNode * parenthesized = semi->pn_kid;
1698    
1699    /* this must be a parenthesized expression */    /* this must be a parenthesized expression */
1700    if (parenthesized->pn_type != TOK_RP) {    if (parenthesized->pn_type != TOK_RP) {
1701      return -1;      result = -1;
1702        goto done;
1703    }    }
1704    JSParseNode * object = parenthesized->pn_kid;    JSParseNode * object = parenthesized->pn_kid;
1705    
1706    /* this must be an object literal */    /* this must be an object literal */
1707    if (object->pn_type != TOK_RC) {    if (object->pn_type != TOK_RC) {
1708      return -1;      result = -1;
1709        goto done;
1710    }    }
1711    
1712    for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {    for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {
1713      /* every element of this list must be TOK_COLON */      /* every element of this list must be TOK_COLON */
1714      if (p->pn_type != TOK_COLON) {      if (p->pn_type != TOK_COLON) {
1715        return -1;        result = -1;
1716          goto done;
1717      }      }
1718    
1719      /* the key must be a string representing the file */      /* the key must be a string representing the file */
1720      JSParseNode * key = p->pn_left;      JSParseNode * key = p->pn_left;
1721      if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {      if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {
1722        return -1;        result = -1;
1723          goto done;
1724      }      }
1725      char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));      char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));
1726    
1727      /* the value must be an object literal OR an array */      /* the value must be an object literal OR an array */
1728      JSParseNode * value = p->pn_right;      JSParseNode * value = p->pn_right;
1729      if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {      if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {
1730        return -1;        result = -1;
1731          goto done;
1732      }      }
1733    
1734      JSParseNode * array = NULL;      JSParseNode * array = NULL;
# Line 1306  Line 1740 
1740      else if (value->pn_type == TOK_RC) {      else if (value->pn_type == TOK_RC) {
1741        /* an object literal */        /* an object literal */
1742        if (value->pn_count != 2) {        if (value->pn_count != 2) {
1743          return -1;          result = -1;
1744            goto done;
1745        }        }
1746        for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {        for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {
1747          if (element->pn_type != TOK_COLON) {          if (element->pn_type != TOK_COLON) {
1748            return -1;            result = -1;
1749              goto done;
1750          }          }
1751          JSParseNode * left = element->pn_left;          JSParseNode * left = element->pn_left;
1752          if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {          if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {
1753            return -1;            result = -1;
1754              goto done;
1755          }          }
1756          const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));          const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));
1757          if (strcmp(s, "coverage") == 0) {          if (strcmp(s, "coverage") == 0) {
1758            array = element->pn_right;            array = element->pn_right;
1759            if (array->pn_type != TOK_RB) {            if (array->pn_type != TOK_RB) {
1760              return -1;              result = -1;
1761                goto done;
1762            }            }
1763          }          }
1764          else if (strcmp(s, "source") == 0) {          else if (strcmp(s, "source") == 0) {
1765            source = element->pn_right;            source = element->pn_right;
1766            if (source->pn_type != TOK_RB) {            if (source->pn_type != TOK_RB) {
1767              return -1;              result = -1;
1768                goto done;
1769            }            }
1770          }          }
1771          else {          else {
1772            return -1;            result = -1;
1773              goto done;
1774          }          }
1775        }        }
1776      }      }
1777      else {      else {
1778        return -1;        result = -1;
1779          goto done;
1780      }      }
1781    
1782      if (array == NULL) {      if (array == NULL) {
1783        return -1;        result = -1;
1784          goto done;
1785      }      }
1786    
1787      /* look up the file in the coverage table */      /* look up the file in the coverage table */
# Line 1351  Line 1793 
1793        file_coverage->id = id;        file_coverage->id = id;
1794        file_coverage->num_coverage_lines = array->pn_count;        file_coverage->num_coverage_lines = array->pn_count;
1795        file_coverage->coverage_lines = xnew(int, array->pn_count);        file_coverage->coverage_lines = xnew(int, array->pn_count);
1796        if (source == NULL) {        file_coverage->source_lines = NULL;
         file_coverage->source_lines = NULL;  
       }  
       else {  
         file_coverage->num_source_lines = source->pn_count;  
         file_coverage->source_lines = xnew(char *, source->pn_count);  
         uint32 i = 0;  
         for (JSParseNode * element = source->pn_head; element != NULL; element = element->pn_next, i++) {  
           if (element->pn_type != TOK_STRING) {  
             return -1;  
           }  
           file_coverage->source_lines[i] = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(element->pn_atom)));  
         }  
         assert(i == source->pn_count);  
       }  
1797    
1798        /* set coverage for all lines */        /* set coverage for all lines */
1799        uint32 i = 0;        uint32 i = 0;
# Line 1377  Line 1805 
1805            file_coverage->coverage_lines[i] = -1;            file_coverage->coverage_lines[i] = -1;
1806          }          }
1807          else {          else {
1808            return -1;            result = -1;
1809              goto done;
1810          }          }
1811        }        }
1812        assert(i == array->pn_count);        assert(i == array->pn_count);
# Line 1393  Line 1822 
1822        /* sanity check */        /* sanity check */
1823        assert(strcmp(file_coverage->id, id_bytes) == 0);        assert(strcmp(file_coverage->id, id_bytes) == 0);
1824        if (file_coverage->num_coverage_lines != array->pn_count) {        if (file_coverage->num_coverage_lines != array->pn_count) {
1825          return -2;          result = -2;
1826            goto done;
1827        }        }
1828    
1829        /* merge the coverage */        /* merge the coverage */
# Line 1401  Line 1831 
1831        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++) {
1832          if (element->pn_type == TOK_NUMBER) {          if (element->pn_type == TOK_NUMBER) {
1833            if (file_coverage->coverage_lines[i] == -1) {            if (file_coverage->coverage_lines[i] == -1) {
1834              return -2;              result = -2;
1835                goto done;
1836            }            }
1837            file_coverage->coverage_lines[i] += (int) element->pn_dval;            file_coverage->coverage_lines[i] += (int) element->pn_dval;
1838          }          }
1839          else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {          else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1840            if (file_coverage->coverage_lines[i] != -1) {            if (file_coverage->coverage_lines[i] != -1) {
1841              return -2;              result = -2;
1842                goto done;
1843            }            }
1844          }          }
1845          else {          else {
1846            return -1;            result = -1;
1847              goto done;
1848          }          }
1849        }        }
1850        assert(i == array->pn_count);        assert(i == array->pn_count);
1851        }
1852    
1853        /* if this JSON file has source, use it */      /* if this JSON file has source, use it */
1854        if (file_coverage->source_lines == NULL && source != NULL) {      if (file_coverage->source_lines == NULL && source != NULL) {
1855          file_coverage->num_source_lines = source->pn_count;        file_coverage->num_source_lines = source->pn_count;
1856          file_coverage->source_lines = xnew(char *, source->pn_count);        file_coverage->source_lines = xnew(char *, source->pn_count);
1857          uint32 i = 0;        uint32 i = 0;
1858          for (JSParseNode * element = source->pn_head; element != NULL; element = element->pn_next, i++) {        for (JSParseNode * element = source->pn_head; element != NULL; element = element->pn_next, i++) {
1859            if (element->pn_type != TOK_STRING) {          if (element->pn_type != TOK_STRING) {
1860              return -1;            result = -1;
1861            }            goto done;
           file_coverage->source_lines[i] = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(element->pn_atom)));  
1862          }          }
1863          assert(i == source->pn_count);          file_coverage->source_lines[i] = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(element->pn_atom)));
1864        }        }
1865          assert(i == source->pn_count);
1866      }      }
1867    }    }
1868    
1869    return 0;  done:
1870      js_FinishParseContext(context, &parse_context);
1871      return result;
1872  }  }

Legend:
Removed from v.190  
changed lines
  Added in v.377

  ViewVC Help
Powered by ViewVC 1.1.24