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

Contents of /trunk/instrument-js.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 460 - (show annotations)
Sat Sep 26 23:15:22 2009 UTC (9 years, 2 months ago) by siliconforks
File size: 57284 byte(s)
Upgrade to SpiderMonkey from Firefox 3.5.3.

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 uint16_t start_line;
57 uint16_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 uint16_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 (%d)", 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 (%d)", 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 2^(-52) = 2.22 x 10^(-16)
855 Thus there are 16 significant digits.
856 To keep the output simple, special-case zero.
857 */
858 if (node->pn_dval == 0.0) {
859 if (signbit(node->pn_dval)) {
860 Stream_write_string(f, "-0");
861 }
862 else {
863 Stream_write_string(f, "0");
864 }
865 }
866 else if (node->pn_dval == INFINITY) {
867 Stream_write_string(f, "Number.POSITIVE_INFINITY");
868 }
869 else if (node->pn_dval == -INFINITY) {
870 Stream_write_string(f, "Number.NEGATIVE_INFINITY");
871 }
872 else if (isnan(node->pn_dval)) {
873 Stream_write_string(f, "Number.NaN");
874 }
875 else {
876 Stream_printf(f, "%.15g", node->pn_dval);
877 }
878 break;
879 case TOK_PRIMARY:
880 switch (node->pn_op) {
881 case JSOP_TRUE:
882 Stream_write_string(f, "true");
883 break;
884 case JSOP_FALSE:
885 Stream_write_string(f, "false");
886 break;
887 case JSOP_NULL:
888 Stream_write_string(f, "null");
889 break;
890 case JSOP_THIS:
891 Stream_write_string(f, "this");
892 break;
893 /* jsscan.h mentions `super' ??? */
894 default:
895 abort();
896 }
897 break;
898 case TOK_INSTANCEOF:
899 output_expression(node->pn_left, f, parenthesize_object_literals);
900 Stream_write_string(f, " instanceof ");
901 output_expression(node->pn_right, f, false);
902 break;
903 case TOK_IN:
904 output_expression(node->pn_left, f, false);
905 Stream_write_string(f, " in ");
906 output_expression(node->pn_right, f, false);
907 break;
908 case TOK_LEXICALSCOPE:
909 assert(node->pn_arity == PN_NAME);
910 assert(node->pn_expr->pn_type == TOK_LET);
911 assert(node->pn_expr->pn_arity == PN_BINARY);
912 Stream_write_string(f, "let(");
913 assert(node->pn_expr->pn_left->pn_type == TOK_LP);
914 assert(node->pn_expr->pn_left->pn_arity == PN_LIST);
915 instrument_declarations(node->pn_expr->pn_left, f);
916 Stream_write_string(f, ") ");
917 output_expression(node->pn_expr->pn_right, f, true);
918 break;
919 case TOK_YIELD:
920 assert(node->pn_arity == PN_UNARY);
921 Stream_write_string(f, "yield");
922 if (node->pn_kid != NULL) {
923 Stream_write_char(f, ' ');
924 output_expression(node->pn_kid, f, true);
925 }
926 break;
927 case TOK_ARRAYCOMP:
928 assert(node->pn_arity == PN_LIST);
929 {
930 JSParseNode * block_node;
931 switch (node->pn_count) {
932 case 1:
933 block_node = node->pn_head;
934 break;
935 case 2:
936 block_node = node->pn_head->pn_next;
937 break;
938 default:
939 abort();
940 break;
941 }
942 Stream_write_char(f, '[');
943 output_array_comprehension_or_generator_expression(block_node, f);
944 Stream_write_char(f, ']');
945 }
946 break;
947 case TOK_VAR:
948 assert(node->pn_arity == PN_LIST);
949 Stream_write_string(f, "var ");
950 instrument_declarations(node, f);
951 break;
952 case TOK_LET:
953 assert(node->pn_arity == PN_LIST);
954 Stream_write_string(f, "let ");
955 instrument_declarations(node, f);
956 break;
957 default:
958 fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%d)", node->pn_type);
959 }
960 }
961
962 static void output_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
963 switch (node->pn_type) {
964 case TOK_FUNCTION:
965 instrument_function(node, f, indent, FUNCTION_NORMAL);
966 Stream_write_char(f, '\n');
967 break;
968 case TOK_LC:
969 assert(node->pn_arity == PN_LIST);
970 /*
971 Stream_write_string(f, "{\n");
972 */
973 for (struct JSParseNode * p = node->pn_u.list.head; p != NULL; p = p->pn_next) {
974 instrument_statement(p, f, indent, false);
975 }
976 /*
977 Stream_printf(f, "%*s", indent, "");
978 Stream_write_string(f, "}\n");
979 */
980 break;
981 case TOK_IF:
982 {
983 assert(node->pn_arity == PN_TERNARY);
984
985 uint16_t line = node->pn_pos.begin.lineno;
986 if (! is_jscoverage_if) {
987 if (line > num_lines) {
988 fatal("file %s contains more than 65,535 lines", file_id);
989 }
990 if (line >= 2 && exclusive_directives[line - 2]) {
991 is_jscoverage_if = true;
992 }
993 }
994
995 Stream_printf(f, "%*s", indent, "");
996 Stream_write_string(f, "if (");
997 output_expression(node->pn_kid1, f, false);
998 Stream_write_string(f, ") {\n");
999 if (is_jscoverage_if && node->pn_kid3) {
1000 uint16_t else_start = node->pn_kid3->pn_pos.begin.lineno;
1001 uint16_t else_end = node->pn_kid3->pn_pos.end.lineno + 1;
1002 Stream_printf(f, "%*s", indent + 2, "");
1003 Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, else_start, else_end);
1004 }
1005 instrument_statement(node->pn_kid2, f, indent + 2, false);
1006 Stream_printf(f, "%*s", indent, "");
1007 Stream_write_string(f, "}\n");
1008
1009 if (node->pn_kid3 || is_jscoverage_if) {
1010 Stream_printf(f, "%*s", indent, "");
1011 Stream_write_string(f, "else {\n");
1012
1013 if (is_jscoverage_if) {
1014 uint16_t if_start = node->pn_kid2->pn_pos.begin.lineno + 1;
1015 uint16_t if_end = node->pn_kid2->pn_pos.end.lineno + 1;
1016 Stream_printf(f, "%*s", indent + 2, "");
1017 Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_start, if_end);
1018 }
1019
1020 if (node->pn_kid3) {
1021 instrument_statement(node->pn_kid3, f, indent + 2, is_jscoverage_if);
1022 }
1023
1024 Stream_printf(f, "%*s", indent, "");
1025 Stream_write_string(f, "}\n");
1026 }
1027
1028 break;
1029 }
1030 case TOK_SWITCH:
1031 assert(node->pn_arity == PN_BINARY);
1032 Stream_printf(f, "%*s", indent, "");
1033 Stream_write_string(f, "switch (");
1034 output_expression(node->pn_left, f, false);
1035 Stream_write_string(f, ") {\n");
1036 {
1037 JSParseNode * list = node->pn_right;
1038 if (list->pn_type == TOK_LEXICALSCOPE) {
1039 list = list->pn_expr;
1040 }
1041 for (struct JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) {
1042 Stream_printf(f, "%*s", indent, "");
1043 switch (p->pn_type) {
1044 case TOK_CASE:
1045 Stream_write_string(f, "case ");
1046 output_expression(p->pn_left, f, false);
1047 Stream_write_string(f, ":\n");
1048 break;
1049 case TOK_DEFAULT:
1050 Stream_write_string(f, "default:\n");
1051 break;
1052 default:
1053 abort();
1054 break;
1055 }
1056 instrument_statement(p->pn_right, f, indent + 2, false);
1057 }
1058 }
1059 Stream_printf(f, "%*s", indent, "");
1060 Stream_write_string(f, "}\n");
1061 break;
1062 case TOK_CASE:
1063 case TOK_DEFAULT:
1064 abort();
1065 break;
1066 case TOK_WHILE:
1067 assert(node->pn_arity == PN_BINARY);
1068 Stream_printf(f, "%*s", indent, "");
1069 Stream_write_string(f, "while (");
1070 output_expression(node->pn_left, f, false);
1071 Stream_write_string(f, ") {\n");
1072 instrument_statement(node->pn_right, f, indent + 2, false);
1073 Stream_write_string(f, "}\n");
1074 break;
1075 case TOK_DO:
1076 assert(node->pn_arity == PN_BINARY);
1077 Stream_printf(f, "%*s", indent, "");
1078 Stream_write_string(f, "do {\n");
1079 instrument_statement(node->pn_left, f, indent + 2, false);
1080 Stream_write_string(f, "}\n");
1081 Stream_printf(f, "%*s", indent, "");
1082 Stream_write_string(f, "while (");
1083 output_expression(node->pn_right, f, false);
1084 Stream_write_string(f, ");\n");
1085 break;
1086 case TOK_FOR:
1087 assert(node->pn_arity == PN_BINARY);
1088 Stream_printf(f, "%*s", indent, "");
1089 switch (node->pn_left->pn_type) {
1090 case TOK_IN:
1091 /* for/in */
1092 assert(node->pn_left->pn_arity == PN_BINARY);
1093 output_for_in(node, f);
1094 break;
1095 case TOK_FORHEAD:
1096 /* for (;;) */
1097 assert(node->pn_left->pn_arity == PN_TERNARY);
1098 Stream_write_string(f, "for (");
1099 if (node->pn_left->pn_kid1) {
1100 output_expression(node->pn_left->pn_kid1, f, false);
1101 }
1102 Stream_write_string(f, ";");
1103 if (node->pn_left->pn_kid2) {
1104 Stream_write_char(f, ' ');
1105 output_expression(node->pn_left->pn_kid2, f, false);
1106 }
1107 Stream_write_string(f, ";");
1108 if (node->pn_left->pn_kid3) {
1109 Stream_write_char(f, ' ');
1110 output_expression(node->pn_left->pn_kid3, f, false);
1111 }
1112 Stream_write_char(f, ')');
1113 break;
1114 default:
1115 abort();
1116 break;
1117 }
1118 Stream_write_string(f, " {\n");
1119 instrument_statement(node->pn_right, f, indent + 2, false);
1120 Stream_write_string(f, "}\n");
1121 break;
1122 case TOK_THROW:
1123 assert(node->pn_arity == PN_UNARY);
1124 Stream_printf(f, "%*s", indent, "");
1125 Stream_write_string(f, "throw ");
1126 output_expression(node->pn_u.unary.kid, f, false);
1127 Stream_write_string(f, ";\n");
1128 break;
1129 case TOK_TRY:
1130 Stream_printf(f, "%*s", indent, "");
1131 Stream_write_string(f, "try {\n");
1132 instrument_statement(node->pn_kid1, f, indent + 2, false);
1133 Stream_printf(f, "%*s", indent, "");
1134 Stream_write_string(f, "}\n");
1135 if (node->pn_kid2) {
1136 assert(node->pn_kid2->pn_type == TOK_RESERVED);
1137 for (JSParseNode * scope = node->pn_kid2->pn_head; scope != NULL; scope = scope->pn_next) {
1138 assert(scope->pn_type == TOK_LEXICALSCOPE);
1139 JSParseNode * catch_node = scope->pn_expr;
1140 assert(catch_node->pn_type == TOK_CATCH);
1141 Stream_printf(f, "%*s", indent, "");
1142 Stream_write_string(f, "catch (");
1143 output_expression(catch_node->pn_kid1, f, false);
1144 if (catch_node->pn_kid2) {
1145 Stream_write_string(f, " if ");
1146 output_expression(catch_node->pn_kid2, f, false);
1147 }
1148 Stream_write_string(f, ") {\n");
1149 instrument_statement(catch_node->pn_kid3, f, indent + 2, false);
1150 Stream_printf(f, "%*s", indent, "");
1151 Stream_write_string(f, "}\n");
1152 }
1153 }
1154 if (node->pn_kid3) {
1155 Stream_printf(f, "%*s", indent, "");
1156 Stream_write_string(f, "finally {\n");
1157 instrument_statement(node->pn_kid3, f, indent + 2, false);
1158 Stream_printf(f, "%*s", indent, "");
1159 Stream_write_string(f, "}\n");
1160 }
1161 break;
1162 case TOK_CATCH:
1163 abort();
1164 break;
1165 case TOK_BREAK:
1166 case TOK_CONTINUE:
1167 assert(node->pn_arity == PN_NAME || node->pn_arity == PN_NULLARY);
1168 Stream_printf(f, "%*s", indent, "");
1169 Stream_write_string(f, node->pn_type == TOK_BREAK? "break": "continue");
1170 if (node->pn_atom != NULL) {
1171 Stream_write_char(f, ' ');
1172 print_string_atom(node->pn_atom, f);
1173 }
1174 Stream_write_string(f, ";\n");
1175 break;
1176 case TOK_WITH:
1177 assert(node->pn_arity == PN_BINARY);
1178 Stream_printf(f, "%*s", indent, "");
1179 Stream_write_string(f, "with (");
1180 output_expression(node->pn_left, f, false);
1181 Stream_write_string(f, ") {\n");
1182 instrument_statement(node->pn_right, f, indent + 2, false);
1183 Stream_printf(f, "%*s", indent, "");
1184 Stream_write_string(f, "}\n");
1185 break;
1186 case TOK_VAR:
1187 Stream_printf(f, "%*s", indent, "");
1188 output_expression(node, f, false);
1189 Stream_write_string(f, ";\n");
1190 break;
1191 case TOK_RETURN:
1192 assert(node->pn_arity == PN_UNARY);
1193 Stream_printf(f, "%*s", indent, "");
1194 Stream_write_string(f, "return");
1195 if (node->pn_kid != NULL) {
1196 Stream_write_char(f, ' ');
1197 output_expression(node->pn_kid, f, true);
1198 }
1199 Stream_write_string(f, ";\n");
1200 break;
1201 case TOK_SEMI:
1202 assert(node->pn_arity == PN_UNARY);
1203 Stream_printf(f, "%*s", indent, "");
1204 if (node->pn_kid != NULL) {
1205 output_expression(node->pn_kid, f, true);
1206 }
1207 Stream_write_string(f, ";\n");
1208 break;
1209 case TOK_COLON:
1210 {
1211 assert(node->pn_arity == PN_NAME);
1212 Stream_printf(f, "%*s", indent < 2? 0: indent - 2, "");
1213 print_string_atom(node->pn_atom, f);
1214 Stream_write_string(f, ":\n");
1215 JSParseNode * labelled = node->pn_expr;
1216 if (labelled->pn_type == TOK_LEXICALSCOPE) {
1217 labelled = labelled->pn_expr;
1218 }
1219 if (labelled->pn_type == TOK_LC) {
1220 /* labelled block */
1221 Stream_printf(f, "%*s", indent, "");
1222 Stream_write_string(f, "{\n");
1223 instrument_statement(labelled, f, indent + 2, false);
1224 Stream_printf(f, "%*s", indent, "");
1225 Stream_write_string(f, "}\n");
1226 }
1227 else {
1228 /*
1229 This one is tricky: can't output instrumentation between the label and the
1230 statement it's supposed to label, so use output_statement instead of
1231 instrument_statement.
1232 */
1233 output_statement(labelled, f, indent, false);
1234 }
1235 break;
1236 }
1237 case TOK_LEXICALSCOPE:
1238 /* let statement */
1239 assert(node->pn_arity == PN_NAME);
1240 switch (node->pn_expr->pn_type) {
1241 case TOK_LET:
1242 /* let statement */
1243 assert(node->pn_expr->pn_arity == PN_BINARY);
1244 instrument_statement(node->pn_expr, f, indent, false);
1245 break;
1246 case TOK_LC:
1247 /* block */
1248 Stream_printf(f, "%*s", indent, "");
1249 Stream_write_string(f, "{\n");
1250 instrument_statement(node->pn_expr, f, indent + 2, false);
1251 Stream_printf(f, "%*s", indent, "");
1252 Stream_write_string(f, "}\n");
1253 break;
1254 case TOK_FOR:
1255 instrument_statement(node->pn_expr, f, indent, false);
1256 break;
1257 default:
1258 abort();
1259 break;
1260 }
1261 break;
1262 case TOK_LET:
1263 switch (node->pn_arity) {
1264 case PN_BINARY:
1265 /* let statement */
1266 Stream_printf(f, "%*s", indent, "");
1267 Stream_write_string(f, "let (");
1268 assert(node->pn_left->pn_type == TOK_LP);
1269 assert(node->pn_left->pn_arity == PN_LIST);
1270 instrument_declarations(node->pn_left, f);
1271 Stream_write_string(f, ") {\n");
1272 instrument_statement(node->pn_right, f, indent + 2, false);
1273 Stream_printf(f, "%*s", indent, "");
1274 Stream_write_string(f, "}\n");
1275 break;
1276 case PN_LIST:
1277 /* let definition */
1278 Stream_printf(f, "%*s", indent, "");
1279 output_expression(node, f, false);
1280 Stream_write_string(f, ";\n");
1281 break;
1282 default:
1283 abort();
1284 break;
1285 }
1286 break;
1287 case TOK_DEBUGGER:
1288 Stream_printf(f, "%*s", indent, "");
1289 Stream_write_string(f, "debugger;\n");
1290 break;
1291 case TOK_SEQ:
1292 /*
1293 This occurs with the statement:
1294 for (var a = b in c) {}
1295 */
1296 assert(node->pn_arity == PN_LIST);
1297 for (JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
1298 instrument_statement(p, f, indent, false);
1299 }
1300 break;
1301 case TOK_NAME:
1302 // this is a duplicate function
1303 JSParseNode function_node;
1304 function_node.pn_type = TOK_FUNCTION;
1305 function_node.pn_arity = PN_FUNC;
1306 function_node.pn_funbox = node->pn_u.name.funbox2;
1307 function_node.pn_expr = node->pn_u.name.expr2;
1308 instrument_function(&function_node, f, indent, FUNCTION_NORMAL);
1309 Stream_write_char(f, '\n');
1310 break;
1311 default:
1312 fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%d)", node->pn_type);
1313 }
1314 }
1315
1316 /*
1317 See <Statements> in jsparse.h.
1318 TOK_FUNCTION is handled as a statement and as an expression.
1319 TOK_EXPORT, TOK_IMPORT are not handled.
1320 */
1321 static void instrument_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
1322 if (node->pn_type != TOK_LC && node->pn_type != TOK_LEXICALSCOPE) {
1323 uint16_t line = node->pn_pos.begin.lineno;
1324 if (line > num_lines) {
1325 fatal("file %s contains more than 65,535 lines", file_id);
1326 }
1327
1328 /* the root node has line number 0 */
1329 if (line != 0) {
1330 Stream_printf(f, "%*s", indent, "");
1331 Stream_printf(f, "_$jscoverage['%s'][%d]++;\n", file_id, line);
1332 lines[line - 1] = 1;
1333 }
1334 }
1335 output_statement(node, f, indent, is_jscoverage_if);
1336 }
1337
1338 static bool characters_start_with(const jschar * characters, size_t line_start, size_t line_end, const char * prefix) {
1339 const jschar * characters_end = characters + line_end;
1340 const jschar * cp = characters + line_start;
1341 const char * bp = prefix;
1342 for (;;) {
1343 if (*bp == '\0') {
1344 return true;
1345 }
1346 else if (cp == characters_end) {
1347 return false;
1348 }
1349 else if (*cp != *bp) {
1350 return false;
1351 }
1352 bp++;
1353 cp++;
1354 }
1355 }
1356
1357 static bool characters_are_white_space(const jschar * characters, size_t line_start, size_t line_end) {
1358 /* XXX - other Unicode space */
1359 const jschar * end = characters + line_end;
1360 for (const jschar * p = characters + line_start; p < end; p++) {
1361 jschar c = *p;
1362 if (c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0) {
1363 continue;
1364 }
1365 else {
1366 return false;
1367 }
1368 }
1369 return true;
1370 }
1371
1372 static void error_reporter(JSContext * context, const char * message, JSErrorReport * report) {
1373 warn_source(file_id, report->lineno, "%s", message);
1374 }
1375
1376 void jscoverage_instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output) {
1377 file_id = id;
1378
1379 /* parse the javascript */
1380 JSCompiler compiler(context);
1381 if (! compiler.init(characters, num_characters, NULL, id, 1)) {
1382 fatal("cannot create token stream from file %s", file_id);
1383 }
1384 JSErrorReporter old_error_reporter = JS_SetErrorReporter(context, error_reporter);
1385 JSParseNode * node = compiler.parse(global);
1386 if (node == NULL) {
1387 js_ReportUncaughtException(context);
1388 fatal("parse error in file %s", file_id);
1389 }
1390 JS_SetErrorReporter(context, old_error_reporter);
1391 num_lines = node->pn_pos.end.lineno;
1392 lines = (char *) xmalloc(num_lines);
1393 for (unsigned int i = 0; i < num_lines; i++) {
1394 lines[i] = 0;
1395 }
1396
1397 /* search code for conditionals */
1398 exclusive_directives = xnew(bool, num_lines);
1399 for (unsigned int i = 0; i < num_lines; i++) {
1400 exclusive_directives[i] = false;
1401 }
1402
1403 bool has_conditionals = false;
1404 struct IfDirective * if_directives = NULL;
1405 size_t line_number = 0;
1406 size_t i = 0;
1407 while (i < num_characters) {
1408 if (line_number == UINT16_MAX) {
1409 fatal("file %s contains more than 65,535 lines", file_id);
1410 }
1411 line_number++;
1412 size_t line_start = i;
1413 jschar c;
1414 bool done = false;
1415 while (! done && i < num_characters) {
1416 c = characters[i];
1417 switch (c) {
1418 case '\r':
1419 case '\n':
1420 case 0x2028:
1421 case 0x2029:
1422 done = true;
1423 break;
1424 default:
1425 i++;
1426 }
1427 }
1428 size_t line_end = i;
1429 if (i < num_characters) {
1430 i++;
1431 if (c == '\r' && i < num_characters && characters[i] == '\n') {
1432 i++;
1433 }
1434 }
1435
1436 if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_IF")) {
1437 has_conditionals = true;
1438
1439 if (characters_are_white_space(characters, line_start + 16, line_end)) {
1440 exclusive_directives[line_number - 1] = true;
1441 }
1442 else {
1443 struct IfDirective * if_directive = xnew(struct IfDirective, 1);
1444 if_directive->condition_start = characters + line_start + 16;
1445 if_directive->condition_end = characters + line_end;
1446 if_directive->start_line = line_number;
1447 if_directive->end_line = 0;
1448 if_directive->next = if_directives;
1449 if_directives = if_directive;
1450 }
1451 }
1452 else if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_ENDIF")) {
1453 for (struct IfDirective * p = if_directives; p != NULL; p = p->next) {
1454 if (p->end_line == 0) {
1455 p->end_line = line_number;
1456 break;
1457 }
1458 }
1459 }
1460 }
1461
1462 /*
1463 An instrumented JavaScript file has 4 sections:
1464 1. initialization
1465 2. instrumented source code
1466 3. conditionals
1467 4. original source code
1468 */
1469
1470 Stream * instrumented = Stream_new(0);
1471 instrument_statement(node, instrumented, 0, false);
1472
1473 /* write line number info to the output */
1474 Stream_write_string(output, JSCOVERAGE_INSTRUMENTED_HEADER);
1475 switch (jscoverage_mode) {
1476 case JSCOVERAGE_MOZILLA:
1477 Stream_write_string(output, "try {\n");
1478 Stream_write_string(output, " Components.utils.import('resource://gre/modules/jscoverage.jsm');\n");
1479 Stream_printf(output, " dump('%s: successfully imported jscoverage module\\n');\n", id);
1480 Stream_write_string(output, "}\n");
1481 Stream_write_string(output, "catch (e) {\n");
1482 Stream_write_string(output, " _$jscoverage = {};\n");
1483 Stream_printf(output, " dump('%s: failed to import jscoverage module - coverage not available for this file\\n');\n", id);
1484 Stream_write_string(output, "}\n");
1485 break;
1486 case JSCOVERAGE_NORMAL:
1487 Stream_write_string(output, "if (! top._$jscoverage) {\n top._$jscoverage = {};\n}\n");
1488 Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n");
1489 break;
1490 case JSCOVERAGE_NO_BROWSER:
1491 Stream_write_string(output, "if (typeof _$jscoverage === 'undefined') {\n var _$jscoverage = {};\n}\n");
1492 break;
1493 }
1494 Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id);
1495 Stream_printf(output, " _$jscoverage['%s'] = [];\n", file_id);
1496 for (int i = 0; i < num_lines; i++) {
1497 if (lines[i]) {
1498 Stream_printf(output, " _$jscoverage['%s'][%d] = 0;\n", file_id, i + 1);
1499 }
1500 }
1501 Stream_write_string(output, "}\n");
1502 free(lines);
1503 lines = NULL;
1504 free(exclusive_directives);
1505 exclusive_directives = NULL;
1506
1507 /* copy the original source to the output */
1508 Stream_printf(output, "_$jscoverage['%s'].source = ", file_id);
1509 jscoverage_write_source(id, characters, num_characters, output);
1510 Stream_printf(output, ";\n");
1511
1512 /* conditionals */
1513 if (has_conditionals) {
1514 Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);
1515 }
1516
1517 /* copy the instrumented source code to the output */
1518 Stream_write(output, instrumented->data, instrumented->length);
1519
1520 /* conditionals */
1521 for (struct IfDirective * if_directive = if_directives; if_directive != NULL; if_directive = if_directive->next) {
1522 Stream_write_string(output, "if (!(");
1523 print_javascript(if_directive->condition_start, if_directive->condition_end - if_directive->condition_start, output);
1524 Stream_write_string(output, ")) {\n");
1525 Stream_printf(output, " _$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_directive->start_line, if_directive->end_line);
1526 Stream_write_string(output, "}\n");
1527 }
1528
1529 /* free */
1530 while (if_directives != NULL) {
1531 struct IfDirective * if_directive = if_directives;
1532 if_directives = if_directives->next;
1533 free(if_directive);
1534 }
1535
1536 Stream_delete(instrumented);
1537
1538 file_id = NULL;
1539 }
1540
1541 void jscoverage_write_source(const char * id, const jschar * characters, size_t num_characters, Stream * output) {
1542 Stream_write_string(output, "[");
1543 if (jscoverage_highlight) {
1544 Stream * highlighted_stream = Stream_new(num_characters);
1545 jscoverage_highlight_js(context, id, characters, num_characters, highlighted_stream);
1546 size_t i = 0;
1547 while (i < highlighted_stream->length) {
1548 if (i > 0) {
1549 Stream_write_char(output, ',');
1550 }
1551
1552 Stream_write_char(output, '"');
1553 bool done = false;
1554 while (! done) {
1555 char c = highlighted_stream->data[i];
1556 switch (c) {
1557 case 0x8:
1558 /* backspace */
1559 Stream_write_string(output, "\\b");
1560 break;
1561 case 0x9:
1562 /* horizontal tab */
1563 Stream_write_string(output, "\\t");
1564 break;
1565 case 0xa:
1566 /* line feed (new line) */
1567 done = true;
1568 break;
1569 /* IE doesn't support this */
1570 /*
1571 case 0xb:
1572 Stream_write_string(output, "\\v");
1573 break;
1574 */
1575 case 0xc:
1576 /* form feed */
1577 Stream_write_string(output, "\\f");
1578 break;
1579 case 0xd:
1580 /* carriage return */
1581 done = true;
1582 if (i + 1 < highlighted_stream->length && highlighted_stream->data[i + 1] == '\n') {
1583 i++;
1584 }
1585 break;
1586 case '"':
1587 Stream_write_string(output, "\\\"");
1588 break;
1589 case '\\':
1590 Stream_write_string(output, "\\\\");
1591 break;
1592 default:
1593 Stream_write_char(output, c);
1594 break;
1595 }
1596 i++;
1597 if (i >= highlighted_stream->length) {
1598 done = true;
1599 }
1600 }
1601 Stream_write_char(output, '"');
1602 }
1603 Stream_delete(highlighted_stream);
1604 }
1605 else {
1606 size_t i = 0;
1607 while (i < num_characters) {
1608 if (i > 0) {
1609 Stream_write_char(output, ',');
1610 }
1611
1612 Stream_write_char(output, '"');
1613 bool done = false;
1614 while (! done) {
1615 jschar c = characters[i];
1616 switch (c) {
1617 case 0x8:
1618 /* backspace */
1619 Stream_write_string(output, "\\b");
1620 break;
1621 case 0x9:
1622 /* horizontal tab */
1623 Stream_write_string(output, "\\t");
1624 break;
1625 case 0xa:
1626 /* line feed (new line) */
1627 done = true;
1628 break;
1629 /* IE doesn't support this */
1630 /*
1631 case 0xb:
1632 Stream_write_string(output, "\\v");
1633 break;
1634 */
1635 case 0xc:
1636 /* form feed */
1637 Stream_write_string(output, "\\f");
1638 break;
1639 case 0xd:
1640 /* carriage return */
1641 done = true;
1642 if (i + 1 < num_characters && characters[i + 1] == '\n') {
1643 i++;
1644 }
1645 break;
1646 case '"':
1647 Stream_write_string(output, "\\\"");
1648 break;
1649 case '\\':
1650 Stream_write_string(output, "\\\\");
1651 break;
1652 case '&':
1653 Stream_write_string(output, "&amp;");
1654 break;
1655 case '<':
1656 Stream_write_string(output, "&lt;");
1657 break;
1658 case '>':
1659 Stream_write_string(output, "&gt;");
1660 break;
1661 case 0x2028:
1662 case 0x2029:
1663 done = true;
1664 break;
1665 default:
1666 if (32 <= c && c <= 126) {
1667 Stream_write_char(output, c);
1668 }
1669 else {
1670 Stream_printf(output, "&#%d;", c);
1671 }
1672 break;
1673 }
1674 i++;
1675 if (i >= num_characters) {
1676 done = true;
1677 }
1678 }
1679 Stream_write_char(output, '"');
1680 }
1681 }
1682 Stream_write_string(output, "]");
1683 }
1684
1685 void jscoverage_copy_resources(const char * destination_directory) {
1686 copy_resource("jscoverage.html", destination_directory);
1687 copy_resource("jscoverage.css", destination_directory);
1688 copy_resource("jscoverage.js", destination_directory);
1689 copy_resource("jscoverage-ie.css", destination_directory);
1690 copy_resource("jscoverage-throbber.gif", destination_directory);
1691 copy_resource("jscoverage-highlight.css", destination_directory);
1692 }
1693
1694 /*
1695 coverage reports
1696 */
1697
1698 struct FileCoverageList {
1699 FileCoverage * file_coverage;
1700 struct FileCoverageList * next;
1701 };
1702
1703 struct Coverage {
1704 JSHashTable * coverage_table;
1705 struct FileCoverageList * coverage_list;
1706 };
1707
1708 static int compare_strings(const void * p1, const void * p2) {
1709 return strcmp((const char *) p1, (const char *) p2) == 0;
1710 }
1711
1712 Coverage * Coverage_new(void) {
1713 Coverage * result = (Coverage *) xmalloc(sizeof(Coverage));
1714 result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL);
1715 if (result->coverage_table == NULL) {
1716 fatal("cannot create hash table");
1717 }
1718 result->coverage_list = NULL;
1719 return result;
1720 }
1721
1722 void Coverage_delete(Coverage * coverage) {
1723 JS_HashTableDestroy(coverage->coverage_table);
1724 struct FileCoverageList * p = coverage->coverage_list;
1725 while (p != NULL) {
1726 free(p->file_coverage->coverage_lines);
1727 if (p->file_coverage->source_lines != NULL) {
1728 for (uint32 i = 0; i < p->file_coverage->num_source_lines; i++) {
1729 free(p->file_coverage->source_lines[i]);
1730 }
1731 free(p->file_coverage->source_lines);
1732 }
1733 free(p->file_coverage->id);
1734 free(p->file_coverage);
1735 struct FileCoverageList * q = p;
1736 p = p->next;
1737 free(q);
1738 }
1739 free(coverage);
1740 }
1741
1742 struct EnumeratorArg {
1743 CoverageForeachFunction f;
1744 void * p;
1745 };
1746
1747 static intN enumerator(JSHashEntry * entry, intN i, void * arg) {
1748 struct EnumeratorArg * enumerator_arg = (struct EnumeratorArg *) arg;
1749 enumerator_arg->f((FileCoverage *) entry->value, i, enumerator_arg->p);
1750 return 0;
1751 }
1752
1753 void Coverage_foreach_file(Coverage * coverage, CoverageForeachFunction f, void * p) {
1754 struct EnumeratorArg enumerator_arg;
1755 enumerator_arg.f = f;
1756 enumerator_arg.p = p;
1757 JS_HashTableEnumerateEntries(coverage->coverage_table, enumerator, &enumerator_arg);
1758 }
1759
1760 int jscoverage_parse_json(Coverage * coverage, const uint8_t * json, size_t length) {
1761 int result = 0;
1762
1763 jschar * base = js_InflateString(context, (char *) json, &length);
1764 if (base == NULL) {
1765 fatal("out of memory");
1766 }
1767
1768 jschar * parenthesized_json = xnew(jschar, addst(length, 2));
1769 parenthesized_json[0] = '(';
1770 memcpy(parenthesized_json + 1, base, mulst(length, sizeof(jschar)));
1771 parenthesized_json[length + 1] = ')';
1772
1773 JS_free(context, base);
1774
1775 JSCompiler compiler(context);
1776 if (! compiler.init(parenthesized_json, length + 2, NULL, NULL, 1)) {
1777 free(parenthesized_json);
1778 return -1;
1779 }
1780 JSParseNode * root = compiler.parse(global);
1781 free(parenthesized_json);
1782
1783 JSParseNode * semi = NULL;
1784 JSParseNode * object = NULL;
1785
1786 if (root == NULL) {
1787 result = -1;
1788 goto done;
1789 }
1790
1791 /* root node must be TOK_LC */
1792 if (root->pn_type != TOK_LC) {
1793 result = -1;
1794 goto done;
1795 }
1796 semi = root->pn_u.list.head;
1797
1798 /* the list must be TOK_SEMI and it must contain only one element */
1799 if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {
1800 result = -1;
1801 goto done;
1802 }
1803 object = semi->pn_kid;
1804
1805 /* this must be an object literal */
1806 if (object->pn_type != TOK_RC) {
1807 result = -1;
1808 goto done;
1809 }
1810
1811 for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {
1812 /* every element of this list must be TOK_COLON */
1813 if (p->pn_type != TOK_COLON) {
1814 result = -1;
1815 goto done;
1816 }
1817
1818 /* the key must be a string representing the file */
1819 JSParseNode * key = p->pn_left;
1820 if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {
1821 result = -1;
1822 goto done;
1823 }
1824 char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));
1825
1826 /* the value must be an object literal OR an array */
1827 JSParseNode * value = p->pn_right;
1828 if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {
1829 result = -1;
1830 goto done;
1831 }
1832
1833 JSParseNode * array = NULL;
1834 JSParseNode * source = NULL;
1835 if (value->pn_type == TOK_RB) {
1836 /* an array */
1837 array = value;
1838 }
1839 else if (value->pn_type == TOK_RC) {
1840 /* an object literal */
1841 if (value->pn_count != 2) {
1842 result = -1;
1843 goto done;
1844 }
1845 for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {
1846 if (element->pn_type != TOK_COLON) {
1847 result = -1;
1848 goto done;
1849 }
1850 JSParseNode * left = element->pn_left;
1851 if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {
1852 result = -1;
1853 goto done;
1854 }
1855 const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));
1856 if (strcmp(s, "coverage") == 0) {
1857 array = element->pn_right;
1858 if (array->pn_type != TOK_RB) {
1859 result = -1;
1860 goto done;
1861 }
1862 }
1863 else if (strcmp(s, "source") == 0) {
1864 source = element->pn_right;
1865 if (source->pn_type != TOK_RB) {
1866 result = -1;
1867 goto done;
1868 }
1869 }
1870 else {
1871 result = -1;
1872 goto done;
1873 }
1874 }
1875 }
1876 else {
1877 result = -1;
1878 goto done;
1879 }
1880
1881 if (array == NULL) {
1882 result = -1;
1883 goto done;
1884 }
1885
1886 /* look up the file in the coverage table */
1887 FileCoverage * file_coverage = (FileCoverage *) JS_HashTableLookup(coverage->coverage_table, id_bytes);
1888 if (file_coverage == NULL) {
1889 /* not there: create a new one */
1890 char * id = xstrdup(id_bytes);
1891 file_coverage = (FileCoverage *) xmalloc(sizeof(FileCoverage));
1892 file_coverage->id = id;
1893 file_coverage->num_coverage_lines = array->pn_count;
1894 file_coverage->coverage_lines = xnew(int, array->pn_count);
1895 file_coverage->source_lines = NULL;
1896
1897 /* set coverage for all lines */
1898 uint32 i = 0;
1899 for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1900 if (element->pn_type == TOK_NUMBER) {
1901 file_coverage->coverage_lines[i] = (int) element->pn_dval;
1902 }
1903 else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1904 file_coverage->coverage_lines[i] = -1;
1905 }
1906 else {
1907 result = -1;
1908 goto done;
1909 }
1910 }
1911 assert(i == array->pn_count);
1912
1913 /* add to the hash table */
1914 JS_HashTableAdd(coverage->coverage_table, id, file_coverage);
1915 struct FileCoverageList * coverage_list = (FileCoverageList *) xmalloc(sizeof(struct FileCoverageList));
1916 coverage_list->file_coverage = file_coverage;
1917 coverage_list->next = coverage->coverage_list;
1918 coverage->coverage_list = coverage_list;
1919 }
1920 else {
1921 /* sanity check */
1922 assert(strcmp(file_coverage->id, id_bytes) == 0);
1923 if (file_coverage->num_coverage_lines != array->pn_count) {
1924 result = -2;
1925 goto done;
1926 }
1927
1928 /* merge the coverage */
1929 uint32 i = 0;
1930 for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1931 if (element->pn_type == TOK_NUMBER) {
1932 if (file_coverage->coverage_lines[i] == -1) {
1933 result = -2;
1934 goto done;
1935 }
1936 file_coverage->coverage_lines[i] += (int) element->pn_dval;
1937 }
1938 else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1939 if (file_coverage->coverage_lines[i] != -1) {
1940 result = -2;
1941 goto done;
1942 }
1943 }
1944 else {
1945 result = -1;
1946 goto done;
1947 }
1948 }
1949 assert(i == array->pn_count);
1950 }
1951
1952 /* if this JSON file has source, use it */
1953 if (file_coverage->source_lines == NULL && source != NULL) {
1954 file_coverage->num_source_lines = source->pn_count;
1955 file_coverage->source_lines = xnew(char *, source->pn_count);
1956 uint32 i = 0;
1957 for (JSParseNode * element = source->pn_head; element != NULL; element = element->pn_next, i++) {
1958 if (element->pn_type != TOK_STRING) {
1959 result = -1;
1960 goto done;
1961 }
1962 file_coverage->source_lines[i] = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(element->pn_atom)));
1963 }
1964 assert(i == source->pn_count);
1965 }
1966 }
1967
1968 done:
1969 return result;
1970 }

Properties

Name Value
svn:mergeinfo

  ViewVC Help
Powered by ViewVC 1.1.24