/[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 70 by siliconforks, Thu Nov 22 02:52:38 2007 UTC revision 378 by siliconforks, Tue Oct 28 05:31:03 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 instrument_expression(JSParseNode * node, Stream * f);
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      instrument_expression(node->pn_left, f);
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      }    instrument_expression(p, f);
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);      instrument_expression(if_node->pn_kid1, f);
324        Stream_write_char(f, ')');
325      }
326    }
327    
328    static void output_destructuring_expression(JSParseNode * node, Stream * f) {
329      switch (node->pn_type) {
330      case TOK_NAME:
331        assert(node->pn_arity == PN_NAME);
332        print_string_atom(node->pn_atom, f);
333        if (node->pn_expr != NULL) {
334          Stream_write_string(f, " = ");
335          instrument_expression(node->pn_expr, f);
336      }      }
337      for (int i = 0; i < function->nargs; i++) {      break;
338        assert(params[i] != NULL);    case TOK_RB:
339        if (i > 0) {      Stream_write_char(f, '[');
340          fprintf(f, ", ");      for (JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
341          if (p != node->pn_head) {
342            Stream_write_string(f, ", ");
343          }
344          /* TOK_COMMA is a special case: a hole in the array */
345          if (p->pn_type != TOK_COMMA) {
346            output_destructuring_expression(p, f);
347        }        }
348        if (ATOM_IS_STRING(params[i])) {      }
349          print_string_atom(params[i], f);      if (node->pn_extra == PNX_ENDCOMMA) {
350          Stream_write_char(f, ',');
351        }
352        Stream_write_char(f, ']');
353        break;
354      case TOK_RC:
355        Stream_write_char(f, '{');
356        for (JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
357          if (p != node->pn_head) {
358            Stream_write_string(f, ", ");
359          }
360          if (p->pn_type != TOK_COLON) {
361            fatal_source(file_id, p->pn_pos.begin.lineno, "unsupported node type (%d)", p->pn_type);
362          }
363          instrument_expression(p->pn_left, f);
364          Stream_write_string(f, ": ");
365          output_destructuring_expression(p->pn_right, f);
366        }
367        Stream_write_char(f, '}');
368        break;
369      case TOK_ASSIGN:
370        output_destructuring_expression(node->pn_left, f);
371        Stream_write_string(f, " = ");
372        instrument_expression(node->pn_right, f);
373        break;
374      default:
375        fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%d)", node->pn_type);
376        break;
377      }
378    }
379    
380    static void instrument_function(JSParseNode * node, Stream * f, int indent, enum FunctionType type) {
381      assert(node->pn_type == TOK_FUNCTION);
382      assert(node->pn_arity == PN_FUNC);
383      JSObject * object = node->pn_funpob->object;
384      assert(JS_ObjectIsFunction(context, object));
385      JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);
386      assert(function);
387      assert(object == &function->object);
388      Stream_printf(f, "%*s", indent, "");
389      if (type == FUNCTION_NORMAL) {
390        Stream_write_string(f, "function ");
391      }
392    
393      /* function name */
394      if (function->atom) {
395        print_string_atom(function->atom, f);
396      }
397    
398      /*
399      function parameters - see JS_DecompileFunction in jsapi.cpp, which calls
400      js_DecompileFunction in jsopcode.cpp
401      */
402      Stream_write_char(f, '(');
403      JSArenaPool pool;
404      JS_INIT_ARENA_POOL(&pool, "instrument_function", 256, 1, &context->scriptStackQuota);
405      jsuword * local_names = NULL;
406      if (JS_GET_LOCAL_NAME_COUNT(function)) {
407        local_names = js_GetLocalNameArray(context, function, &pool);
408        if (local_names == NULL) {
409          fatal("out of memory");
410        }
411      }
412      bool destructuring = false;
413      for (int i = 0; i < function->nargs; i++) {
414        if (i > 0) {
415          Stream_write_string(f, ", ");
416        }
417        JSAtom * param = JS_LOCAL_NAME_TO_ATOM(local_names[i]);
418        if (param == NULL) {
419          destructuring = true;
420          JSParseNode * expression = NULL;
421          assert(node->pn_body->pn_type == TOK_LC || node->pn_body->pn_type == TOK_BODY);
422          JSParseNode * semi = node->pn_body->pn_head;
423          assert(semi->pn_type == TOK_SEMI);
424          JSParseNode * comma = semi->pn_kid;
425          assert(comma->pn_type == TOK_COMMA);
426          for (JSParseNode * p = comma->pn_head; p != NULL; p = p->pn_next) {
427            assert(p->pn_type == TOK_ASSIGN);
428            JSParseNode * rhs = p->pn_right;
429            assert(JSSTRING_LENGTH(ATOM_TO_STRING(rhs->pn_atom)) == 0);
430            if (rhs->pn_slot == i) {
431              expression = p->pn_left;
432              break;
433            }
434        }        }
435          assert(expression != NULL);
436          output_destructuring_expression(expression, f);
437      }      }
438      fprintf(f, ") {\n");      else {
439      free(params);        print_string_atom(param, f);
440        }
441      }
442      JS_FinishArenaPool(&pool);
443      Stream_write_string(f, ") {\n");
444    
445      /* function body */    /* function body */
446      instrument_statement(node->pn_body, f, indent + 2);    if (function->flags & JSFUN_EXPR_CLOSURE) {
447        /* expression closure - use output_statement instead of instrument_statement */
448        if (node->pn_body->pn_type == TOK_BODY) {
449          assert(node->pn_body->pn_arity == PN_LIST);
450          assert(node->pn_body->pn_count == 2);
451          output_statement(node->pn_body->pn_head->pn_next, f, indent + 2, false);
452        }
453        else {
454          output_statement(node->pn_body, f, indent + 2, false);
455        }
456      }
457      else {
458        assert(node->pn_body->pn_type == TOK_LC);
459        assert(node->pn_body->pn_arity == PN_LIST);
460        JSParseNode * p = node->pn_body->pn_head;
461        if (destructuring) {
462          p = p->pn_next;
463        }
464        for (; p != NULL; p = p->pn_next) {
465          instrument_statement(p, f, indent + 2, false);
466        }
467      }
468    
469      fprintf(f, "}\n");    Stream_write_string(f, "}\n");
470  }  }
471    
472  static void instrument_function_call(JSParseNode * node, FILE * f) {  static void instrument_function_call(JSParseNode * node, Stream * f) {
473    instrument_expression(node->pn_head, f);    JSParseNode * function_node = node->pn_head;
474    fputc('(', f);    if (function_node->pn_type == TOK_FUNCTION) {
475    for (struct JSParseNode * p = node->pn_head->pn_next; p != NULL; p = p->pn_next) {      JSObject * object = function_node->pn_funpob->object;
476        assert(JS_ObjectIsFunction(context, object));
477        JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);
478        assert(function);
479        assert(object == &function->object);
480    
481        if (function_node->pn_flags & TCF_GENEXP_LAMBDA) {
482          /* it's a generator expression */
483          Stream_write_char(f, '(');
484          output_array_comprehension_or_generator_expression(function_node->pn_body, f);
485          Stream_write_char(f, ')');
486          return;
487        }
488        else {
489          Stream_write_char(f, '(');
490          instrument_expression(function_node, f);
491          Stream_write_char(f, ')');
492        }
493      }
494      else {
495        instrument_expression(function_node, f);
496      }
497      Stream_write_char(f, '(');
498      for (struct JSParseNode * p = function_node->pn_next; p != NULL; p = p->pn_next) {
499      if (p != node->pn_head->pn_next) {      if (p != node->pn_head->pn_next) {
500        fprintf(f, ", ");        Stream_write_string(f, ", ");
501      }      }
502      instrument_expression(p, f);      instrument_expression(p, f);
503    }    }
504    fputc(')', f);    Stream_write_char(f, ')');
505    }
506    
507    static void instrument_declarations(JSParseNode * list, Stream * f) {
508      assert(list->pn_arity == PN_LIST);
509      for (JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) {
510        if (p != list->pn_head) {
511          Stream_write_string(f, ", ");
512        }
513        output_destructuring_expression(p, f);
514      }
515  }  }
516    
517  /*  /*
# Line 222  Line 527 
527  TOK_INSTANCEOF  binary  TOK_INSTANCEOF  binary
528  TOK_IN          binary  TOK_IN          binary
529  */  */
530  static void instrument_expression(JSParseNode * node, FILE * f) {  static void instrument_expression(JSParseNode * node, Stream * f) {
531    switch (node->pn_type) {    switch (node->pn_type) {
532    case TOK_FUNCTION:    case TOK_FUNCTION:
533      instrument_function(node, f, 0);      instrument_function(node, f, 0, FUNCTION_NORMAL);
534      break;      break;
535    case TOK_COMMA:    case TOK_COMMA:
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, ", ");          Stream_write_string(f, ", ");
539        }        }
540        instrument_expression(p, f);        instrument_expression(p, f);
541      }      }
542      break;      break;
543    case TOK_ASSIGN:    case TOK_ASSIGN:
544      instrument_expression(node->pn_left, f);      switch (node->pn_left->pn_type) {
545      fputc(' ', f);      case TOK_RB:
546          output_destructuring_expression(node->pn_left, f);
547          break;
548        case TOK_RC:
549          Stream_write_char(f, '(');
550          output_destructuring_expression(node->pn_left, f);
551          Stream_write_char(f, ')');
552          break;
553        default:
554          instrument_expression(node->pn_left, f);
555          break;
556        }
557        Stream_write_char(f, ' ');
558      switch (node->pn_op) {      switch (node->pn_op) {
559      case JSOP_ADD:      case JSOP_ADD:
560      case JSOP_SUB:      case JSOP_SUB:
# Line 250  Line 567 
567      case JSOP_BITOR:      case JSOP_BITOR:
568      case JSOP_BITXOR:      case JSOP_BITXOR:
569      case JSOP_DIV:      case JSOP_DIV:
570        fprintf(f, "%s", get_op(node->pn_op));        Stream_printf(f, "%s", get_op(node->pn_op));
571        break;        break;
572      default:      default:
573        /* do nothing - it must be a simple assignment */        /* do nothing - it must be a simple assignment */
574        break;        break;
575      }      }
576      fprintf(f, "= ");      Stream_write_string(f, "= ");
577      instrument_expression(node->pn_right, f);      instrument_expression(node->pn_right, f);
578      break;      break;
579    case TOK_HOOK:    case TOK_HOOK:
580      instrument_expression(node->pn_kid1, f);      instrument_expression(node->pn_kid1, f);
581      fprintf(f, "? ");      Stream_write_string(f, "? ");
582      instrument_expression(node->pn_kid2, f);      instrument_expression(node->pn_kid2, f);
583      fprintf(f, ": ");      Stream_write_string(f, ": ");
584      instrument_expression(node->pn_kid3, f);      instrument_expression(node->pn_kid3, f);
585      break;      break;
586    case TOK_OR:    case TOK_OR:
     instrument_expression(node->pn_left, f);  
     fprintf(f, " || ");  
     instrument_expression(node->pn_right, f);  
     break;  
587    case TOK_AND:    case TOK_AND:
     instrument_expression(node->pn_left, f);  
     fprintf(f, " && ");  
     instrument_expression(node->pn_right, f);  
     break;  
588    case TOK_BITOR:    case TOK_BITOR:
589    case TOK_BITXOR:    case TOK_BITXOR:
590    case TOK_BITAND:    case TOK_BITAND:
# Line 289  Line 598 
598      switch (node->pn_arity) {      switch (node->pn_arity) {
599      case PN_BINARY:      case PN_BINARY:
600        instrument_expression(node->pn_left, f);        instrument_expression(node->pn_left, f);
601        fprintf(f, " %s ", get_op(node->pn_op));        Stream_printf(f, " %s ", get_op(node->pn_op));
602        instrument_expression(node->pn_right, f);        instrument_expression(node->pn_right, f);
603        break;        break;
604      case PN_LIST:      case PN_LIST:
605        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) {
606          if (p != node->pn_head) {          if (p != node->pn_head) {
607            fprintf(f, " %s ", get_op(node->pn_op));            Stream_printf(f, " %s ", get_op(node->pn_op));
608          }          }
609          instrument_expression(p, f);          instrument_expression(p, f);
610        }        }
# Line 307  Line 616 
616    case TOK_UNARYOP:    case TOK_UNARYOP:
617      switch (node->pn_op) {      switch (node->pn_op) {
618      case JSOP_NEG:      case JSOP_NEG:
619        fputc('-', f);        Stream_write_char(f, '-');
620        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
621        break;        break;
622      case JSOP_POS:      case JSOP_POS:
623        fputc('+', f);        Stream_write_char(f, '+');
624        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
625        break;        break;
626      case JSOP_NOT:      case JSOP_NOT:
627        fputc('!', f);        Stream_write_char(f, '!');
628        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
629        break;        break;
630      case JSOP_BITNOT:      case JSOP_BITNOT:
631        fputc('~', f);        Stream_write_char(f, '~');
632        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
633        break;        break;
634      case JSOP_TYPEOF:      case JSOP_TYPEOF:
635        fprintf(f, "typeof ");        Stream_write_string(f, "typeof ");
636        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
637        break;        break;
638      case JSOP_VOID:      case JSOP_VOID:
639        fprintf(f, "void ");        Stream_write_string(f, "void ");
640        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
641        break;        break;
642      default:      default:
643        abort();        fatal_source(file_id, node->pn_pos.begin.lineno, "unknown operator (%d)", node->pn_op);
644        break;        break;
645      }      }
646      break;      break;
# Line 344  Line 653 
653      case JSOP_INCNAME:      case JSOP_INCNAME:
654      case JSOP_INCPROP:      case JSOP_INCPROP:
655      case JSOP_INCELEM:      case JSOP_INCELEM:
656        fprintf(f, "++");        Stream_write_string(f, "++");
657        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
658        break;        break;
659      case JSOP_DECNAME:      case JSOP_DECNAME:
660      case JSOP_DECPROP:      case JSOP_DECPROP:
661      case JSOP_DECELEM:      case JSOP_DECELEM:
662        fprintf(f, "--");        Stream_write_string(f, "--");
663        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
664        break;        break;
665      case JSOP_NAMEINC:      case JSOP_NAMEINC:
666      case JSOP_PROPINC:      case JSOP_PROPINC:
667      case JSOP_ELEMINC:      case JSOP_ELEMINC:
668        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
669        fprintf(f, "++");        Stream_write_string(f, "++");
670        break;        break;
671      case JSOP_NAMEDEC:      case JSOP_NAMEDEC:
672      case JSOP_PROPDEC:      case JSOP_PROPDEC:
673      case JSOP_ELEMDEC:      case JSOP_ELEMDEC:
674        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
675        fprintf(f, "--");        Stream_write_string(f, "--");
676        break;        break;
677      default:      default:
678        abort();        abort();
# Line 371  Line 680 
680      }      }
681      break;      break;
682    case TOK_NEW:    case TOK_NEW:
683      fprintf(f, "new ");      Stream_write_string(f, "new ");
684      instrument_function_call(node, f);      instrument_function_call(node, f);
685      break;      break;
686    case TOK_DELETE:    case TOK_DELETE:
687      fprintf(f, "delete ");      Stream_write_string(f, "delete ");
688      instrument_expression(node->pn_kid, f);      instrument_expression(node->pn_kid, f);
689      break;      break;
690    case TOK_DOT:    case TOK_DOT:
691        /* numeric literals must be parenthesized */
692        switch (node->pn_expr->pn_type) {
693        case TOK_NUMBER:
694          Stream_write_char(f, '(');
695          instrument_expression(node->pn_expr, f);
696          Stream_write_char(f, ')');
697          break;
698        default:
699          instrument_expression(node->pn_expr, f);
700          break;
701        }
702      /*      /*
703      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'
704      contains illegal characters, we have to use the subscript syntax instead of      contains illegal characters, we have to use the subscript syntax instead of
705      the dot syntax.      the dot syntax.
706      */      */
707      instrument_expression(node->pn_expr, f);      assert(ATOM_IS_STRING(node->pn_atom));
708      /*      {
709      fputc('.', f);        JSString * s = ATOM_TO_STRING(node->pn_atom);
710      print_string_atom(node->pn_atom, f);        bool must_quote;
711      */        if (JSSTRING_LENGTH(s) == 0) {
712      fputc('[', f);          must_quote = true;
713      print_quoted_string_atom(node->pn_atom, f);        }
714      fputc(']', f);        else if (js_CheckKeyword(JSSTRING_CHARS(s), JSSTRING_LENGTH(s)) != TOK_EOF) {
715            must_quote = true;
716          }
717          else if (! js_IsIdentifier(s)) {
718            must_quote = true;
719          }
720          else {
721            must_quote = false;
722          }
723          if (must_quote) {
724            Stream_write_char(f, '[');
725            print_quoted_string_atom(node->pn_atom, f);
726            Stream_write_char(f, ']');
727          }
728          else {
729            Stream_write_char(f, '.');
730            print_string_atom(node->pn_atom, f);
731          }
732        }
733      break;      break;
734    case TOK_LB:    case TOK_LB:
735      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
736      fputc('[', f);      Stream_write_char(f, '[');
737      instrument_expression(node->pn_right, f);      instrument_expression(node->pn_right, f);
738      fputc(']', f);      Stream_write_char(f, ']');
739      break;      break;
740    case TOK_LP:    case TOK_LP:
741      instrument_function_call(node, f);      instrument_function_call(node, f);
742      break;      break;
743    case TOK_RB:    case TOK_RB:
744      fputc('[', f);      Stream_write_char(f, '[');
745      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) {
746        if (p != node->pn_head) {        if (p != node->pn_head) {
747          fprintf(f, ", ");          Stream_write_string(f, ", ");
748        }        }
749        /* TOK_COMMA is a special case: a hole in the array */        /* TOK_COMMA is a special case: a hole in the array */
750        if (p->pn_type != TOK_COMMA) {        if (p->pn_type != TOK_COMMA) {
# Line 414  Line 752 
752        }        }
753      }      }
754      if (node->pn_extra == PNX_ENDCOMMA) {      if (node->pn_extra == PNX_ENDCOMMA) {
755        fputc(',', f);        Stream_write_char(f, ',');
756      }      }
757      fputc(']', f);      Stream_write_char(f, ']');
758      break;      break;
759    case TOK_RC:    case TOK_RC:
760      fputc('{', f);      Stream_write_char(f, '(');
761        Stream_write_char(f, '{');
762      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) {
763        assert(p->pn_type == TOK_COLON);        if (p->pn_type != TOK_COLON) {
764            fatal_source(file_id, p->pn_pos.begin.lineno, "unsupported node type (%d)", p->pn_type);
765          }
766        if (p != node->pn_head) {        if (p != node->pn_head) {
767          fprintf(f, ", ");          Stream_write_string(f, ", ");
768          }
769    
770          /* check whether this is a getter or setter */
771          switch (p->pn_op) {
772          case JSOP_GETTER:
773          case JSOP_SETTER:
774            if (p->pn_op == JSOP_GETTER) {
775              Stream_write_string(f, "get ");
776            }
777            else {
778              Stream_write_string(f, "set ");
779            }
780            instrument_expression(p->pn_left, f);
781            if (p->pn_right->pn_type != TOK_FUNCTION) {
782              fatal_source(file_id, p->pn_pos.begin.lineno, "expected function");
783            }
784            instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER);
785            break;
786          default:
787            instrument_expression(p->pn_left, f);
788            Stream_write_string(f, ": ");
789            instrument_expression(p->pn_right, f);
790            break;
791        }        }
       instrument_expression(p->pn_left, f);  
       fprintf(f, ": ");  
       instrument_expression(p->pn_right, f);  
792      }      }
793      fputc('}', f);      Stream_write_char(f, '}');
794        Stream_write_char(f, ')');
795      break;      break;
796    case TOK_RP:    case TOK_RP:
797      fputc('(', f);      Stream_write_char(f, '(');
798      instrument_expression(node->pn_kid, f);      instrument_expression(node->pn_kid, f);
799      fputc(')', f);      Stream_write_char(f, ')');
800      break;      break;
801    case TOK_NAME:    case TOK_NAME:
802      print_string_atom(node->pn_atom, f);      print_string_atom(node->pn_atom, f);
# Line 442  Line 804 
804    case TOK_STRING:    case TOK_STRING:
805      print_quoted_string_atom(node->pn_atom, f);      print_quoted_string_atom(node->pn_atom, f);
806      break;      break;
807    case TOK_OBJECT:    case TOK_REGEXP:
808      switch (node->pn_op) {      assert(node->pn_op == JSOP_REGEXP);
809      case JSOP_OBJECT:      {
810        /* I assume this is JSOP_REGEXP */        JSObject * object = node->pn_pob->object;
811        abort();        jsval result;
812        break;        js_regexp_toString(context, object, &result);
813      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;  
814      }      }
815      break;      break;
816    case TOK_NUMBER:    case TOK_NUMBER:
# Line 470  Line 821 
821      To keep the output simple, special-case zero.      To keep the output simple, special-case zero.
822      */      */
823      if (node->pn_dval == 0.0) {      if (node->pn_dval == 0.0) {
824        fprintf(f, "0");        Stream_write_string(f, "0");
825      }      }
826      else {      else {
827        fprintf(f, "%.15g", node->pn_dval);        Stream_printf(f, "%.15g", node->pn_dval);
828      }      }
829      break;      break;
830    case TOK_PRIMARY:    case TOK_PRIMARY:
831      switch (node->pn_op) {      switch (node->pn_op) {
832      case JSOP_TRUE:      case JSOP_TRUE:
833        fprintf(f, "true");        Stream_write_string(f, "true");
834        break;        break;
835      case JSOP_FALSE:      case JSOP_FALSE:
836        fprintf(f, "false");        Stream_write_string(f, "false");
837        break;        break;
838      case JSOP_NULL:      case JSOP_NULL:
839        fprintf(f, "null");        Stream_write_string(f, "null");
840        break;        break;
841      case JSOP_THIS:      case JSOP_THIS:
842        fprintf(f, "this");        Stream_write_string(f, "this");
843        break;        break;
844      /* jsscan.h mentions `super' ??? */      /* jsscan.h mentions `super' ??? */
845      default:      default:
# Line 497  Line 848 
848      break;      break;
849    case TOK_INSTANCEOF:    case TOK_INSTANCEOF:
850      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
851      fprintf(f, " instanceof ");      Stream_write_string(f, " instanceof ");
852      instrument_expression(node->pn_right, f);      instrument_expression(node->pn_right, f);
853      break;      break;
854    case TOK_IN:    case TOK_IN:
855      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
856      fprintf(f, " in ");      Stream_write_string(f, " in ");
857      instrument_expression(node->pn_right, f);      instrument_expression(node->pn_right, f);
858      break;      break;
859    default:    case TOK_LEXICALSCOPE:
860      fatal("unsupported node type in file %s: %d", file_id, node->pn_type);      assert(node->pn_arity == PN_NAME);
861    }      assert(node->pn_expr->pn_type == TOK_LET);
862  }      assert(node->pn_expr->pn_arity == PN_BINARY);
863        Stream_write_string(f, "let(");
864  static void instrument_var_statement(JSParseNode * node, FILE * f, int indent) {      assert(node->pn_expr->pn_left->pn_type == TOK_LP);
865    assert(node->pn_arity == PN_LIST);      assert(node->pn_expr->pn_left->pn_arity == PN_LIST);
866    fprintf(f, "%*s", indent, "");      instrument_declarations(node->pn_expr->pn_left, f);
867    fprintf(f, "var ");      Stream_write_string(f, ") ");
868    for (struct JSParseNode * p = node->pn_u.list.head; p != NULL; p = p->pn_next) {      instrument_expression(node->pn_expr->pn_right, f);
869      assert(p->pn_type == TOK_NAME);      break;
870      assert(p->pn_arity == PN_NAME);    case TOK_YIELD:
871      if (p != node->pn_head) {      assert(node->pn_arity == PN_UNARY);
872        fprintf(f, ", ");      Stream_write_string(f, "yield");
873        if (node->pn_kid != NULL) {
874          Stream_write_char(f, ' ');
875          instrument_expression(node->pn_kid, f);
876      }      }
877      print_string_atom(p->pn_atom, f);      break;
878      if (p->pn_expr != NULL) {    case TOK_ARRAYCOMP:
879        fprintf(f, " = ");      assert(node->pn_arity == PN_LIST);
880        instrument_expression(p->pn_expr, f);      {
881          JSParseNode * block_node;
882          switch (node->pn_count) {
883          case 1:
884            block_node = node->pn_head;
885            break;
886          case 2:
887            block_node = node->pn_head->pn_next;
888            break;
889          default:
890            abort();
891            break;
892          }
893          Stream_write_char(f, '[');
894          output_array_comprehension_or_generator_expression(block_node, f);
895          Stream_write_char(f, ']');
896      }      }
897        break;
898      case TOK_VAR:
899        assert(node->pn_arity == PN_LIST);
900        Stream_write_string(f, "var ");
901        instrument_declarations(node, f);
902        break;
903      case TOK_LET:
904        assert(node->pn_arity == PN_LIST);
905        Stream_write_string(f, "let ");
906        instrument_declarations(node, f);
907        break;
908      default:
909        fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%d)", node->pn_type);
910    }    }
911  }  }
912    
913  static void output_statement(JSParseNode * node, FILE * f, int indent) {  static void output_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
914    switch (node->pn_type) {    switch (node->pn_type) {
915    case TOK_FUNCTION:    case TOK_FUNCTION:
916      instrument_function(node, f, indent);      instrument_function(node, f, indent, FUNCTION_NORMAL);
917      break;      break;
918    case TOK_LC:    case TOK_LC:
919      assert(node->pn_arity == PN_LIST);      assert(node->pn_arity == PN_LIST);
920  /*  /*
921      fprintf(f, "{\n");      Stream_write_string(f, "{\n");
922  */  */
923      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) {
924        instrument_statement(p, f, indent);        instrument_statement(p, f, indent, false);
925      }      }
926  /*  /*
927      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
928      fprintf(f, "}\n");      Stream_write_string(f, "}\n");
929  */  */
930      break;      break;
931    case TOK_IF:    case TOK_IF:
932      {
933      assert(node->pn_arity == PN_TERNARY);      assert(node->pn_arity == PN_TERNARY);
934      fprintf(f, "%*s", indent, "");  
935      fprintf(f, "if (");      uint16_t line = node->pn_pos.begin.lineno;
936        if (! is_jscoverage_if) {
937          if (line > num_lines) {
938            fatal("file %s contains more than 65,535 lines", file_id);
939          }
940          if (line >= 2 && exclusive_directives[line - 2]) {
941            is_jscoverage_if = true;
942          }
943        }
944    
945        Stream_printf(f, "%*s", indent, "");
946        Stream_write_string(f, "if (");
947      instrument_expression(node->pn_kid1, f);      instrument_expression(node->pn_kid1, f);
948      fprintf(f, ") {\n");      Stream_write_string(f, ") {\n");
949      instrument_statement(node->pn_kid2, f, indent + 2);      if (is_jscoverage_if && node->pn_kid3) {
950      fprintf(f, "%*s", indent, "");        uint16_t else_start = node->pn_kid3->pn_pos.begin.lineno;
951      fprintf(f, "}\n");        uint16_t else_end = node->pn_kid3->pn_pos.end.lineno + 1;
952      if (node->pn_kid3) {        Stream_printf(f, "%*s", indent + 2, "");
953        fprintf(f, "%*s", indent, "");        Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, else_start, else_end);
954        fprintf(f, "else {\n");      }
955        instrument_statement(node->pn_kid3, f, indent + 2);      instrument_statement(node->pn_kid2, f, indent + 2, false);
956        fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
957        fprintf(f, "}\n");      Stream_write_string(f, "}\n");
958    
959        if (node->pn_kid3 || is_jscoverage_if) {
960          Stream_printf(f, "%*s", indent, "");
961          Stream_write_string(f, "else {\n");
962    
963          if (is_jscoverage_if) {
964            uint16_t if_start = node->pn_kid2->pn_pos.begin.lineno + 1;
965            uint16_t if_end = node->pn_kid2->pn_pos.end.lineno + 1;
966            Stream_printf(f, "%*s", indent + 2, "");
967            Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_start, if_end);
968          }
969    
970          if (node->pn_kid3) {
971            instrument_statement(node->pn_kid3, f, indent + 2, is_jscoverage_if);
972          }
973    
974          Stream_printf(f, "%*s", indent, "");
975          Stream_write_string(f, "}\n");
976      }      }
977    
978      break;      break;
979      }
980    case TOK_SWITCH:    case TOK_SWITCH:
981      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
982      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
983      fprintf(f, "switch (");      Stream_write_string(f, "switch (");
984      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
985      fprintf(f, ") {\n");      Stream_write_string(f, ") {\n");
986      for (struct JSParseNode * p = node->pn_right->pn_head; p != NULL; p = p->pn_next) {      {
987        fprintf(f, "%*s", indent, "");        JSParseNode * list = node->pn_right;
988        switch (p->pn_type) {        if (list->pn_type == TOK_LEXICALSCOPE) {
989        case TOK_CASE:          list = list->pn_expr;
990          fprintf(f, "case ");        }
991          instrument_expression(p->pn_left, f);        for (struct JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) {
992          fprintf(f, ":\n");          Stream_printf(f, "%*s", indent, "");
993          break;          switch (p->pn_type) {
994        case TOK_DEFAULT:          case TOK_CASE:
995          fprintf(f, "default:\n");            Stream_write_string(f, "case ");
996          break;            instrument_expression(p->pn_left, f);
997        default:            Stream_write_string(f, ":\n");
998          abort();            break;
999          break;          case TOK_DEFAULT:
1000              Stream_write_string(f, "default:\n");
1001              break;
1002            default:
1003              abort();
1004              break;
1005            }
1006            instrument_statement(p->pn_right, f, indent + 2, false);
1007        }        }
       instrument_statement(p->pn_right, f, indent + 2);  
1008      }      }
1009      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1010      fprintf(f, "}\n");      Stream_write_string(f, "}\n");
1011      break;      break;
1012    case TOK_CASE:    case TOK_CASE:
1013    case TOK_DEFAULT:    case TOK_DEFAULT:
# Line 595  Line 1015 
1015      break;      break;
1016    case TOK_WHILE:    case TOK_WHILE:
1017      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
1018      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1019      fprintf(f, "while (");      Stream_write_string(f, "while (");
1020      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
1021      fprintf(f, ") {\n");      Stream_write_string(f, ") {\n");
1022      instrument_statement(node->pn_right, f, indent + 2);      instrument_statement(node->pn_right, f, indent + 2, false);
1023      fprintf(f, "}\n");      Stream_write_string(f, "}\n");
1024      break;      break;
1025    case TOK_DO:    case TOK_DO:
1026      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
1027      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1028      fprintf(f, "do {\n");      Stream_write_string(f, "do {\n");
1029      instrument_statement(node->pn_left, f, indent + 2);      instrument_statement(node->pn_left, f, indent + 2, false);
1030      fprintf(f, "}\n");      Stream_write_string(f, "}\n");
1031      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1032      fprintf(f, "while (");      Stream_write_string(f, "while (");
1033      instrument_expression(node->pn_right, f);      instrument_expression(node->pn_right, f);
1034      fprintf(f, ");\n");      Stream_write_string(f, ");\n");
1035      break;      break;
1036    case TOK_FOR:    case TOK_FOR:
1037      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
1038      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
     fprintf(f, "for (");  
1039      switch (node->pn_left->pn_type) {      switch (node->pn_left->pn_type) {
1040      case TOK_IN:      case TOK_IN:
1041        /* for/in */        /* for/in */
1042        assert(node->pn_left->pn_arity == PN_BINARY);        assert(node->pn_left->pn_arity == PN_BINARY);
1043        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);  
1044        break;        break;
1045      case TOK_RESERVED:      case TOK_RESERVED:
1046        /* for (;;) */        /* for (;;) */
1047        assert(node->pn_left->pn_arity == PN_TERNARY);        assert(node->pn_left->pn_arity == PN_TERNARY);
1048          Stream_write_string(f, "for (");
1049        if (node->pn_left->pn_kid1) {        if (node->pn_left->pn_kid1) {
1050          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);  
         }  
1051        }        }
1052        fprintf(f, ";");        Stream_write_string(f, ";");
1053        if (node->pn_left->pn_kid2) {        if (node->pn_left->pn_kid2) {
1054          fputc(' ', f);          Stream_write_char(f, ' ');
1055          instrument_expression(node->pn_left->pn_kid2, f);          instrument_expression(node->pn_left->pn_kid2, f);
1056        }        }
1057        fprintf(f, ";");        Stream_write_string(f, ";");
1058        if (node->pn_left->pn_kid3) {        if (node->pn_left->pn_kid3) {
1059          fputc(' ', f);          Stream_write_char(f, ' ');
1060          instrument_expression(node->pn_left->pn_kid3, f);          instrument_expression(node->pn_left->pn_kid3, f);
1061        }        }
1062          Stream_write_char(f, ')');
1063        break;        break;
1064      default:      default:
1065        abort();        abort();
1066        break;        break;
1067      }      }
1068      fprintf(f, ") {\n");      Stream_write_string(f, " {\n");
1069      instrument_statement(node->pn_right, f, indent + 2);      instrument_statement(node->pn_right, f, indent + 2, false);
1070      fprintf(f, "}\n");      Stream_write_string(f, "}\n");
1071      break;      break;
1072    case TOK_THROW:    case TOK_THROW:
1073      assert(node->pn_arity == PN_UNARY);      assert(node->pn_arity == PN_UNARY);
1074      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1075      fprintf(f, "throw ");      Stream_write_string(f, "throw ");
1076      instrument_expression(node->pn_u.unary.kid, f);      instrument_expression(node->pn_u.unary.kid, f);
1077      fprintf(f, ";\n");      Stream_write_string(f, ";\n");
1078      break;      break;
1079    case TOK_TRY:    case TOK_TRY:
1080      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1081      fprintf(f, "try {\n");      Stream_write_string(f, "try {\n");
1082      instrument_statement(node->pn_kid1, f, indent + 2);      instrument_statement(node->pn_kid1, f, indent + 2, false);
1083      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1084      fprintf(f, "}\n");      Stream_write_string(f, "}\n");
1085      {      if (node->pn_kid2) {
1086        for (JSParseNode * catch = node->pn_kid2; catch != NULL; catch = catch->pn_kid2) {        assert(node->pn_kid2->pn_type == TOK_RESERVED);
1087          for (JSParseNode * scope = node->pn_kid2->pn_head; scope != NULL; scope = scope->pn_next) {
1088            assert(scope->pn_type == TOK_LEXICALSCOPE);
1089            JSParseNode * catch = scope->pn_expr;
1090          assert(catch->pn_type == TOK_CATCH);          assert(catch->pn_type == TOK_CATCH);
1091          fprintf(f, "%*s", indent, "");          Stream_printf(f, "%*s", indent, "");
1092          fprintf(f, "catch (");          Stream_write_string(f, "catch (");
1093          assert(catch->pn_kid1->pn_arity == PN_NAME);          output_destructuring_expression(catch->pn_kid1, f);
1094          print_string_atom(catch->pn_kid1->pn_atom, f);          if (catch->pn_kid2) {
1095          if (catch->pn_kid1->pn_expr) {            Stream_write_string(f, " if ");
1096            fprintf(f, " if ");            instrument_expression(catch->pn_kid2, f);
1097            instrument_expression(catch->pn_kid1->pn_expr, f);          }
1098          }          Stream_write_string(f, ") {\n");
1099          fprintf(f, ") {\n");          instrument_statement(catch->pn_kid3, f, indent + 2, false);
1100          instrument_statement(catch->pn_kid3, f, indent + 2);          Stream_printf(f, "%*s", indent, "");
1101          fprintf(f, "%*s", indent, "");          Stream_write_string(f, "}\n");
         fprintf(f, "}\n");  
1102        }        }
1103      }      }
1104      if (node->pn_kid3) {      if (node->pn_kid3) {
1105        fprintf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
1106        fprintf(f, "finally {\n");        Stream_write_string(f, "finally {\n");
1107        instrument_statement(node->pn_kid3, f, indent + 2);        instrument_statement(node->pn_kid3, f, indent + 2, false);
1108        fprintf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
1109        fprintf(f, "}\n");        Stream_write_string(f, "}\n");
1110      }      }
1111      break;      break;
1112    case TOK_CATCH:    case TOK_CATCH:
# Line 716  Line 1115 
1115    case TOK_BREAK:    case TOK_BREAK:
1116    case TOK_CONTINUE:    case TOK_CONTINUE:
1117      assert(node->pn_arity == PN_NAME || node->pn_arity == PN_NULLARY);      assert(node->pn_arity == PN_NAME || node->pn_arity == PN_NULLARY);
1118      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1119      fputs(node->pn_type == TOK_BREAK? "break": "continue", f);      Stream_write_string(f, node->pn_type == TOK_BREAK? "break": "continue");
1120      JSAtom * atom = node->pn_u.name.atom;      JSAtom * atom = node->pn_u.name.atom;
1121      if (atom != NULL) {      if (atom != NULL) {
1122        fputc(' ', f);        Stream_write_char(f, ' ');
1123        print_string_atom(node->pn_atom, f);        print_string_atom(node->pn_atom, f);
1124      }      }
1125      fprintf(f, ";\n");      Stream_write_string(f, ";\n");
1126      break;      break;
1127    case TOK_WITH:    case TOK_WITH:
1128      assert(node->pn_arity == PN_BINARY);      assert(node->pn_arity == PN_BINARY);
1129      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1130      fprintf(f, "with (");      Stream_write_string(f, "with (");
1131      instrument_expression(node->pn_left, f);      instrument_expression(node->pn_left, f);
1132      fprintf(f, ") {\n");      Stream_write_string(f, ") {\n");
1133      instrument_statement(node->pn_right, f, indent + 2);      instrument_statement(node->pn_right, f, indent + 2, false);
1134      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1135      fprintf(f, "}\n");      Stream_write_string(f, "}\n");
1136      break;      break;
1137    case TOK_VAR:    case TOK_VAR:
1138      instrument_var_statement(node, f, indent);      Stream_printf(f, "%*s", indent, "");
1139      fprintf(f, ";\n");      instrument_expression(node, f);
1140        Stream_write_string(f, ";\n");
1141      break;      break;
1142    case TOK_RETURN:    case TOK_RETURN:
1143      assert(node->pn_arity == PN_UNARY);      assert(node->pn_arity == PN_UNARY);
1144      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1145      fprintf(f, "return");      Stream_write_string(f, "return");
1146      if (node->pn_kid != NULL) {      if (node->pn_kid != NULL) {
1147        fprintf(f, " ");        Stream_write_char(f, ' ');
1148        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
1149      }      }
1150      fprintf(f, ";\n");      Stream_write_string(f, ";\n");
1151      break;      break;
1152    case TOK_SEMI:    case TOK_SEMI:
1153      assert(node->pn_arity == PN_UNARY);      assert(node->pn_arity == PN_UNARY);
1154      fprintf(f, "%*s", indent, "");      Stream_printf(f, "%*s", indent, "");
1155      if (node->pn_kid != NULL) {      if (node->pn_kid != NULL) {
1156        instrument_expression(node->pn_kid, f);        instrument_expression(node->pn_kid, f);
1157      }      }
1158      fprintf(f, ";\n");      Stream_write_string(f, ";\n");
1159      break;      break;
1160    case TOK_COLON:    case TOK_COLON:
1161      {
1162      assert(node->pn_arity == PN_NAME);      assert(node->pn_arity == PN_NAME);
1163      /*      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, "");  
1164      print_string_atom(node->pn_atom, f);      print_string_atom(node->pn_atom, f);
1165      fprintf(f, ":\n");      Stream_write_string(f, ":\n");
1166      /*      JSParseNode * labelled = node->pn_expr;
1167      ... use output_statement instead of instrument_statement.      if (labelled->pn_type == TOK_LEXICALSCOPE) {
1168      */        labelled = labelled->pn_expr;
1169      output_statement(node->pn_expr, f, indent);      }
1170        if (labelled->pn_type == TOK_LC) {
1171          /* labelled block */
1172          Stream_printf(f, "%*s", indent, "");
1173          Stream_write_string(f, "{\n");
1174          instrument_statement(labelled, f, indent + 2, false);
1175          Stream_printf(f, "%*s", indent, "");
1176          Stream_write_string(f, "}\n");
1177        }
1178        else {
1179          /*
1180          This one is tricky: can't output instrumentation between the label and the
1181          statement it's supposed to label, so use output_statement instead of
1182          instrument_statement.
1183          */
1184          output_statement(labelled, f, indent, false);
1185        }
1186        break;
1187      }
1188      case TOK_LEXICALSCOPE:
1189        /* let statement */
1190        assert(node->pn_arity == PN_NAME);
1191        switch (node->pn_expr->pn_type) {
1192        case TOK_LET:
1193          /* let statement */
1194          assert(node->pn_expr->pn_arity == PN_BINARY);
1195          instrument_statement(node->pn_expr, f, indent, false);
1196          break;
1197        case TOK_LC:
1198          /* block */
1199          Stream_printf(f, "%*s", indent, "");
1200          Stream_write_string(f, "{\n");
1201          instrument_statement(node->pn_expr, f, indent + 2, false);
1202          Stream_printf(f, "%*s", indent, "");
1203          Stream_write_string(f, "}\n");
1204          break;
1205        case TOK_FOR:
1206          instrument_statement(node->pn_expr, f, indent, false);
1207          break;
1208        default:
1209          abort();
1210          break;
1211        }
1212        break;
1213      case TOK_LET:
1214        switch (node->pn_arity) {
1215        case PN_BINARY:
1216          /* let statement */
1217          Stream_printf(f, "%*s", indent, "");
1218          Stream_write_string(f, "let (");
1219          assert(node->pn_left->pn_type == TOK_LP);
1220          assert(node->pn_left->pn_arity == PN_LIST);
1221          instrument_declarations(node->pn_left, f);
1222          Stream_write_string(f, ") {\n");
1223          instrument_statement(node->pn_right, f, indent + 2, false);
1224          Stream_printf(f, "%*s", indent, "");
1225          Stream_write_string(f, "}\n");
1226          break;
1227        case PN_LIST:
1228          /* let definition */
1229          Stream_printf(f, "%*s", indent, "");
1230          instrument_expression(node, f);
1231          Stream_write_string(f, ";\n");
1232          break;
1233        default:
1234          abort();
1235          break;
1236        }
1237        break;
1238      case TOK_DEBUGGER:
1239        Stream_printf(f, "%*s", indent, "");
1240        Stream_write_string(f, "debugger;\n");
1241      break;      break;
1242    default:    default:
1243      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);
1244    }    }
1245  }  }
1246    
# Line 781  Line 1249 
1249  TOK_FUNCTION is handled as a statement and as an expression.  TOK_FUNCTION is handled as a statement and as an expression.
1250  TOK_EXPORT, TOK_IMPORT are not handled.  TOK_EXPORT, TOK_IMPORT are not handled.
1251  */  */
1252  static void instrument_statement(JSParseNode * node, FILE * f, int indent) {  static void instrument_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
1253    if (node->pn_type != TOK_LC) {    if (node->pn_type != TOK_LC && node->pn_type != TOK_LEXICALSCOPE) {
1254      int line = node->pn_pos.begin.lineno;      uint16_t line = node->pn_pos.begin.lineno;
1255        if (line > num_lines) {
1256          fatal("file %s contains more than 65,535 lines", file_id);
1257        }
1258    
1259      /* the root node has line number 0 */      /* the root node has line number 0 */
1260      if (line != 0) {      if (line != 0) {
1261        fprintf(f, "%*s", indent, "");        Stream_printf(f, "%*s", indent, "");
1262        fprintf(f, "_$jscoverage['%s'][%d]++;\n", file_id, line);        Stream_printf(f, "_$jscoverage['%s'][%d]++;\n", file_id, line);
1263        lines[line - 1] = 1;        lines[line - 1] = 1;
1264      }      }
1265    }    }
1266    output_statement(node, f, indent);    output_statement(node, f, indent, is_jscoverage_if);
1267  }  }
1268    
1269  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) {
1270    file_id = id;    const jschar * characters_end = characters + line_end;
1271      const jschar * cp = characters + line_start;
1272      const char * bp = prefix;
1273      for (;;) {
1274        if (*bp == '\0') {
1275          return true;
1276        }
1277        else if (cp == characters_end) {
1278          return false;
1279        }
1280        else if (*cp != *bp) {
1281          return false;
1282        }
1283        bp++;
1284        cp++;
1285      }
1286    }
1287    
1288    /* scan the javascript */  static bool characters_are_white_space(const jschar * characters, size_t line_start, size_t line_end) {
1289    JSTokenStream * token_stream = js_NewFileTokenStream(context, NULL, input);    /* XXX - other Unicode space */
1290    if (token_stream == NULL) {    const jschar * end = characters + line_end;
1291      fatal("cannot create token stream from file: %s", file_id);    for (const jschar * p = characters + line_start; p < end; p++) {
1292        jschar c = *p;
1293        if (c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0) {
1294          continue;
1295        }
1296        else {
1297          return false;
1298        }
1299    }    }
1300      return true;
1301    }
1302    
1303    static void error_reporter(JSContext * context, const char * message, JSErrorReport * report) {
1304      warn_source(file_id, report->lineno, "%s", message);
1305    }
1306    
1307    void jscoverage_instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output) {
1308      file_id = id;
1309    
1310    /* parse the javascript */    /* parse the javascript */
1311    JSParseNode * node = js_ParseTokenStream(context, global, token_stream);    JSParseContext parse_context;
1312      if (! js_InitParseContext(context, &parse_context, NULL, NULL, characters, num_characters, NULL, NULL, 1)) {
1313        fatal("cannot create token stream from file %s", file_id);
1314      }
1315      JSErrorReporter old_error_reporter = JS_SetErrorReporter(context, error_reporter);
1316      JSParseNode * node = js_ParseScript(context, global, &parse_context);
1317    if (node == NULL) {    if (node == NULL) {
1318      fatal("parse error in file: %s", file_id);      js_ReportUncaughtException(context);
1319        fatal("parse error in file %s", file_id);
1320    }    }
1321    int num_lines = node->pn_pos.end.lineno;    JS_SetErrorReporter(context, old_error_reporter);
1322      num_lines = node->pn_pos.end.lineno;
1323    lines = xmalloc(num_lines);    lines = xmalloc(num_lines);
1324    for (int i = 0; i < num_lines; i++) {    for (unsigned int i = 0; i < num_lines; i++) {
1325      lines[i] = 0;      lines[i] = 0;
1326    }    }
1327    
1328      /* search code for conditionals */
1329      exclusive_directives = xnew(bool, num_lines);
1330      for (unsigned int i = 0; i < num_lines; i++) {
1331        exclusive_directives[i] = false;
1332      }
1333    
1334      bool has_conditionals = false;
1335      struct IfDirective * if_directives = NULL;
1336      size_t line_number = 0;
1337      size_t i = 0;
1338      while (i < num_characters) {
1339        if (line_number == UINT16_MAX) {
1340          fatal("file %s contains more than 65,535 lines", file_id);
1341        }
1342        line_number++;
1343        size_t line_start = i;
1344        jschar c;
1345        bool done = false;
1346        while (! done && i < num_characters) {
1347          c = characters[i];
1348          switch (c) {
1349          case '\r':
1350          case '\n':
1351          case 0x2028:
1352          case 0x2029:
1353            done = true;
1354            break;
1355          default:
1356            i++;
1357          }
1358        }
1359        size_t line_end = i;
1360        if (i < num_characters) {
1361          i++;
1362          if (c == '\r' && i < num_characters && characters[i] == '\n') {
1363            i++;
1364          }
1365        }
1366    
1367        if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_IF")) {
1368          has_conditionals = true;
1369    
1370          if (characters_are_white_space(characters, line_start + 16, line_end)) {
1371            exclusive_directives[line_number - 1] = true;
1372          }
1373          else {
1374            struct IfDirective * if_directive = xnew(struct IfDirective, 1);
1375            if_directive->condition_start = characters + line_start + 16;
1376            if_directive->condition_end = characters + line_end;
1377            if_directive->start_line = line_number;
1378            if_directive->end_line = 0;
1379            if_directive->next = if_directives;
1380            if_directives = if_directive;
1381          }
1382        }
1383        else if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_ENDIF")) {
1384          for (struct IfDirective * p = if_directives; p != NULL; p = p->next) {
1385            if (p->end_line == 0) {
1386              p->end_line = line_number;
1387              break;
1388            }
1389          }
1390        }
1391      }
1392    
1393    /*    /*
1394    Create a temporary file - we can't write directly to the output because we    An instrumented JavaScript file has 4 sections:
1395    need to know the line number info first.    1. initialization
1396      2. instrumented source code
1397      3. conditionals
1398      4. original source code
1399    */    */
   FILE * temporary = fopen(temporary_file_name, "w+");  
   if (temporary == NULL) {  
     fatal("cannot create temporary file for script: %s", file_id);  
   }  
1400    
1401    /* write instrumented javascript to the temporary */    Stream * instrumented = Stream_new(0);
1402    instrument_statement(node, temporary, 0);    instrument_statement(node, instrumented, 0, false);
1403      js_FinishParseContext(context, &parse_context);
1404    
1405    /* write line number info to the output */    /* write line number info to the output */
1406    fprintf(output, "/* automatically generated by JSCoverage - do not edit */\n");    Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n");
1407    fprintf(output, "if (! top._$jscoverage) {\n  top._$jscoverage = {};\n}\n");    if (jscoverage_mozilla) {
1408    fprintf(output, "var _$jscoverage = top._$jscoverage;\n");      Stream_write_string(output, "try {\n");
1409    fprintf(output, "if (! _$jscoverage['%s']) {\n", file_id);      Stream_write_string(output, "  Components.utils.import('resource://gre/modules/jscoverage.jsm');\n");
1410    fprintf(output, "  _$jscoverage['%s'] = [];\n", file_id);      Stream_printf(output, "  dump('%s: successfully imported jscoverage module\\n');\n", id);
1411        Stream_write_string(output, "}\n");
1412        Stream_write_string(output, "catch (e) {\n");
1413        Stream_write_string(output, "  _$jscoverage = {};\n");
1414        Stream_printf(output, "  dump('%s: failed to import jscoverage module - coverage not available for this file\\n');\n", id);
1415        Stream_write_string(output, "}\n");
1416      }
1417      else {
1418        Stream_write_string(output, "if (! top._$jscoverage) {\n  top._$jscoverage = {};\n}\n");
1419        Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n");
1420      }
1421      Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id);
1422      Stream_printf(output, "  _$jscoverage['%s'] = [];\n", file_id);
1423    for (int i = 0; i < num_lines; i++) {    for (int i = 0; i < num_lines; i++) {
1424      if (lines[i]) {      if (lines[i]) {
1425        fprintf(output, "  _$jscoverage['%s'][%d] = 0;\n", file_id, i + 1);        Stream_printf(output, "  _$jscoverage['%s'][%d] = 0;\n", file_id, i + 1);
1426      }      }
1427    }    }
1428    fprintf(output, "}\n");    Stream_write_string(output, "}\n");
1429      free(lines);
1430    lines = NULL;    lines = NULL;
1431      free(exclusive_directives);
1432      exclusive_directives = NULL;
1433    
1434    /* copy the temporary to the output */    /* conditionals */
1435    fseek(temporary, 0, SEEK_SET);    if (has_conditionals) {
1436    copy_stream(temporary, output);      Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);
1437      }
1438    
1439    fclose(temporary);    /* copy the instrumented source code to the output */
1440      Stream_write(output, instrumented->data, instrumented->length);
1441    
1442      /* conditionals */
1443      for (struct IfDirective * if_directive = if_directives; if_directive != NULL; if_directive = if_directive->next) {
1444        Stream_write_string(output, "if (!(");
1445        print_javascript(if_directive->condition_start, if_directive->condition_end - if_directive->condition_start, output);
1446        Stream_write_string(output, ")) {\n");
1447        Stream_printf(output, "  _$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_directive->start_line, if_directive->end_line);
1448        Stream_write_string(output, "}\n");
1449      }
1450    
1451      /* free */
1452      while (if_directives != NULL) {
1453        struct IfDirective * if_directive = if_directives;
1454        if_directives = if_directives->next;
1455        free(if_directive);
1456      }
1457    
1458      /* copy the original source to the output */
1459      Stream_printf(output, "_$jscoverage['%s'].source = ", file_id);
1460      jscoverage_write_source(id, characters, num_characters, output);
1461      Stream_printf(output, ";\n");
1462    
1463      Stream_delete(instrumented);
1464    
1465    file_id = NULL;    file_id = NULL;
1466  }  }
1467    
1468  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) {
1469    instrument_js_stream(id, 0, input, output, temporary_file_name);    Stream_write_string(output, "[");
1470      if (jscoverage_highlight) {
1471        Stream * highlighted_stream = Stream_new(num_characters);
1472        jscoverage_highlight_js(context, id, characters, num_characters, highlighted_stream);
1473        size_t i = 0;
1474        while (i < highlighted_stream->length) {
1475          if (i > 0) {
1476            Stream_write_char(output, ',');
1477          }
1478    
1479          Stream_write_char(output, '"');
1480          bool done = false;
1481          while (! done) {
1482            char c = highlighted_stream->data[i];
1483            switch (c) {
1484            case 0x8:
1485              /* backspace */
1486              Stream_write_string(output, "\\b");
1487              break;
1488            case 0x9:
1489              /* horizontal tab */
1490              Stream_write_string(output, "\\t");
1491              break;
1492            case 0xa:
1493              /* line feed (new line) */
1494              done = true;
1495              break;
1496            /* IE doesn't support this */
1497            /*
1498            case 0xb:
1499              Stream_write_string(output, "\\v");
1500              break;
1501            */
1502            case 0xc:
1503              /* form feed */
1504              Stream_write_string(output, "\\f");
1505              break;
1506            case 0xd:
1507              /* carriage return */
1508              done = true;
1509              if (i + 1 < highlighted_stream->length && highlighted_stream->data[i + 1] == '\n') {
1510                i++;
1511              }
1512              break;
1513            case '"':
1514              Stream_write_string(output, "\\\"");
1515              break;
1516            case '\\':
1517              Stream_write_string(output, "\\\\");
1518              break;
1519            default:
1520              Stream_write_char(output, c);
1521              break;
1522            }
1523            i++;
1524            if (i >= highlighted_stream->length) {
1525              done = true;
1526            }
1527          }
1528          Stream_write_char(output, '"');
1529        }
1530        Stream_delete(highlighted_stream);
1531      }
1532      else {
1533        size_t i = 0;
1534        while (i < num_characters) {
1535          if (i > 0) {
1536            Stream_write_char(output, ',');
1537          }
1538    
1539          Stream_write_char(output, '"');
1540          bool done = false;
1541          while (! done) {
1542            jschar c = characters[i];
1543            switch (c) {
1544            case 0x8:
1545              /* backspace */
1546              Stream_write_string(output, "\\b");
1547              break;
1548            case 0x9:
1549              /* horizontal tab */
1550              Stream_write_string(output, "\\t");
1551              break;
1552            case 0xa:
1553              /* line feed (new line) */
1554              done = true;
1555              break;
1556            /* IE doesn't support this */
1557            /*
1558            case 0xb:
1559              Stream_write_string(output, "\\v");
1560              break;
1561            */
1562            case 0xc:
1563              /* form feed */
1564              Stream_write_string(output, "\\f");
1565              break;
1566            case 0xd:
1567              /* carriage return */
1568              done = true;
1569              if (i + 1 < num_characters && characters[i + 1] == '\n') {
1570                i++;
1571              }
1572              break;
1573            case '"':
1574              Stream_write_string(output, "\\\"");
1575              break;
1576            case '\\':
1577              Stream_write_string(output, "\\\\");
1578              break;
1579            case '&':
1580              Stream_write_string(output, "&amp;");
1581              break;
1582            case '<':
1583              Stream_write_string(output, "&lt;");
1584              break;
1585            case '>':
1586              Stream_write_string(output, "&gt;");
1587              break;
1588            case 0x2028:
1589            case 0x2029:
1590              done = true;
1591              break;
1592            default:
1593              if (32 <= c && c <= 126) {
1594                Stream_write_char(output, c);
1595              }
1596              else {
1597                Stream_printf(output, "&#%d;", c);
1598              }
1599              break;
1600            }
1601            i++;
1602            if (i >= num_characters) {
1603              done = true;
1604            }
1605          }
1606          Stream_write_char(output, '"');
1607        }
1608      }
1609      Stream_write_string(output, "]");
1610    }
1611    
1612    void jscoverage_copy_resources(const char * destination_directory) {
1613      copy_resource("jscoverage.html", destination_directory);
1614      copy_resource("jscoverage.css", destination_directory);
1615      copy_resource("jscoverage.js", destination_directory);
1616      copy_resource("jscoverage-ie.css", destination_directory);
1617      copy_resource("jscoverage-throbber.gif", destination_directory);
1618      copy_resource("jscoverage-highlight.css", destination_directory);
1619    }
1620    
1621    /*
1622    coverage reports
1623    */
1624    
1625    struct FileCoverageList {
1626      FileCoverage * file_coverage;
1627      struct FileCoverageList * next;
1628    };
1629    
1630    struct Coverage {
1631      JSHashTable * coverage_table;
1632      struct FileCoverageList * coverage_list;
1633    };
1634    
1635    static int compare_strings(const void * p1, const void * p2) {
1636      return strcmp(p1, p2) == 0;
1637    }
1638    
1639    Coverage * Coverage_new(void) {
1640      Coverage * result = xmalloc(sizeof(Coverage));
1641      result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL);
1642      if (result->coverage_table == NULL) {
1643        fatal("cannot create hash table");
1644      }
1645      result->coverage_list = NULL;
1646      return result;
1647    }
1648    
1649    void Coverage_delete(Coverage * coverage) {
1650      JS_HashTableDestroy(coverage->coverage_table);
1651      struct FileCoverageList * p = coverage->coverage_list;
1652      while (p != NULL) {
1653        free(p->file_coverage->coverage_lines);
1654        if (p->file_coverage->source_lines != NULL) {
1655          for (uint32 i = 0; i < p->file_coverage->num_source_lines; i++) {
1656            free(p->file_coverage->source_lines[i]);
1657          }
1658          free(p->file_coverage->source_lines);
1659        }
1660        free(p->file_coverage->id);
1661        free(p->file_coverage);
1662        struct FileCoverageList * q = p;
1663        p = p->next;
1664        free(q);
1665      }
1666      free(coverage);
1667    }
1668    
1669    struct EnumeratorArg {
1670      CoverageForeachFunction f;
1671      void * p;
1672    };
1673    
1674    static intN enumerator(JSHashEntry * entry, intN i, void * arg) {
1675      struct EnumeratorArg * enumerator_arg = arg;
1676      enumerator_arg->f(entry->value, i, enumerator_arg->p);
1677      return 0;
1678    }
1679    
1680    void Coverage_foreach_file(Coverage * coverage, CoverageForeachFunction f, void * p) {
1681      struct EnumeratorArg enumerator_arg;
1682      enumerator_arg.f = f;
1683      enumerator_arg.p = p;
1684      JS_HashTableEnumerateEntries(coverage->coverage_table, enumerator, &enumerator_arg);
1685    }
1686    
1687    int jscoverage_parse_json(Coverage * coverage, const uint8_t * json, size_t length) {
1688      int result = 0;
1689    
1690      jschar * base = js_InflateString(context, (char *) json, &length);
1691      if (base == NULL) {
1692        fatal("out of memory");
1693      }
1694    
1695      jschar * parenthesized_json = xnew(jschar, addst(length, 2));
1696      parenthesized_json[0] = '(';
1697      memcpy(parenthesized_json + 1, base, mulst(length, sizeof(jschar)));
1698      parenthesized_json[length + 1] = ')';
1699    
1700      JS_free(context, base);
1701    
1702      JSParseContext parse_context;
1703      if (! js_InitParseContext(context, &parse_context, NULL, NULL, parenthesized_json, length + 2, NULL, NULL, 1)) {
1704        free(parenthesized_json);
1705        return -1;
1706      }
1707      JSParseNode * root = js_ParseScript(context, global, &parse_context);
1708      free(parenthesized_json);
1709      if (root == NULL) {
1710        result = -1;
1711        goto done;
1712      }
1713    
1714      /* root node must be TOK_LC */
1715      if (root->pn_type != TOK_LC) {
1716        result = -1;
1717        goto done;
1718      }
1719      JSParseNode * semi = root->pn_u.list.head;
1720    
1721      /* the list must be TOK_SEMI and it must contain only one element */
1722      if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {
1723        result = -1;
1724        goto done;
1725      }
1726      JSParseNode * parenthesized = semi->pn_kid;
1727    
1728      /* this must be a parenthesized expression */
1729      if (parenthesized->pn_type != TOK_RP) {
1730        result = -1;
1731        goto done;
1732      }
1733      JSParseNode * object = parenthesized->pn_kid;
1734    
1735      /* this must be an object literal */
1736      if (object->pn_type != TOK_RC) {
1737        result = -1;
1738        goto done;
1739      }
1740    
1741      for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {
1742        /* every element of this list must be TOK_COLON */
1743        if (p->pn_type != TOK_COLON) {
1744          result = -1;
1745          goto done;
1746        }
1747    
1748        /* the key must be a string representing the file */
1749        JSParseNode * key = p->pn_left;
1750        if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {
1751          result = -1;
1752          goto done;
1753        }
1754        char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));
1755    
1756        /* the value must be an object literal OR an array */
1757        JSParseNode * value = p->pn_right;
1758        if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {
1759          result = -1;
1760          goto done;
1761        }
1762    
1763        JSParseNode * array = NULL;
1764        JSParseNode * source = NULL;
1765        if (value->pn_type == TOK_RB) {
1766          /* an array */
1767          array = value;
1768        }
1769        else if (value->pn_type == TOK_RC) {
1770          /* an object literal */
1771          if (value->pn_count != 2) {
1772            result = -1;
1773            goto done;
1774          }
1775          for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {
1776            if (element->pn_type != TOK_COLON) {
1777              result = -1;
1778              goto done;
1779            }
1780            JSParseNode * left = element->pn_left;
1781            if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {
1782              result = -1;
1783              goto done;
1784            }
1785            const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));
1786            if (strcmp(s, "coverage") == 0) {
1787              array = element->pn_right;
1788              if (array->pn_type != TOK_RB) {
1789                result = -1;
1790                goto done;
1791              }
1792            }
1793            else if (strcmp(s, "source") == 0) {
1794              source = element->pn_right;
1795              if (source->pn_type != TOK_RB) {
1796                result = -1;
1797                goto done;
1798              }
1799            }
1800            else {
1801              result = -1;
1802              goto done;
1803            }
1804          }
1805        }
1806        else {
1807          result = -1;
1808          goto done;
1809        }
1810    
1811        if (array == NULL) {
1812          result = -1;
1813          goto done;
1814        }
1815    
1816        /* look up the file in the coverage table */
1817        FileCoverage * file_coverage = JS_HashTableLookup(coverage->coverage_table, id_bytes);
1818        if (file_coverage == NULL) {
1819          /* not there: create a new one */
1820          char * id = xstrdup(id_bytes);
1821          file_coverage = xmalloc(sizeof(FileCoverage));
1822          file_coverage->id = id;
1823          file_coverage->num_coverage_lines = array->pn_count;
1824          file_coverage->coverage_lines = xnew(int, array->pn_count);
1825          file_coverage->source_lines = NULL;
1826    
1827          /* set coverage for all lines */
1828          uint32 i = 0;
1829          for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1830            if (element->pn_type == TOK_NUMBER) {
1831              file_coverage->coverage_lines[i] = (int) element->pn_dval;
1832            }
1833            else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1834              file_coverage->coverage_lines[i] = -1;
1835            }
1836            else {
1837              result = -1;
1838              goto done;
1839            }
1840          }
1841          assert(i == array->pn_count);
1842    
1843          /* add to the hash table */
1844          JS_HashTableAdd(coverage->coverage_table, id, file_coverage);
1845          struct FileCoverageList * coverage_list = xmalloc(sizeof(struct FileCoverageList));
1846          coverage_list->file_coverage = file_coverage;
1847          coverage_list->next = coverage->coverage_list;
1848          coverage->coverage_list = coverage_list;
1849        }
1850        else {
1851          /* sanity check */
1852          assert(strcmp(file_coverage->id, id_bytes) == 0);
1853          if (file_coverage->num_coverage_lines != array->pn_count) {
1854            result = -2;
1855            goto done;
1856          }
1857    
1858          /* merge the coverage */
1859          uint32 i = 0;
1860          for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1861            if (element->pn_type == TOK_NUMBER) {
1862              if (file_coverage->coverage_lines[i] == -1) {
1863                result = -2;
1864                goto done;
1865              }
1866              file_coverage->coverage_lines[i] += (int) element->pn_dval;
1867            }
1868            else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1869              if (file_coverage->coverage_lines[i] != -1) {
1870                result = -2;
1871                goto done;
1872              }
1873            }
1874            else {
1875              result = -1;
1876              goto done;
1877            }
1878          }
1879          assert(i == array->pn_count);
1880        }
1881    
1882        /* if this JSON file has source, use it */
1883        if (file_coverage->source_lines == NULL && source != NULL) {
1884          file_coverage->num_source_lines = source->pn_count;
1885          file_coverage->source_lines = xnew(char *, source->pn_count);
1886          uint32 i = 0;
1887          for (JSParseNode * element = source->pn_head; element != NULL; element = element->pn_next, i++) {
1888            if (element->pn_type != TOK_STRING) {
1889              result = -1;
1890              goto done;
1891            }
1892            file_coverage->source_lines[i] = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(element->pn_atom)));
1893          }
1894          assert(i == source->pn_count);
1895        }
1896      }
1897    
1898    done:
1899      js_FinishParseContext(context, &parse_context);
1900      return result;
1901  }  }

Legend:
Removed from v.70  
changed lines
  Added in v.378

  ViewVC Help
Powered by ViewVC 1.1.24