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

Diff of /trunk/instrument-js.c

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

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

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

  ViewVC Help
Powered by ViewVC 1.1.24