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

Contents of /trunk/instrument-js.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 473 - (show annotations)
Sun Oct 4 04:48:09 2009 UTC (10 years, 1 month ago) by siliconforks
File size: 57125 byte(s)
Remove limitation of 65535 lines per file and characters per line.

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

Properties

Name Value
svn:mergeinfo

  ViewVC Help
Powered by ViewVC 1.1.24