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

Diff of /trunk/instrument-js.cpp

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

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

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

  ViewVC Help
Powered by ViewVC 1.1.24