/[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 101 by siliconforks, Sat May 24 18:11:30 2008 UTC revision 375 by siliconforks, Tue Oct 28 05:30:01 2008 UTC
# Line 17  Line 17 
17      51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.      51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */  */
19    
20    #include <config.h>
21    
22  #include "instrument-js.h"  #include "instrument-js.h"
23    
24  #include <assert.h>  #include <assert.h>
# Line 24  Line 26 
26  #include <string.h>  #include <string.h>
27    
28  #include <jsapi.h>  #include <jsapi.h>
29    #include <jsarena.h>
30  #include <jsatom.h>  #include <jsatom.h>
31    #include <jsemit.h>
32    #include <jsexn.h>
33  #include <jsfun.h>  #include <jsfun.h>
34  #include <jsinterp.h>  #include <jsinterp.h>
35    #include <jsiter.h>
36  #include <jsparse.h>  #include <jsparse.h>
37  #include <jsregexp.h>  #include <jsregexp.h>
38  #include <jsscope.h>  #include <jsscope.h>
39  #include <jsstr.h>  #include <jsstr.h>
40    
41    #include "encoding.h"
42    #include "global.h"
43    #include "highlight.h"
44    #include "resource-manager.h"
45  #include "util.h"  #include "util.h"
46    
47    struct IfDirective {
48      const jschar * condition_start;
49      const jschar * condition_end;
50      uint16_t start_line;
51      uint16_t end_line;
52      struct IfDirective * next;
53    };
54    
55    bool jscoverage_mozilla = false;
56    
57    static bool * exclusive_directives = NULL;
58    
59  static JSRuntime * runtime = NULL;  static JSRuntime * runtime = NULL;
60  static JSContext * context = NULL;  static JSContext * context = NULL;
61  static JSObject * global = NULL;  static JSObject * global = NULL;
62    static JSVersion js_version = JSVERSION_ECMA_3;
63    
64  /*  /*
65  JSParseNode objects store line numbers starting from 1.  JSParseNode objects store line numbers starting from 1.
# Line 44  Line 67 
67  */  */
68  static const char * file_id = NULL;  static const char * file_id = NULL;
69  static char * lines = NULL;  static char * lines = NULL;
70    static uint16_t num_lines = 0;
71    
72    void jscoverage_set_js_version(const char * version) {
73      js_version = JS_StringToVersion(version);
74      if (js_version != JSVERSION_UNKNOWN) {
75        return;
76      }
77    
78      char * end;
79      js_version = (JSVersion) strtol(version, &end, 10);
80      if ((size_t) (end - version) != strlen(version)) {
81        fatal("invalid version: %s", version);
82      }
83    }
84    
85  void jscoverage_init(void) {  void jscoverage_init(void) {
86    runtime = JS_NewRuntime(8L * 1024L * 1024L);    runtime = JS_NewRuntime(8L * 1024L * 1024L);
# Line 56  Line 93 
93      fatal("cannot create context");      fatal("cannot create context");
94    }    }
95    
96      JS_SetVersion(context, js_version);
97    
98    global = JS_NewObject(context, NULL, NULL, NULL);    global = JS_NewObject(context, NULL, NULL, NULL);
99    if (global == NULL) {    if (global == NULL) {
100      fatal("cannot create global object");      fatal("cannot create global object");
# Line 71  Line 110 
110    JS_DestroyRuntime(runtime);    JS_DestroyRuntime(runtime);
111  }  }
112    
113    static void print_javascript(const jschar * characters, size_t num_characters, Stream * f) {
114      for (size_t i = 0; i < num_characters; i++) {
115        jschar c = characters[i];
116        /*
117        XXX does not handle no-break space, other unicode "space separator"
118        */
119        switch (c) {
120        case 0x9:
121        case 0xB:
122        case 0xC:
123          Stream_write_char(f, c);
124          break;
125        default:
126          if (32 <= c && c <= 126) {
127            Stream_write_char(f, c);
128          }
129          else {
130            Stream_printf(f, "\\u%04x", c);
131          }
132          break;
133        }
134      }
135    }
136    
137  static void print_string(JSString * s, Stream * f) {  static void print_string(JSString * s, Stream * f) {
138    for (size_t i = 0; i < s->length; i++) {    size_t length = JSSTRING_LENGTH(s);
139      char c = s->chars[i];    jschar * characters = JSSTRING_CHARS(s);
140      Stream_write_char(f, c);    for (size_t i = 0; i < length; i++) {
141        jschar c = characters[i];
142        if (32 <= c && c <= 126) {
143          switch (c) {
144          case '"':
145            Stream_write_string(f, "\\\"");
146            break;
147    /*
148          case '\'':
149            Stream_write_string(f, "\\'");
150            break;
151    */
152          case '\\':
153            Stream_write_string(f, "\\\\");
154            break;
155          default:
156            Stream_write_char(f, c);
157            break;
158          }
159        }
160        else {
161          switch (c) {
162          case 0x8:
163            Stream_write_string(f, "\\b");
164            break;
165          case 0x9:
166            Stream_write_string(f, "\\t");
167            break;
168          case 0xa:
169            Stream_write_string(f, "\\n");
170            break;
171          /* IE doesn't support this */
172          /*
173          case 0xb:
174            Stream_write_string(f, "\\v");
175            break;
176          */
177          case 0xc:
178            Stream_write_string(f, "\\f");
179            break;
180          case 0xd:
181            Stream_write_string(f, "\\r");
182            break;
183          default:
184            Stream_printf(f, "\\u%04x", c);
185            break;
186          }
187        }
188    }    }
189  }  }
190    
# Line 84  Line 194 
194    print_string(s, f);    print_string(s, f);
195  }  }
196    
197  static void print_string_jsval(jsval value, Stream * f) {  static void print_regex(jsval value, Stream * f) {
198    assert(JSVAL_IS_STRING(value));    assert(JSVAL_IS_STRING(value));
199    JSString * s = JSVAL_TO_STRING(value);    JSString * s = JSVAL_TO_STRING(value);
200    print_string(s, f);    size_t length = JSSTRING_LENGTH(s);
201      jschar * characters = JSSTRING_CHARS(s);
202      for (size_t i = 0; i < length; i++) {
203        jschar c = characters[i];
204        if (32 <= c && c <= 126) {
205          Stream_write_char(f, c);
206        }
207        else {
208          Stream_printf(f, "\\u%04x", c);
209        }
210      }
211  }  }
212    
213  static void print_quoted_string_atom(JSAtom * atom, Stream * f) {  static void print_quoted_string_atom(JSAtom * atom, Stream * f) {
214    assert(ATOM_IS_STRING(atom));    assert(ATOM_IS_STRING(atom));
215    JSString * s = ATOM_TO_STRING(atom);    JSString * s = ATOM_TO_STRING(atom);
216    JSString * quoted = js_QuoteString(context, s, '"');    Stream_write_char(f, '"');
217    print_string(quoted, f);    print_string(s, f);
218      Stream_write_char(f, '"');
219  }  }
220    
221  static const char * get_op(uint8 op) {  static const char * get_op(uint8 op) {
222    switch(op) {    switch(op) {
223      case JSOP_OR:
224        return "||";
225      case JSOP_AND:
226        return "&&";
227    case JSOP_BITOR:    case JSOP_BITOR:
228      return "|";      return "|";
229    case JSOP_BITXOR:    case JSOP_BITXOR:
# Line 109  Line 234 
234      return "==";      return "==";
235    case JSOP_NE:    case JSOP_NE:
236      return "!=";      return "!=";
237    case JSOP_NEW_EQ:    case JSOP_STRICTEQ:
238      return "===";      return "===";
239    case JSOP_NEW_NE:    case JSOP_STRICTNE:
240      return "!==";      return "!==";
241    case JSOP_LT:    case JSOP_LT:
242      return "<";      return "<";
# Line 143  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  static void instrument_function(JSParseNode * node, Stream * f, int indent) {  enum FunctionType {
275      assert(node->pn_arity == PN_FUNC);    FUNCTION_NORMAL,
276      assert(ATOM_IS_OBJECT(node->pn_funAtom));    FUNCTION_GETTER_OR_SETTER
277      JSObject * object = ATOM_TO_OBJECT(node->pn_funAtom);  };
278      assert(JS_ObjectIsFunction(context, object));  
279      JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);  static void output_for_in(JSParseNode * node, Stream * f) {
280      assert(function);    assert(node->pn_type == TOK_FOR);
281      assert(object == function->object);    assert(node->pn_arity == PN_BINARY);
282      Stream_printf(f, "%*s", indent, "");    Stream_write_string(f, "for ");
283      Stream_write_string(f, "function");    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      /* function name */  static void output_array_comprehension_or_generator_expression(JSParseNode * node, Stream * f) {
292      if (function->atom) {    assert(node->pn_type == TOK_LEXICALSCOPE);
293        Stream_write_char(f, ' ');    assert(node->pn_arity == PN_NAME);
294        print_string_atom(function->atom, f);    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) {
329      assert(node->pn_type == TOK_FUNCTION);
330      assert(node->pn_arity == PN_FUNC);
331      JSObject * object = node->pn_funpob->object;
332      assert(JS_ObjectIsFunction(context, object));
333      JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);
334      assert(function);
335      assert(object == &function->object);
336      Stream_printf(f, "%*s", indent, "");
337      if (type == FUNCTION_NORMAL) {
338        Stream_write_string(f, "function ");
339      }
340    
341      /* function name */
342      if (function->atom) {
343        print_string_atom(function->atom, f);
344      }
345    
346      /* function parameters */    /*
347      Stream_write_string(f, "(");    function parameters - see JS_DecompileFunction in jsapi.cpp, which calls
348      JSAtom ** params = xmalloc(function->nargs * sizeof(JSAtom *));    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");
       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);  
358      }      }
359      for (int i = 0; i < function->nargs; i++) {    }
360        assert(params[i] != NULL);    bool destructuring = false;
361        if (i > 0) {    for (int i = 0; i < function->nargs; i++) {
362          Stream_write_string(f, ", ");      if (i > 0) {
363        }        Stream_write_string(f, ", ");
364        if (ATOM_IS_STRING(params[i])) {      }
365          print_string_atom(params[i], f);      JSAtom * param = JS_LOCAL_NAME_TO_ATOM(local_names[i]);
366        if (param == NULL) {
367          destructuring = true;
368          JSParseNode * expression = NULL;
369          assert(node->pn_body->pn_type == TOK_LC || node->pn_body->pn_type == TOK_BODY);
370          JSParseNode * semi = node->pn_body->pn_head;
371          assert(semi->pn_type == TOK_SEMI);
372          JSParseNode * comma = semi->pn_kid;
373          assert(comma->pn_type == TOK_COMMA);
374          for (JSParseNode * p = comma->pn_head; p != NULL; p = p->pn_next) {
375            assert(p->pn_type == TOK_ASSIGN);
376            JSParseNode * rhs = p->pn_right;
377            assert(JSSTRING_LENGTH(ATOM_TO_STRING(rhs->pn_atom)) == 0);
378            if (rhs->pn_slot == i) {
379              expression = p->pn_left;
380              break;
381            }
382        }        }
383          assert(expression != NULL);
384          instrument_expression(expression, f);
385      }      }
386      Stream_write_string(f, ") {\n");      else {
387      free(params);        print_string_atom(param, f);
388        }
389      }
390      JS_FinishArenaPool(&pool);
391      Stream_write_string(f, ") {\n");
392    
393      /* function body */    /* function body */
394      instrument_statement(node->pn_body, f, indent + 2);    if (function->flags & JSFUN_EXPR_CLOSURE) {
395        /* expression closure - use output_statement instead of instrument_statement */
396        if (node->pn_body->pn_type == TOK_BODY) {
397          assert(node->pn_body->pn_arity == PN_LIST);
398          assert(node->pn_body->pn_count == 2);
399          output_statement(node->pn_body->pn_head->pn_next, f, indent + 2, false);
400        }
401        else {
402          output_statement(node->pn_body, f, indent + 2, false);
403        }
404      }
405      else {
406        assert(node->pn_body->pn_type == TOK_LC);
407        assert(node->pn_body->pn_arity == PN_LIST);
408        JSParseNode * p = node->pn_body->pn_head;
409        if (destructuring) {
410          p = p->pn_next;
411        }
412        for (; p != NULL; p = p->pn_next) {
413          instrument_statement(p, f, indent + 2, false);
414        }
415      }
416    
417      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 209  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 225  Line 501 
501  static void instrument_expression(JSParseNode * node, Stream * f) {  static void instrument_expression(JSParseNode * node, Stream * f) {
502    switch (node->pn_type) {    switch (node->pn_type) {
503    case TOK_FUNCTION:    case TOK_FUNCTION:
504      instrument_function(node, f, 0);      instrument_function(node, f, 0, FUNCTION_NORMAL);
505      break;      break;
506    case TOK_COMMA:    case TOK_COMMA:
507      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) {
# Line 236  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 267  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 331  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 379  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 must be parenthesized */
659        if (node->pn_expr->pn_type == TOK_NUMBER) {
660          Stream_write_char(f, '(');
661          instrument_expression(node->pn_expr, f);
662          Stream_write_char(f, ')');
663        }
664        else {
665          instrument_expression(node->pn_expr, f);
666        }
667      /*      /*
668      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'
669      contains illegal characters, we have to use the subscript syntax instead of      contains illegal characters, we have to use the subscript syntax instead of
670      the dot syntax.      the dot syntax.
671      */      */
     instrument_expression(node->pn_expr, f);  
672      assert(ATOM_IS_STRING(node->pn_atom));      assert(ATOM_IS_STRING(node->pn_atom));
673      {      {
674        JSString * s = ATOM_TO_STRING(node->pn_atom);        JSString * s = ATOM_TO_STRING(node->pn_atom);
675        /* XXX - semantics changed in 1.7 */        bool must_quote;
676        if (! ATOM_KEYWORD(node->pn_atom) && js_IsIdentifier(s)) {        if (JSSTRING_LENGTH(s) == 0) {
677          Stream_write_char(f, '.');          must_quote = true;
678          print_string_atom(node->pn_atom, f);        }
679          else if (js_CheckKeyword(JSSTRING_CHARS(s), JSSTRING_LENGTH(s)) != TOK_EOF) {
680            must_quote = true;
681          }
682          else if (! js_IsIdentifier(s)) {
683            must_quote = true;
684        }        }
685        else {        else {
686            must_quote = false;
687          }
688          if (must_quote) {
689          Stream_write_char(f, '[');          Stream_write_char(f, '[');
690          print_quoted_string_atom(node->pn_atom, f);          print_quoted_string_atom(node->pn_atom, f);
691          Stream_write_char(f, ']');          Stream_write_char(f, ']');
692        }        }
693          else {
694            Stream_write_char(f, '.');
695            print_string_atom(node->pn_atom, f);
696          }
697      }      }
698      break;      break;
699    case TOK_LB:    case TOK_LB:
# Line 428  Line 724 
724    case TOK_RC:    case TOK_RC:
725      Stream_write_char(f, '{');      Stream_write_char(f, '{');
726      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) {
727        assert(p->pn_type == TOK_COLON);        if (p->pn_type != TOK_COLON) {
728            fatal_source(file_id, p->pn_pos.begin.lineno, "unsupported node type (%d)", p->pn_type);
729          }
730        if (p != node->pn_head) {        if (p != node->pn_head) {
731          Stream_write_string(f, ", ");          Stream_write_string(f, ", ");
732        }        }
733        instrument_expression(p->pn_left, f);  
734        Stream_write_string(f, ": ");        /* check whether this is a getter or setter */
735        instrument_expression(p->pn_right, f);        switch (p->pn_op) {
736          case JSOP_GETTER:
737          case JSOP_SETTER:
738            if (p->pn_op == JSOP_GETTER) {
739              Stream_write_string(f, "get ");
740            }
741            else {
742              Stream_write_string(f, "set ");
743            }
744            instrument_expression(p->pn_left, f);
745            if (p->pn_right->pn_type != TOK_FUNCTION) {
746              fatal_source(file_id, p->pn_pos.begin.lineno, "expected function");
747            }
748            instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER);
749            break;
750          default:
751            instrument_expression(p->pn_left, f);
752            Stream_write_string(f, ": ");
753            instrument_expression(p->pn_right, f);
754            break;
755          }
756      }      }
757      Stream_write_char(f, '}');      Stream_write_char(f, '}');
758      break;      break;
# Line 449  Line 767 
767    case TOK_STRING:    case TOK_STRING:
768      print_quoted_string_atom(node->pn_atom, f);      print_quoted_string_atom(node->pn_atom, f);
769      break;      break;
770    case TOK_OBJECT:    case TOK_REGEXP:
771      switch (node->pn_op) {      assert(node->pn_op == JSOP_REGEXP);
772      case JSOP_OBJECT:      {
773        /* I assume this is JSOP_REGEXP */        JSObject * object = node->pn_pob->object;
774        abort();        jsval result;
775        break;        js_regexp_toString(context, object, &result);
776      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;  
777      }      }
778      break;      break;
779    case TOK_NUMBER:    case TOK_NUMBER:
# Line 512  Line 819 
819      Stream_write_string(f, " in ");      Stream_write_string(f, " in ");
820      instrument_expression(node->pn_right, f);      instrument_expression(node->pn_right, f);
821      break;      break;
822    default:    case TOK_LEXICALSCOPE:
823      fatal("unsupported node type in file %s: %d", file_id, node->pn_type);      assert(node->pn_arity == PN_NAME);
824    }      assert(node->pn_expr->pn_type == TOK_LET);
825  }      assert(node->pn_expr->pn_arity == PN_BINARY);
826        Stream_write_string(f, "let(");
827  static void instrument_var_statement(JSParseNode * node, Stream * f, int indent) {      assert(node->pn_expr->pn_left->pn_type == TOK_LP);
828    assert(node->pn_arity == PN_LIST);      assert(node->pn_expr->pn_left->pn_arity == PN_LIST);
829    Stream_printf(f, "%*s", indent, "");      instrument_declarations(node->pn_expr->pn_left, f);
830    Stream_write_string(f, "var ");      Stream_write_string(f, ") ");
831    for (struct JSParseNode * p = node->pn_u.list.head; p != NULL; p = p->pn_next) {      instrument_expression(node->pn_expr->pn_right, f);
832      assert(p->pn_type == TOK_NAME);      break;
833      assert(p->pn_arity == PN_NAME);    case TOK_YIELD:
834      if (p != node->pn_head) {      assert(node->pn_arity == PN_UNARY);
835        Stream_write_string(f, ", ");      Stream_write_string(f, "yield");
836        if (node->pn_kid != NULL) {
837          Stream_write_char(f, ' ');
838          instrument_expression(node->pn_kid, f);
839      }      }
840      print_string_atom(p->pn_atom, f);      break;
841      if (p->pn_expr != NULL) {    case TOK_ARRAYCOMP:
842        Stream_write_string(f, " = ");      assert(node->pn_arity == PN_LIST);
843        instrument_expression(p->pn_expr, f);      {
844          JSParseNode * block_node;
845          switch (node->pn_count) {
846          case 1:
847            block_node = node->pn_head;
848            break;
849          case 2:
850            block_node = node->pn_head->pn_next;
851            break;
852          default:
853            abort();
854            break;
855          }
856          Stream_write_char(f, '[');
857          output_array_comprehension_or_generator_expression(block_node, f);
858          Stream_write_char(f, ']');
859      }      }
860        break;
861      case TOK_VAR:
862        assert(node->pn_arity == PN_LIST);
863        Stream_write_string(f, "var ");
864        instrument_declarations(node, f);
865        break;
866      case TOK_LET:
867        assert(node->pn_arity == PN_LIST);
868        Stream_write_string(f, "let ");
869        instrument_declarations(node, f);
870        break;
871      default:
872        fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%d)", node->pn_type);
873    }    }
874  }  }
875    
876  static void output_statement(JSParseNode * node, Stream * f, int indent) {  static void output_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
877    switch (node->pn_type) {    switch (node->pn_type) {
878    case TOK_FUNCTION:    case TOK_FUNCTION:
879      instrument_function(node, f, indent);      instrument_function(node, f, indent, FUNCTION_NORMAL);
880      break;      break;
881    case TOK_LC:    case TOK_LC:
882      assert(node->pn_arity == PN_LIST);      assert(node->pn_arity == PN_LIST);
# Line 546  Line 884 
884      Stream_write_string(f, "{\n");      Stream_write_string(f, "{\n");
885  */  */
886      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) {
887        instrument_statement(p, f, indent);        instrument_statement(p, f, indent, false);
888      }      }
889  /*  /*
890      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
# Line 554  Line 892 
892  */  */
893      break;      break;
894    case TOK_IF:    case TOK_IF:
895      {
896      assert(node->pn_arity == PN_TERNARY);      assert(node->pn_arity == PN_TERNARY);
897    
898        uint16_t line = node->pn_pos.begin.lineno;
899        if (! is_jscoverage_if) {
900          if (line > num_lines) {
901            fatal("file %s contains more than 65,535 lines", file_id);
902          }
903          if (line >= 2 && exclusive_directives[line - 2]) {
904            is_jscoverage_if = true;
905          }
906        }
907    
908      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
909      Stream_write_string(f, "if (");      Stream_write_string(f, "if (");
910      instrument_expression(node->pn_kid1, f);      instrument_expression(node->pn_kid1, f);
911      Stream_write_string(f, ") {\n");      Stream_write_string(f, ") {\n");
912      instrument_statement(node->pn_kid2, f, indent + 2);      if (is_jscoverage_if && node->pn_kid3) {
913          uint16_t else_start = node->pn_kid3->pn_pos.begin.lineno;
914          uint16_t else_end = node->pn_kid3->pn_pos.end.lineno + 1;
915          Stream_printf(f, "%*s", indent + 2, "");
916          Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, else_start, else_end);
917        }
918        instrument_statement(node->pn_kid2, f, indent + 2, false);
919      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
920      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
921      if (node->pn_kid3) {  
922        if (node->pn_kid3 || is_jscoverage_if) {
923        Stream_printf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
924        Stream_write_string(f, "else {\n");        Stream_write_string(f, "else {\n");
925        instrument_statement(node->pn_kid3, f, indent + 2);  
926          if (is_jscoverage_if) {
927            uint16_t if_start = node->pn_kid2->pn_pos.begin.lineno + 1;
928            uint16_t if_end = node->pn_kid2->pn_pos.end.lineno + 1;
929            Stream_printf(f, "%*s", indent + 2, "");
930            Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_start, if_end);
931          }
932    
933          if (node->pn_kid3) {
934            instrument_statement(node->pn_kid3, f, indent + 2, is_jscoverage_if);
935          }
936    
937        Stream_printf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
938        Stream_write_string(f, "}\n");        Stream_write_string(f, "}\n");
939      }      }
940    
941      break;      break;
942      }
943    case TOK_SWITCH:    case TOK_SWITCH:
944      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
945      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
946      Stream_write_string(f, "switch (");      Stream_write_string(f, "switch (");
947      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
948      Stream_write_string(f, ") {\n");      Stream_write_string(f, ") {\n");
949      for (struct JSParseNode * p = node->pn_right->pn_head; p != NULL; p = p->pn_next) {      {
950        Stream_printf(f, "%*s", indent, "");        JSParseNode * list = node->pn_right;
951        switch (p->pn_type) {        if (list->pn_type == TOK_LEXICALSCOPE) {
952        case TOK_CASE:          list = list->pn_expr;
953          Stream_write_string(f, "case ");        }
954          instrument_expression(p->pn_left, f);        for (struct JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) {
955          Stream_write_string(f, ":\n");          Stream_printf(f, "%*s", indent, "");
956          break;          switch (p->pn_type) {
957        case TOK_DEFAULT:          case TOK_CASE:
958          Stream_write_string(f, "default:\n");            Stream_write_string(f, "case ");
959          break;            instrument_expression(p->pn_left, f);
960        default:            Stream_write_string(f, ":\n");
961          abort();            break;
962          break;          case TOK_DEFAULT:
963              Stream_write_string(f, "default:\n");
964              break;
965            default:
966              abort();
967              break;
968            }
969            instrument_statement(p->pn_right, f, indent + 2, false);
970        }        }
       instrument_statement(p->pn_right, f, indent + 2);  
971      }      }
972      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
973      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
# Line 606  Line 982 
982      Stream_write_string(f, "while (");      Stream_write_string(f, "while (");
983      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
984      Stream_write_string(f, ") {\n");      Stream_write_string(f, ") {\n");
985      instrument_statement(node->pn_right, f, indent + 2);      instrument_statement(node->pn_right, f, indent + 2, false);
986      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
987      break;      break;
988    case TOK_DO:    case TOK_DO:
989      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
990      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
991      Stream_write_string(f, "do {\n");      Stream_write_string(f, "do {\n");
992      instrument_statement(node->pn_left, f, indent + 2);      instrument_statement(node->pn_left, f, indent + 2, false);
993      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
994      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
995      Stream_write_string(f, "while (");      Stream_write_string(f, "while (");
# Line 623  Line 999 
999    case TOK_FOR:    case TOK_FOR:
1000      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
1001      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
     Stream_write_string(f, "for (");  
1002      switch (node->pn_left->pn_type) {      switch (node->pn_left->pn_type) {
1003      case TOK_IN:      case TOK_IN:
1004        /* for/in */        /* for/in */
1005        assert(node->pn_left->pn_arity == PN_BINARY);        assert(node->pn_left->pn_arity == PN_BINARY);
1006        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);  
1007        break;        break;
1008      case TOK_RESERVED:      case TOK_RESERVED:
1009        /* for (;;) */        /* for (;;) */
1010        assert(node->pn_left->pn_arity == PN_TERNARY);        assert(node->pn_left->pn_arity == PN_TERNARY);
1011          Stream_write_string(f, "for (");
1012        if (node->pn_left->pn_kid1) {        if (node->pn_left->pn_kid1) {
1013          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);  
         }  
1014        }        }
1015        Stream_write_string(f, ";");        Stream_write_string(f, ";");
1016        if (node->pn_left->pn_kid2) {        if (node->pn_left->pn_kid2) {
# Line 670  Line 1022 
1022          Stream_write_char(f, ' ');          Stream_write_char(f, ' ');
1023          instrument_expression(node->pn_left->pn_kid3, f);          instrument_expression(node->pn_left->pn_kid3, f);
1024        }        }
1025          Stream_write_char(f, ')');
1026        break;        break;
1027      default:      default:
1028        abort();        abort();
1029        break;        break;
1030      }      }
1031      Stream_write_string(f, ") {\n");      Stream_write_string(f, " {\n");
1032      instrument_statement(node->pn_right, f, indent + 2);      instrument_statement(node->pn_right, f, indent + 2, false);
1033      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
1034      break;      break;
1035    case TOK_THROW:    case TOK_THROW:
# Line 689  Line 1042 
1042    case TOK_TRY:    case TOK_TRY:
1043      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1044      Stream_write_string(f, "try {\n");      Stream_write_string(f, "try {\n");
1045      instrument_statement(node->pn_kid1, f, indent + 2);      instrument_statement(node->pn_kid1, f, indent + 2, false);
1046      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1047      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
1048      {      if (node->pn_kid2) {
1049        for (JSParseNode * catch = node->pn_kid2; catch != NULL; catch = catch->pn_kid2) {        assert(node->pn_kid2->pn_type == TOK_RESERVED);
1050          for (JSParseNode * scope = node->pn_kid2->pn_head; scope != NULL; scope = scope->pn_next) {
1051            assert(scope->pn_type == TOK_LEXICALSCOPE);
1052            JSParseNode * catch = scope->pn_expr;
1053          assert(catch->pn_type == TOK_CATCH);          assert(catch->pn_type == TOK_CATCH);
1054          Stream_printf(f, "%*s", indent, "");          Stream_printf(f, "%*s", indent, "");
1055          Stream_write_string(f, "catch (");          Stream_write_string(f, "catch (");
1056            /* this may not be a name - destructuring assignment */
1057            /*
1058          assert(catch->pn_kid1->pn_arity == PN_NAME);          assert(catch->pn_kid1->pn_arity == PN_NAME);
1059          print_string_atom(catch->pn_kid1->pn_atom, f);          print_string_atom(catch->pn_kid1->pn_atom, f);
1060          if (catch->pn_kid1->pn_expr) {          */
1061            instrument_expression(catch->pn_kid1, f);
1062            if (catch->pn_kid2) {
1063            Stream_write_string(f, " if ");            Stream_write_string(f, " if ");
1064            instrument_expression(catch->pn_kid1->pn_expr, f);            instrument_expression(catch->pn_kid2, f);
1065          }          }
1066          Stream_write_string(f, ") {\n");          Stream_write_string(f, ") {\n");
1067          instrument_statement(catch->pn_kid3, f, indent + 2);          instrument_statement(catch->pn_kid3, f, indent + 2, false);
1068          Stream_printf(f, "%*s", indent, "");          Stream_printf(f, "%*s", indent, "");
1069          Stream_write_string(f, "}\n");          Stream_write_string(f, "}\n");
1070        }        }
# Line 712  Line 1072 
1072      if (node->pn_kid3) {      if (node->pn_kid3) {
1073        Stream_printf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
1074        Stream_write_string(f, "finally {\n");        Stream_write_string(f, "finally {\n");
1075        instrument_statement(node->pn_kid3, f, indent + 2);        instrument_statement(node->pn_kid3, f, indent + 2, false);
1076        Stream_printf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
1077        Stream_write_string(f, "}\n");        Stream_write_string(f, "}\n");
1078      }      }
# Line 738  Line 1098 
1098      Stream_write_string(f, "with (");      Stream_write_string(f, "with (");
1099      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
1100      Stream_write_string(f, ") {\n");      Stream_write_string(f, ") {\n");
1101      instrument_statement(node->pn_right, f, indent + 2);      instrument_statement(node->pn_right, f, indent + 2, false);
1102      Stream_printf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1103      Stream_write_string(f, "}\n");      Stream_write_string(f, "}\n");
1104      break;      break;
1105    case TOK_VAR:    case TOK_VAR:
1106      instrument_var_statement(node, f, indent);      Stream_printf(f, "%*s", indent, "");
1107        instrument_expression(node, f);
1108      Stream_write_string(f, ";\n");      Stream_write_string(f, ";\n");
1109      break;      break;
1110    case TOK_RETURN:    case TOK_RETURN:
# Line 776  Line 1137 
1137      /*      /*
1138      ... use output_statement instead of instrument_statement.      ... use output_statement instead of instrument_statement.
1139      */      */
1140      output_statement(node->pn_expr, f, indent);      output_statement(node->pn_expr, f, indent, false);
1141        break;
1142      case TOK_LEXICALSCOPE:
1143        /* let statement */
1144        assert(node->pn_arity == PN_NAME);
1145        switch (node->pn_expr->pn_type) {
1146        case TOK_LET:
1147          /* let statement */
1148          assert(node->pn_expr->pn_arity == PN_BINARY);
1149          instrument_statement(node->pn_expr, f, indent, false);
1150          break;
1151        case TOK_LC:
1152          /* block */
1153          Stream_printf(f, "%*s", indent, "");
1154          Stream_write_string(f, "{\n");
1155          instrument_statement(node->pn_expr, f, indent + 2, false);
1156          Stream_printf(f, "%*s", indent, "");
1157          Stream_write_string(f, "}\n");
1158          break;
1159        case TOK_FOR:
1160          instrument_statement(node->pn_expr, f, indent, false);
1161          break;
1162        default:
1163          abort();
1164          break;
1165        }
1166        break;
1167      case TOK_LET:
1168        switch (node->pn_arity) {
1169        case PN_BINARY:
1170          /* let statement */
1171          Stream_printf(f, "%*s", indent, "");
1172          Stream_write_string(f, "let (");
1173          assert(node->pn_left->pn_type == TOK_LP);
1174          assert(node->pn_left->pn_arity == PN_LIST);
1175          instrument_declarations(node->pn_left, f);
1176          Stream_write_string(f, ") {\n");
1177          instrument_statement(node->pn_right, f, indent + 2, false);
1178          Stream_printf(f, "%*s", indent, "");
1179          Stream_write_string(f, "}\n");
1180          break;
1181        case PN_LIST:
1182          /* let definition */
1183          Stream_printf(f, "%*s", indent, "");
1184          instrument_expression(node, f);
1185          Stream_write_string(f, ";\n");
1186          break;
1187        default:
1188          abort();
1189          break;
1190        }
1191        break;
1192      case TOK_DEBUGGER:
1193        Stream_printf(f, "%*s", indent, "");
1194        Stream_write_string(f, "debugger;\n");
1195      break;      break;
1196    default:    default:
1197      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);
1198    }    }
1199  }  }
1200    
# Line 788  Line 1203 
1203  TOK_FUNCTION is handled as a statement and as an expression.  TOK_FUNCTION is handled as a statement and as an expression.
1204  TOK_EXPORT, TOK_IMPORT are not handled.  TOK_EXPORT, TOK_IMPORT are not handled.
1205  */  */
1206  static void instrument_statement(JSParseNode * node, Stream * f, int indent) {  static void instrument_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
1207    if (node->pn_type != TOK_LC) {    if (node->pn_type != TOK_LC && node->pn_type != TOK_LEXICALSCOPE) {
1208      int line = node->pn_pos.begin.lineno;      uint16_t line = node->pn_pos.begin.lineno;
1209        if (line > num_lines) {
1210          fatal("file %s contains more than 65,535 lines", file_id);
1211        }
1212    
1213      /* the root node has line number 0 */      /* the root node has line number 0 */
1214      if (line != 0) {      if (line != 0) {
1215        Stream_printf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
# Line 798  Line 1217 
1217        lines[line - 1] = 1;        lines[line - 1] = 1;
1218      }      }
1219    }    }
1220    output_statement(node, f, indent);    output_statement(node, f, indent, is_jscoverage_if);
1221  }  }
1222    
1223  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) {
1224    file_id = id;    const jschar * characters_end = characters + line_end;
1225      const jschar * cp = characters + line_start;
1226    /* scan the javascript */    const char * bp = prefix;
1227    size_t input_length = input->length;    for (;;) {
1228    jschar * base = js_InflateString(context, input->data, &input_length);      if (*bp == '\0') {
1229    if (base == NULL) {        return true;
1230      fatal("out of memory");      }
1231        else if (cp == characters_end) {
1232          return false;
1233        }
1234        else if (*cp != *bp) {
1235          return false;
1236        }
1237        bp++;
1238        cp++;
1239    }    }
1240    JSTokenStream * token_stream = js_NewTokenStream(context, base, input_length, NULL, 1, NULL);  }
1241    if (token_stream == NULL) {  
1242      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) {
1243      /* XXX - other Unicode space */
1244      const jschar * end = characters + line_end;
1245      for (const jschar * p = characters + line_start; p < end; p++) {
1246        jschar c = *p;
1247        if (c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0) {
1248          continue;
1249        }
1250        else {
1251          return false;
1252        }
1253    }    }
1254      return true;
1255    }
1256    
1257    static void error_reporter(JSContext * context, const char * message, JSErrorReport * report) {
1258      warn_source(file_id, report->lineno, "%s", message);
1259    }
1260    
1261    void jscoverage_instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output) {
1262      file_id = id;
1263    
1264    /* parse the javascript */    /* parse the javascript */
1265    JSParseNode * node = js_ParseTokenStream(context, global, token_stream);    JSParseContext parse_context;
1266      if (! js_InitParseContext(context, &parse_context, NULL, NULL, characters, num_characters, NULL, NULL, 1)) {
1267        fatal("cannot create token stream from file %s", file_id);
1268      }
1269      JSErrorReporter old_error_reporter = JS_SetErrorReporter(context, error_reporter);
1270      JSParseNode * node = js_ParseScript(context, global, &parse_context);
1271    if (node == NULL) {    if (node == NULL) {
1272      fatal("parse error in file: %s", file_id);      js_ReportUncaughtException(context);
1273        fatal("parse error in file %s", file_id);
1274    }    }
1275    int num_lines = node->pn_pos.end.lineno;    JS_SetErrorReporter(context, old_error_reporter);
1276      num_lines = node->pn_pos.end.lineno;
1277    lines = xmalloc(num_lines);    lines = xmalloc(num_lines);
1278    for (int i = 0; i < num_lines; i++) {    for (unsigned int i = 0; i < num_lines; i++) {
1279      lines[i] = 0;      lines[i] = 0;
1280    }    }
1281    
1282      /* search code for conditionals */
1283      exclusive_directives = xnew(bool, num_lines);
1284      for (unsigned int i = 0; i < num_lines; i++) {
1285        exclusive_directives[i] = false;
1286      }
1287    
1288      bool has_conditionals = false;
1289      struct IfDirective * if_directives = NULL;
1290      size_t line_number = 0;
1291      size_t i = 0;
1292      while (i < num_characters) {
1293        if (line_number == UINT16_MAX) {
1294          fatal("file %s contains more than 65,535 lines", file_id);
1295        }
1296        line_number++;
1297        size_t line_start = i;
1298        jschar c;
1299        bool done = false;
1300        while (! done && i < num_characters) {
1301          c = characters[i];
1302          switch (c) {
1303          case '\r':
1304          case '\n':
1305          case 0x2028:
1306          case 0x2029:
1307            done = true;
1308            break;
1309          default:
1310            i++;
1311          }
1312        }
1313        size_t line_end = i;
1314        if (i < num_characters) {
1315          i++;
1316          if (c == '\r' && i < num_characters && characters[i] == '\n') {
1317            i++;
1318          }
1319        }
1320    
1321        if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_IF")) {
1322          has_conditionals = true;
1323    
1324          if (characters_are_white_space(characters, line_start + 16, line_end)) {
1325            exclusive_directives[line_number - 1] = true;
1326          }
1327          else {
1328            struct IfDirective * if_directive = xnew(struct IfDirective, 1);
1329            if_directive->condition_start = characters + line_start + 16;
1330            if_directive->condition_end = characters + line_end;
1331            if_directive->start_line = line_number;
1332            if_directive->end_line = 0;
1333            if_directive->next = if_directives;
1334            if_directives = if_directive;
1335          }
1336        }
1337        else if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_ENDIF")) {
1338          for (struct IfDirective * p = if_directives; p != NULL; p = p->next) {
1339            if (p->end_line == 0) {
1340              p->end_line = line_number;
1341              break;
1342            }
1343          }
1344        }
1345      }
1346    
1347    /*    /*
1348    An instrumented JavaScript file has 3 sections:    An instrumented JavaScript file has 4 sections:
1349    1. initialization    1. initialization
1350    2. instrumented source code    2. instrumented source code
1351    3. original source code (TODO)    3. conditionals
1352      4. original source code
1353    */    */
1354    
1355    Stream * instrumented = Stream_new(0);    Stream * instrumented = Stream_new(0);
1356    instrument_statement(node, instrumented, 0);    instrument_statement(node, instrumented, 0, false);
1357      js_FinishParseContext(context, &parse_context);
1358    
1359    /* write line number info to the output */    /* write line number info to the output */
1360    Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n");    Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n");
1361    Stream_write_string(output, "if (! top._$jscoverage) {\n  top._$jscoverage = {};\n}\n");    if (jscoverage_mozilla) {
1362    Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n");      Stream_write_string(output, "try {\n");
1363        Stream_write_string(output, "  Components.utils.import('resource://gre/modules/jscoverage.jsm');\n");
1364        Stream_printf(output, "  dump('%s: successfully imported jscoverage module\\n');\n", id);
1365        Stream_write_string(output, "}\n");
1366        Stream_write_string(output, "catch (e) {\n");
1367        Stream_write_string(output, "  _$jscoverage = {};\n");
1368        Stream_printf(output, "  dump('%s: failed to import jscoverage module - coverage not available for this file\\n');\n", id);
1369        Stream_write_string(output, "}\n");
1370      }
1371      else {
1372        Stream_write_string(output, "if (! top._$jscoverage) {\n  top._$jscoverage = {};\n}\n");
1373        Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n");
1374      }
1375    Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id);    Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id);
1376    Stream_printf(output, "  _$jscoverage['%s'] = [];\n", file_id);    Stream_printf(output, "  _$jscoverage['%s'] = [];\n", file_id);
1377    for (int i = 0; i < num_lines; i++) {    for (int i = 0; i < num_lines; i++) {
# Line 850  Line 1382 
1382    Stream_write_string(output, "}\n");    Stream_write_string(output, "}\n");
1383    free(lines);    free(lines);
1384    lines = NULL;    lines = NULL;
1385      free(exclusive_directives);
1386      exclusive_directives = NULL;
1387    
1388      /* conditionals */
1389      if (has_conditionals) {
1390        Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);
1391      }
1392    
1393    /* copy the instrumented source code to the output */    /* copy the instrumented source code to the output */
1394    Stream_write(output, instrumented->data, instrumented->length);    Stream_write(output, instrumented->data, instrumented->length);
1395    Stream_write_char(output, '\n');  
1396      /* conditionals */
1397      for (struct IfDirective * if_directive = if_directives; if_directive != NULL; if_directive = if_directive->next) {
1398        Stream_write_string(output, "if (!(");
1399        print_javascript(if_directive->condition_start, if_directive->condition_end - if_directive->condition_start, output);
1400        Stream_write_string(output, ")) {\n");
1401        Stream_printf(output, "  _$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_directive->start_line, if_directive->end_line);
1402        Stream_write_string(output, "}\n");
1403      }
1404    
1405      /* free */
1406      while (if_directives != NULL) {
1407        struct IfDirective * if_directive = if_directives;
1408        if_directives = if_directives->next;
1409        free(if_directive);
1410      }
1411    
1412    /* copy the original source to the output */    /* copy the original source to the output */
1413    size_t i = 0;    Stream_printf(output, "_$jscoverage['%s'].source = ", file_id);
1414    while (i < input_length) {    jscoverage_write_source(id, characters, num_characters, output);
1415      Stream_write_string(output, "// ");    Stream_printf(output, ";\n");
     size_t line_start = i;  
     while (i < input_length && base[i] != '\r' && base[i] != '\n') {  
       i++;  
     }  
1416    
1417      size_t line_end = i;    Stream_delete(instrumented);
1418      if (i < input_length) {  
1419        if (base[i] == '\r') {    file_id = NULL;
1420          line_end = i;  }
1421    
1422    void jscoverage_write_source(const char * id, const jschar * characters, size_t num_characters, Stream * output) {
1423      Stream_write_string(output, "[");
1424      if (jscoverage_highlight) {
1425        Stream * highlighted_stream = Stream_new(num_characters);
1426        jscoverage_highlight_js(context, id, characters, num_characters, highlighted_stream);
1427        size_t i = 0;
1428        while (i < highlighted_stream->length) {
1429          if (i > 0) {
1430            Stream_write_char(output, ',');
1431          }
1432    
1433          Stream_write_char(output, '"');
1434          bool done = false;
1435          while (! done) {
1436            char c = highlighted_stream->data[i];
1437            switch (c) {
1438            case 0x8:
1439              /* backspace */
1440              Stream_write_string(output, "\\b");
1441              break;
1442            case 0x9:
1443              /* horizontal tab */
1444              Stream_write_string(output, "\\t");
1445              break;
1446            case 0xa:
1447              /* line feed (new line) */
1448              done = true;
1449              break;
1450            /* IE doesn't support this */
1451            /*
1452            case 0xb:
1453              Stream_write_string(output, "\\v");
1454              break;
1455            */
1456            case 0xc:
1457              /* form feed */
1458              Stream_write_string(output, "\\f");
1459              break;
1460            case 0xd:
1461              /* carriage return */
1462              done = true;
1463              if (i + 1 < highlighted_stream->length && highlighted_stream->data[i + 1] == '\n') {
1464                i++;
1465              }
1466              break;
1467            case '"':
1468              Stream_write_string(output, "\\\"");
1469              break;
1470            case '\\':
1471              Stream_write_string(output, "\\\\");
1472              break;
1473            default:
1474              Stream_write_char(output, c);
1475              break;
1476            }
1477          i++;          i++;
1478          if (i < input_length && base[i] == '\n') {          if (i >= highlighted_stream->length) {
1479            i++;            done = true;
1480          }          }
1481        }        }
1482        else if (base[i] == '\n') {        Stream_write_char(output, '"');
1483          line_end = i;      }
1484        Stream_delete(highlighted_stream);
1485      }
1486      else {
1487        size_t i = 0;
1488        while (i < num_characters) {
1489          if (i > 0) {
1490            Stream_write_char(output, ',');
1491          }
1492    
1493          Stream_write_char(output, '"');
1494          bool done = false;
1495          while (! done) {
1496            jschar c = characters[i];
1497            switch (c) {
1498            case 0x8:
1499              /* backspace */
1500              Stream_write_string(output, "\\b");
1501              break;
1502            case 0x9:
1503              /* horizontal tab */
1504              Stream_write_string(output, "\\t");
1505              break;
1506            case 0xa:
1507              /* line feed (new line) */
1508              done = true;
1509              break;
1510            /* IE doesn't support this */
1511            /*
1512            case 0xb:
1513              Stream_write_string(output, "\\v");
1514              break;
1515            */
1516            case 0xc:
1517              /* form feed */
1518              Stream_write_string(output, "\\f");
1519              break;
1520            case 0xd:
1521              /* carriage return */
1522              done = true;
1523              if (i + 1 < num_characters && characters[i + 1] == '\n') {
1524                i++;
1525              }
1526              break;
1527            case '"':
1528              Stream_write_string(output, "\\\"");
1529              break;
1530            case '\\':
1531              Stream_write_string(output, "\\\\");
1532              break;
1533            case '&':
1534              Stream_write_string(output, "&amp;");
1535              break;
1536            case '<':
1537              Stream_write_string(output, "&lt;");
1538              break;
1539            case '>':
1540              Stream_write_string(output, "&gt;");
1541              break;
1542            case 0x2028:
1543            case 0x2029:
1544              done = true;
1545              break;
1546            default:
1547              if (32 <= c && c <= 126) {
1548                Stream_write_char(output, c);
1549              }
1550              else {
1551                Stream_printf(output, "&#%d;", c);
1552              }
1553              break;
1554            }
1555          i++;          i++;
1556            if (i >= num_characters) {
1557              done = true;
1558            }
1559        }        }
1560        else {        Stream_write_char(output, '"');
1561          abort();      }
1562      }
1563      Stream_write_string(output, "]");
1564    }
1565    
1566    void jscoverage_copy_resources(const char * destination_directory) {
1567      copy_resource("jscoverage.html", destination_directory);
1568      copy_resource("jscoverage.css", destination_directory);
1569      copy_resource("jscoverage.js", destination_directory);
1570      copy_resource("jscoverage-ie.css", destination_directory);
1571      copy_resource("jscoverage-throbber.gif", destination_directory);
1572      copy_resource("jscoverage-highlight.css", destination_directory);
1573    }
1574    
1575    /*
1576    coverage reports
1577    */
1578    
1579    struct FileCoverageList {
1580      FileCoverage * file_coverage;
1581      struct FileCoverageList * next;
1582    };
1583    
1584    struct Coverage {
1585      JSHashTable * coverage_table;
1586      struct FileCoverageList * coverage_list;
1587    };
1588    
1589    static int compare_strings(const void * p1, const void * p2) {
1590      return strcmp(p1, p2) == 0;
1591    }
1592    
1593    Coverage * Coverage_new(void) {
1594      Coverage * result = xmalloc(sizeof(Coverage));
1595      result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL);
1596      if (result->coverage_table == NULL) {
1597        fatal("cannot create hash table");
1598      }
1599      result->coverage_list = NULL;
1600      return result;
1601    }
1602    
1603    void Coverage_delete(Coverage * coverage) {
1604      JS_HashTableDestroy(coverage->coverage_table);
1605      struct FileCoverageList * p = coverage->coverage_list;
1606      while (p != NULL) {
1607        free(p->file_coverage->coverage_lines);
1608        if (p->file_coverage->source_lines != NULL) {
1609          for (uint32 i = 0; i < p->file_coverage->num_source_lines; i++) {
1610            free(p->file_coverage->source_lines[i]);
1611        }        }
1612          free(p->file_coverage->source_lines);
1613      }      }
1614        free(p->file_coverage->id);
1615        free(p->file_coverage);
1616        struct FileCoverageList * q = p;
1617        p = p->next;
1618        free(q);
1619      }
1620      free(coverage);
1621    }
1622    
1623    struct EnumeratorArg {
1624      CoverageForeachFunction f;
1625      void * p;
1626    };
1627    
1628    static intN enumerator(JSHashEntry * entry, intN i, void * arg) {
1629      struct EnumeratorArg * enumerator_arg = arg;
1630      enumerator_arg->f(entry->value, i, enumerator_arg->p);
1631      return 0;
1632    }
1633    
1634      char * line = js_DeflateString(context, base + line_start, line_end - line_start);  void Coverage_foreach_file(Coverage * coverage, CoverageForeachFunction f, void * p) {
1635      Stream_write_string(output, line);    struct EnumeratorArg enumerator_arg;
1636      Stream_write_char(output, '\n');    enumerator_arg.f = f;
1637      JS_free(context, line);    enumerator_arg.p = p;
1638      JS_HashTableEnumerateEntries(coverage->coverage_table, enumerator, &enumerator_arg);
1639    }
1640    
1641    int jscoverage_parse_json(Coverage * coverage, const uint8_t * json, size_t length) {
1642      int result = 0;
1643    
1644      jschar * base = js_InflateString(context, (char *) json, &length);
1645      if (base == NULL) {
1646        fatal("out of memory");
1647    }    }
1648    
1649    Stream_delete(instrumented);    jschar * parenthesized_json = xnew(jschar, addst(length, 2));
1650      parenthesized_json[0] = '(';
1651      memcpy(parenthesized_json + 1, base, mulst(length, sizeof(jschar)));
1652      parenthesized_json[length + 1] = ')';
1653    
1654    JS_free(context, base);    JS_free(context, base);
1655    
1656    file_id = NULL;    JSParseContext parse_context;
1657      if (! js_InitParseContext(context, &parse_context, NULL, NULL, parenthesized_json, length + 2, NULL, NULL, 1)) {
1658        free(parenthesized_json);
1659        return -1;
1660      }
1661      JSParseNode * root = js_ParseScript(context, global, &parse_context);
1662      free(parenthesized_json);
1663      if (root == NULL) {
1664        result = -1;
1665        goto done;
1666      }
1667    
1668      /* root node must be TOK_LC */
1669      if (root->pn_type != TOK_LC) {
1670        result = -1;
1671        goto done;
1672      }
1673      JSParseNode * semi = root->pn_u.list.head;
1674    
1675      /* the list must be TOK_SEMI and it must contain only one element */
1676      if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {
1677        result = -1;
1678        goto done;
1679      }
1680      JSParseNode * parenthesized = semi->pn_kid;
1681    
1682      /* this must be a parenthesized expression */
1683      if (parenthesized->pn_type != TOK_RP) {
1684        result = -1;
1685        goto done;
1686      }
1687      JSParseNode * object = parenthesized->pn_kid;
1688    
1689      /* this must be an object literal */
1690      if (object->pn_type != TOK_RC) {
1691        result = -1;
1692        goto done;
1693      }
1694    
1695      for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {
1696        /* every element of this list must be TOK_COLON */
1697        if (p->pn_type != TOK_COLON) {
1698          result = -1;
1699          goto done;
1700        }
1701    
1702        /* the key must be a string representing the file */
1703        JSParseNode * key = p->pn_left;
1704        if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {
1705          result = -1;
1706          goto done;
1707        }
1708        char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));
1709    
1710        /* the value must be an object literal OR an array */
1711        JSParseNode * value = p->pn_right;
1712        if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {
1713          result = -1;
1714          goto done;
1715        }
1716    
1717        JSParseNode * array = NULL;
1718        JSParseNode * source = NULL;
1719        if (value->pn_type == TOK_RB) {
1720          /* an array */
1721          array = value;
1722        }
1723        else if (value->pn_type == TOK_RC) {
1724          /* an object literal */
1725          if (value->pn_count != 2) {
1726            result = -1;
1727            goto done;
1728          }
1729          for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {
1730            if (element->pn_type != TOK_COLON) {
1731              result = -1;
1732              goto done;
1733            }
1734            JSParseNode * left = element->pn_left;
1735            if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {
1736              result = -1;
1737              goto done;
1738            }
1739            const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));
1740            if (strcmp(s, "coverage") == 0) {
1741              array = element->pn_right;
1742              if (array->pn_type != TOK_RB) {
1743                result = -1;
1744                goto done;
1745              }
1746            }
1747            else if (strcmp(s, "source") == 0) {
1748              source = element->pn_right;
1749              if (source->pn_type != TOK_RB) {
1750                result = -1;
1751                goto done;
1752              }
1753            }
1754            else {
1755              result = -1;
1756              goto done;
1757            }
1758          }
1759        }
1760        else {
1761          result = -1;
1762          goto done;
1763        }
1764    
1765        if (array == NULL) {
1766          result = -1;
1767          goto done;
1768        }
1769    
1770        /* look up the file in the coverage table */
1771        FileCoverage * file_coverage = JS_HashTableLookup(coverage->coverage_table, id_bytes);
1772        if (file_coverage == NULL) {
1773          /* not there: create a new one */
1774          char * id = xstrdup(id_bytes);
1775          file_coverage = xmalloc(sizeof(FileCoverage));
1776          file_coverage->id = id;
1777          file_coverage->num_coverage_lines = array->pn_count;
1778          file_coverage->coverage_lines = xnew(int, array->pn_count);
1779          file_coverage->source_lines = NULL;
1780    
1781          /* set coverage for all lines */
1782          uint32 i = 0;
1783          for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1784            if (element->pn_type == TOK_NUMBER) {
1785              file_coverage->coverage_lines[i] = (int) element->pn_dval;
1786            }
1787            else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1788              file_coverage->coverage_lines[i] = -1;
1789            }
1790            else {
1791              result = -1;
1792              goto done;
1793            }
1794          }
1795          assert(i == array->pn_count);
1796    
1797          /* add to the hash table */
1798          JS_HashTableAdd(coverage->coverage_table, id, file_coverage);
1799          struct FileCoverageList * coverage_list = xmalloc(sizeof(struct FileCoverageList));
1800          coverage_list->file_coverage = file_coverage;
1801          coverage_list->next = coverage->coverage_list;
1802          coverage->coverage_list = coverage_list;
1803        }
1804        else {
1805          /* sanity check */
1806          assert(strcmp(file_coverage->id, id_bytes) == 0);
1807          if (file_coverage->num_coverage_lines != array->pn_count) {
1808            result = -2;
1809            goto done;
1810          }
1811    
1812          /* merge the coverage */
1813          uint32 i = 0;
1814          for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1815            if (element->pn_type == TOK_NUMBER) {
1816              if (file_coverage->coverage_lines[i] == -1) {
1817                result = -2;
1818                goto done;
1819              }
1820              file_coverage->coverage_lines[i] += (int) element->pn_dval;
1821            }
1822            else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1823              if (file_coverage->coverage_lines[i] != -1) {
1824                result = -2;
1825                goto done;
1826              }
1827            }
1828            else {
1829              result = -1;
1830              goto done;
1831            }
1832          }
1833          assert(i == array->pn_count);
1834        }
1835    
1836        /* if this JSON file has source, use it */
1837        if (file_coverage->source_lines == NULL && source != NULL) {
1838          file_coverage->num_source_lines = source->pn_count;
1839          file_coverage->source_lines = xnew(char *, source->pn_count);
1840          uint32 i = 0;
1841          for (JSParseNode * element = source->pn_head; element != NULL; element = element->pn_next, i++) {
1842            if (element->pn_type != TOK_STRING) {
1843              result = -1;
1844              goto done;
1845            }
1846            file_coverage->source_lines[i] = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(element->pn_atom)));
1847          }
1848          assert(i == source->pn_count);
1849        }
1850      }
1851    
1852    done:
1853      js_FinishParseContext(context, &parse_context);
1854      return result;
1855  }  }

Legend:
Removed from v.101  
changed lines
  Added in v.375

  ViewVC Help
Powered by ViewVC 1.1.24