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

Contents of /trunk/instrument-js.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 483 - (show annotations)
Thu Oct 8 23:53:18 2009 UTC (9 years ago) by siliconforks
File size: 57627 byte(s)
Add parentheses around callee expression for some unusual function calls.

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
512 // put parentheses around anything that isn't a name or dot
513 switch (function_node->pn_type) {
514 case TOK_NAME:
515 case TOK_DOT:
516
517 // FIXME: TOK_FUNCTION is here because parentheses are already added in output_expression
518 case TOK_FUNCTION:
519
520 output_expression(function_node, f, false);
521 break;
522 default:
523 Stream_write_char(f, '(');
524 output_expression(function_node, f, false);
525 Stream_write_char(f, ')');
526 break;
527 }
528
529 output_function_arguments(node, f);
530 }
531
532 static void instrument_declarations(JSParseNode * list, Stream * f) {
533 assert(list->pn_arity == PN_LIST);
534 for (JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) {
535 if (p != list->pn_head) {
536 Stream_write_string(f, ", ");
537 }
538
539 switch (p->pn_type) {
540 case TOK_NAME:
541 print_string_atom(p->pn_atom, f);
542 if (p->pn_expr != NULL) {
543 Stream_write_string(f, " = ");
544 output_expression(p->pn_expr, f, false);
545 }
546 break;
547 default:
548 output_expression(p, f, false);
549 break;
550 }
551 }
552 }
553
554 /*
555 See <Expressions> in jsparse.h.
556 TOK_FUNCTION is handled as a statement and as an expression.
557 TOK_DBLDOT is not handled (XML op).
558 TOK_DEFSHARP and TOK_USESHARP are not handled.
559 TOK_ANYNAME is not handled (XML op).
560 TOK_AT is not handled (XML op).
561 TOK_DBLCOLON is not handled.
562 TOK_XML* are not handled.
563 There seem to be some undocumented expressions:
564 TOK_INSTANCEOF binary
565 TOK_IN binary
566 */
567 static void output_expression(JSParseNode * node, Stream * f, bool parenthesize_object_literals) {
568 switch (node->pn_type) {
569 case TOK_FUNCTION:
570 Stream_write_char(f, '(');
571 instrument_function(node, f, 0, FUNCTION_NORMAL);
572 Stream_write_char(f, ')');
573 break;
574 case TOK_COMMA:
575 for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
576 if (p != node->pn_head) {
577 Stream_write_string(f, ", ");
578 }
579 output_expression(p, f, parenthesize_object_literals);
580 }
581 break;
582 case TOK_ASSIGN:
583 output_expression(node->pn_left, f, parenthesize_object_literals);
584 Stream_write_char(f, ' ');
585 switch (node->pn_op) {
586 case JSOP_ADD:
587 case JSOP_SUB:
588 case JSOP_MUL:
589 case JSOP_MOD:
590 case JSOP_LSH:
591 case JSOP_RSH:
592 case JSOP_URSH:
593 case JSOP_BITAND:
594 case JSOP_BITOR:
595 case JSOP_BITXOR:
596 case JSOP_DIV:
597 Stream_printf(f, "%s", get_op(node->pn_op));
598 break;
599 default:
600 /* do nothing - it must be a simple assignment */
601 break;
602 }
603 Stream_write_string(f, "= ");
604 output_expression(node->pn_right, f, false);
605 break;
606 case TOK_HOOK:
607 output_expression(node->pn_kid1, f, parenthesize_object_literals);
608 Stream_write_string(f, "? ");
609 output_expression(node->pn_kid2, f, false);
610 Stream_write_string(f, ": ");
611 output_expression(node->pn_kid3, f, false);
612 break;
613 case TOK_OR:
614 case TOK_AND:
615 case TOK_BITOR:
616 case TOK_BITXOR:
617 case TOK_BITAND:
618 case TOK_EQOP:
619 case TOK_RELOP:
620 case TOK_SHOP:
621 case TOK_PLUS:
622 case TOK_MINUS:
623 case TOK_STAR:
624 case TOK_DIVOP:
625 switch (node->pn_arity) {
626 case PN_BINARY:
627 output_expression(node->pn_left, f, parenthesize_object_literals);
628 Stream_printf(f, " %s ", get_op(node->pn_op));
629 output_expression(node->pn_right, f, false);
630 break;
631 case PN_LIST:
632 for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
633 if (p == node->pn_head) {
634 output_expression(p, f, parenthesize_object_literals);
635 }
636 else {
637 Stream_printf(f, " %s ", get_op(node->pn_op));
638 output_expression(p, f, false);
639 }
640 }
641 break;
642 default:
643 abort();
644 }
645 break;
646 case TOK_UNARYOP:
647 switch (node->pn_op) {
648 case JSOP_NEG:
649 Stream_write_string(f, "- ");
650 output_expression(node->pn_kid, f, false);
651 break;
652 case JSOP_POS:
653 Stream_write_string(f, "+ ");
654 output_expression(node->pn_kid, f, false);
655 break;
656 case JSOP_NOT:
657 Stream_write_string(f, "! ");
658 output_expression(node->pn_kid, f, false);
659 break;
660 case JSOP_BITNOT:
661 Stream_write_string(f, "~ ");
662 output_expression(node->pn_kid, f, false);
663 break;
664 case JSOP_TYPEOF:
665 Stream_write_string(f, "typeof ");
666 output_expression(node->pn_kid, f, false);
667 break;
668 case JSOP_VOID:
669 Stream_write_string(f, "void ");
670 output_expression(node->pn_kid, f, false);
671 break;
672 default:
673 fatal_source(file_id, node->pn_pos.begin.lineno, "unknown operator (%ld)", node->pn_op);
674 break;
675 }
676 break;
677 case TOK_INC:
678 case TOK_DEC:
679 /*
680 This is not documented, but node->pn_op tells whether it is pre- or post-increment.
681 */
682 switch (node->pn_op) {
683 case JSOP_INCNAME:
684 case JSOP_INCPROP:
685 case JSOP_INCELEM:
686 Stream_write_string(f, "++");
687 output_expression(node->pn_kid, f, false);
688 break;
689 case JSOP_DECNAME:
690 case JSOP_DECPROP:
691 case JSOP_DECELEM:
692 Stream_write_string(f, "--");
693 output_expression(node->pn_kid, f, false);
694 break;
695 case JSOP_NAMEINC:
696 case JSOP_PROPINC:
697 case JSOP_ELEMINC:
698 output_expression(node->pn_kid, f, parenthesize_object_literals);
699 Stream_write_string(f, "++");
700 break;
701 case JSOP_NAMEDEC:
702 case JSOP_PROPDEC:
703 case JSOP_ELEMDEC:
704 output_expression(node->pn_kid, f, parenthesize_object_literals);
705 Stream_write_string(f, "--");
706 break;
707 default:
708 abort();
709 break;
710 }
711 break;
712 case TOK_NEW:
713 /*
714 For an expression like
715 new (f())();
716 SpiderMonkey creates a node with pn_head NOT in parentheses. If we just
717 output pn_head, we end up with
718 new f()();
719 This is not correct, because it is parsed as
720 (new f())();
721 We can fix this by surrounding pn_head in parentheses.
722 */
723 Stream_write_string(f, "new ");
724 if (node->pn_head->pn_type != TOK_NAME) {
725 Stream_write_char(f, '(');
726 }
727 output_expression(node->pn_head, f, false);
728 if (node->pn_head->pn_type != TOK_NAME) {
729 Stream_write_char(f, ')');
730 }
731 output_function_arguments(node, f);
732 break;
733 case TOK_DELETE:
734 Stream_write_string(f, "delete ");
735 output_expression(node->pn_kid, f, false);
736 break;
737 case TOK_DOT:
738 /* numeric literals must be parenthesized */
739 switch (node->pn_expr->pn_type) {
740 case TOK_NUMBER:
741 Stream_write_char(f, '(');
742 output_expression(node->pn_expr, f, false);
743 Stream_write_char(f, ')');
744 break;
745 default:
746 output_expression(node->pn_expr, f, true);
747 break;
748 }
749 /*
750 This may have originally been x['foo-bar']. Because the string 'foo-bar'
751 contains illegal characters, we have to use the subscript syntax instead of
752 the dot syntax.
753 */
754 assert(ATOM_IS_STRING(node->pn_atom));
755 {
756 JSString * s = ATOM_TO_STRING(node->pn_atom);
757 bool must_quote;
758 if (JSSTRING_LENGTH(s) == 0) {
759 must_quote = true;
760 }
761 else if (js_CheckKeyword(JSSTRING_CHARS(s), JSSTRING_LENGTH(s)) != TOK_EOF) {
762 must_quote = true;
763 }
764 else if (! js_IsIdentifier(s)) {
765 must_quote = true;
766 }
767 else {
768 must_quote = false;
769 }
770 if (must_quote) {
771 Stream_write_char(f, '[');
772 print_quoted_string_atom(node->pn_atom, f);
773 Stream_write_char(f, ']');
774 }
775 else {
776 Stream_write_char(f, '.');
777 print_string_atom(node->pn_atom, f);
778 }
779 }
780 break;
781 case TOK_LB:
782 output_expression(node->pn_left, f, false);
783 Stream_write_char(f, '[');
784 output_expression(node->pn_right, f, false);
785 Stream_write_char(f, ']');
786 break;
787 case TOK_LP:
788 instrument_function_call(node, f);
789 break;
790 case TOK_RB:
791 Stream_write_char(f, '[');
792 for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
793 if (p != node->pn_head) {
794 Stream_write_string(f, ", ");
795 }
796 /* TOK_COMMA is a special case: a hole in the array */
797 if (p->pn_type != TOK_COMMA) {
798 output_expression(p, f, false);
799 }
800 }
801 if (node->pn_xflags & PNX_ENDCOMMA) {
802 Stream_write_char(f, ',');
803 }
804 Stream_write_char(f, ']');
805 break;
806 case TOK_RC:
807 if (parenthesize_object_literals) {
808 Stream_write_char(f, '(');
809 }
810 Stream_write_char(f, '{');
811 for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
812 if (p->pn_type != TOK_COLON) {
813 fatal_source(file_id, p->pn_pos.begin.lineno, "unsupported node type (%ld)", p->pn_type);
814 }
815 if (p != node->pn_head) {
816 Stream_write_string(f, ", ");
817 }
818
819 /* check whether this is a getter or setter */
820 switch (p->pn_op) {
821 case JSOP_GETTER:
822 case JSOP_SETTER:
823 if (p->pn_op == JSOP_GETTER) {
824 Stream_write_string(f, "get ");
825 }
826 else {
827 Stream_write_string(f, "set ");
828 }
829 output_expression(p->pn_left, f, false);
830 Stream_write_char(f, ' ');
831 if (p->pn_right->pn_type != TOK_FUNCTION) {
832 fatal_source(file_id, p->pn_pos.begin.lineno, "expected function");
833 }
834 instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER);
835 break;
836 default:
837 output_expression(p->pn_left, f, false);
838 Stream_write_string(f, ": ");
839 output_expression(p->pn_right, f, false);
840 break;
841 }
842 }
843 Stream_write_char(f, '}');
844 if (parenthesize_object_literals) {
845 Stream_write_char(f, ')');
846 }
847 break;
848 case TOK_RP:
849 Stream_write_char(f, '(');
850 output_expression(node->pn_kid, f, false);
851 Stream_write_char(f, ')');
852 break;
853 case TOK_NAME:
854 print_string_atom(node->pn_atom, f);
855 break;
856 case TOK_STRING:
857 print_quoted_string_atom(node->pn_atom, f);
858 break;
859 case TOK_REGEXP:
860 assert(node->pn_op == JSOP_REGEXP);
861 {
862 JSObject * object = node->pn_objbox->object;
863 jsval result;
864 js_regexp_toString(context, object, &result);
865 print_regex(result, f);
866 }
867 break;
868 case TOK_NUMBER:
869 /*
870 A 64-bit IEEE 754 floating point number has a 52-bit fraction.
871 (This represents 53 bits of precision - the first bit is not stored.)
872 17 decimal digits are required to recover the floating-point number.
873 See http://docs.sun.com/source/806-3568/ncg_goldberg.html
874 To keep the output simple, special-case zero.
875 */
876 if (node->pn_dval == 0.0) {
877 if (signbit(node->pn_dval)) {
878 Stream_write_string(f, "-0");
879 }
880 else {
881 Stream_write_string(f, "0");
882 }
883 }
884 else if (node->pn_dval == INFINITY) {
885 Stream_write_string(f, "Number.POSITIVE_INFINITY");
886 }
887 else if (node->pn_dval == -INFINITY) {
888 Stream_write_string(f, "Number.NEGATIVE_INFINITY");
889 }
890 else if (isnan(node->pn_dval)) {
891 Stream_write_string(f, "Number.NaN");
892 }
893 else {
894 Stream_printf(f, "%.17g", node->pn_dval);
895 }
896 break;
897 case TOK_PRIMARY:
898 switch (node->pn_op) {
899 case JSOP_TRUE:
900 Stream_write_string(f, "true");
901 break;
902 case JSOP_FALSE:
903 Stream_write_string(f, "false");
904 break;
905 case JSOP_NULL:
906 Stream_write_string(f, "null");
907 break;
908 case JSOP_THIS:
909 Stream_write_string(f, "this");
910 break;
911 /* jsscan.h mentions `super' ??? */
912 default:
913 abort();
914 }
915 break;
916 case TOK_INSTANCEOF:
917 output_expression(node->pn_left, f, parenthesize_object_literals);
918 Stream_write_string(f, " instanceof ");
919 output_expression(node->pn_right, f, false);
920 break;
921 case TOK_IN:
922 output_expression(node->pn_left, f, false);
923 Stream_write_string(f, " in ");
924 output_expression(node->pn_right, f, false);
925 break;
926 case TOK_LEXICALSCOPE:
927 assert(node->pn_arity == PN_NAME);
928 assert(node->pn_expr->pn_type == TOK_LET);
929 assert(node->pn_expr->pn_arity == PN_BINARY);
930 Stream_write_string(f, "let(");
931 assert(node->pn_expr->pn_left->pn_type == TOK_LP);
932 assert(node->pn_expr->pn_left->pn_arity == PN_LIST);
933 instrument_declarations(node->pn_expr->pn_left, f);
934 Stream_write_string(f, ") ");
935 output_expression(node->pn_expr->pn_right, f, true);
936 break;
937 case TOK_YIELD:
938 assert(node->pn_arity == PN_UNARY);
939 Stream_write_string(f, "yield");
940 if (node->pn_kid != NULL) {
941 Stream_write_char(f, ' ');
942 output_expression(node->pn_kid, f, true);
943 }
944 break;
945 case TOK_ARRAYCOMP:
946 assert(node->pn_arity == PN_LIST);
947 {
948 JSParseNode * block_node;
949 switch (node->pn_count) {
950 case 1:
951 block_node = node->pn_head;
952 break;
953 case 2:
954 block_node = node->pn_head->pn_next;
955 break;
956 default:
957 abort();
958 break;
959 }
960 Stream_write_char(f, '[');
961 output_array_comprehension_or_generator_expression(block_node, f);
962 Stream_write_char(f, ']');
963 }
964 break;
965 case TOK_VAR:
966 assert(node->pn_arity == PN_LIST);
967 if (node->pn_op == JSOP_DEFCONST) {
968 Stream_write_string(f, "const ");
969 }
970 else {
971 Stream_write_string(f, "var ");
972 }
973 instrument_declarations(node, f);
974 break;
975 case TOK_LET:
976 assert(node->pn_arity == PN_LIST);
977 Stream_write_string(f, "let ");
978 instrument_declarations(node, f);
979 break;
980 default:
981 fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%ld)", node->pn_type);
982 }
983 }
984
985 static void output_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
986 switch (node->pn_type) {
987 case TOK_FUNCTION:
988 instrument_function(node, f, indent, FUNCTION_NORMAL);
989 Stream_write_char(f, '\n');
990 break;
991 case TOK_LC:
992 assert(node->pn_arity == PN_LIST);
993 /*
994 Stream_write_string(f, "{\n");
995 */
996 for (struct JSParseNode * p = node->pn_u.list.head; p != NULL; p = p->pn_next) {
997 instrument_statement(p, f, indent, false);
998 }
999 /*
1000 Stream_printf(f, "%*s", indent, "");
1001 Stream_write_string(f, "}\n");
1002 */
1003 break;
1004 case TOK_IF:
1005 {
1006 assert(node->pn_arity == PN_TERNARY);
1007
1008 uint32_t line = node->pn_pos.begin.lineno;
1009 if (! is_jscoverage_if) {
1010 if (line > num_lines) {
1011 fatal("file %s contains more than 65,535 lines", file_id);
1012 }
1013 if (line >= 2 && exclusive_directives[line - 2]) {
1014 is_jscoverage_if = true;
1015 }
1016 }
1017
1018 Stream_printf(f, "%*s", indent, "");
1019 Stream_write_string(f, "if (");
1020 output_expression(node->pn_kid1, f, false);
1021 Stream_write_string(f, ") {\n");
1022 if (is_jscoverage_if && node->pn_kid3) {
1023 uint32_t else_start = node->pn_kid3->pn_pos.begin.lineno;
1024 uint32_t else_end = node->pn_kid3->pn_pos.end.lineno + 1;
1025 Stream_printf(f, "%*s", indent + 2, "");
1026 Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, else_start, else_end);
1027 }
1028 instrument_statement(node->pn_kid2, f, indent + 2, false);
1029 Stream_printf(f, "%*s", indent, "");
1030 Stream_write_string(f, "}\n");
1031
1032 if (node->pn_kid3 || is_jscoverage_if) {
1033 Stream_printf(f, "%*s", indent, "");
1034 Stream_write_string(f, "else {\n");
1035
1036 if (is_jscoverage_if) {
1037 uint32_t if_start = node->pn_kid2->pn_pos.begin.lineno + 1;
1038 uint32_t if_end = node->pn_kid2->pn_pos.end.lineno + 1;
1039 Stream_printf(f, "%*s", indent + 2, "");
1040 Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_start, if_end);
1041 }
1042
1043 if (node->pn_kid3) {
1044 instrument_statement(node->pn_kid3, f, indent + 2, is_jscoverage_if);
1045 }
1046
1047 Stream_printf(f, "%*s", indent, "");
1048 Stream_write_string(f, "}\n");
1049 }
1050
1051 break;
1052 }
1053 case TOK_SWITCH:
1054 assert(node->pn_arity == PN_BINARY);
1055 Stream_printf(f, "%*s", indent, "");
1056 Stream_write_string(f, "switch (");
1057 output_expression(node->pn_left, f, false);
1058 Stream_write_string(f, ") {\n");
1059 {
1060 JSParseNode * list = node->pn_right;
1061 if (list->pn_type == TOK_LEXICALSCOPE) {
1062 list = list->pn_expr;
1063 }
1064 for (struct JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) {
1065 Stream_printf(f, "%*s", indent, "");
1066 switch (p->pn_type) {
1067 case TOK_CASE:
1068 Stream_write_string(f, "case ");
1069 output_expression(p->pn_left, f, false);
1070 Stream_write_string(f, ":\n");
1071 break;
1072 case TOK_DEFAULT:
1073 Stream_write_string(f, "default:\n");
1074 break;
1075 default:
1076 abort();
1077 break;
1078 }
1079 instrument_statement(p->pn_right, f, indent + 2, false);
1080 }
1081 }
1082 Stream_printf(f, "%*s", indent, "");
1083 Stream_write_string(f, "}\n");
1084 break;
1085 case TOK_CASE:
1086 case TOK_DEFAULT:
1087 abort();
1088 break;
1089 case TOK_WHILE:
1090 assert(node->pn_arity == PN_BINARY);
1091 Stream_printf(f, "%*s", indent, "");
1092 Stream_write_string(f, "while (");
1093 output_expression(node->pn_left, f, false);
1094 Stream_write_string(f, ") {\n");
1095 instrument_statement(node->pn_right, f, indent + 2, false);
1096 Stream_write_string(f, "}\n");
1097 break;
1098 case TOK_DO:
1099 assert(node->pn_arity == PN_BINARY);
1100 Stream_printf(f, "%*s", indent, "");
1101 Stream_write_string(f, "do {\n");
1102 instrument_statement(node->pn_left, f, indent + 2, false);
1103 Stream_write_string(f, "}\n");
1104 Stream_printf(f, "%*s", indent, "");
1105 Stream_write_string(f, "while (");
1106 output_expression(node->pn_right, f, false);
1107 Stream_write_string(f, ");\n");
1108 break;
1109 case TOK_FOR:
1110 assert(node->pn_arity == PN_BINARY);
1111 Stream_printf(f, "%*s", indent, "");
1112 switch (node->pn_left->pn_type) {
1113 case TOK_IN:
1114 /* for/in */
1115 assert(node->pn_left->pn_arity == PN_BINARY);
1116 output_for_in(node, f);
1117 break;
1118 case TOK_FORHEAD:
1119 /* for (;;) */
1120 assert(node->pn_left->pn_arity == PN_TERNARY);
1121 Stream_write_string(f, "for (");
1122 if (node->pn_left->pn_kid1) {
1123 output_expression(node->pn_left->pn_kid1, f, false);
1124 }
1125 Stream_write_string(f, ";");
1126 if (node->pn_left->pn_kid2) {
1127 Stream_write_char(f, ' ');
1128 output_expression(node->pn_left->pn_kid2, f, false);
1129 }
1130 Stream_write_string(f, ";");
1131 if (node->pn_left->pn_kid3) {
1132 Stream_write_char(f, ' ');
1133 output_expression(node->pn_left->pn_kid3, f, false);
1134 }
1135 Stream_write_char(f, ')');
1136 break;
1137 default:
1138 abort();
1139 break;
1140 }
1141 Stream_write_string(f, " {\n");
1142 instrument_statement(node->pn_right, f, indent + 2, false);
1143 Stream_write_string(f, "}\n");
1144 break;
1145 case TOK_THROW:
1146 assert(node->pn_arity == PN_UNARY);
1147 Stream_printf(f, "%*s", indent, "");
1148 Stream_write_string(f, "throw ");
1149 output_expression(node->pn_u.unary.kid, f, false);
1150 Stream_write_string(f, ";\n");
1151 break;
1152 case TOK_TRY:
1153 Stream_printf(f, "%*s", indent, "");
1154 Stream_write_string(f, "try {\n");
1155 instrument_statement(node->pn_kid1, f, indent + 2, false);
1156 Stream_printf(f, "%*s", indent, "");
1157 Stream_write_string(f, "}\n");
1158 if (node->pn_kid2) {
1159 assert(node->pn_kid2->pn_type == TOK_RESERVED);
1160 for (JSParseNode * scope = node->pn_kid2->pn_head; scope != NULL; scope = scope->pn_next) {
1161 assert(scope->pn_type == TOK_LEXICALSCOPE);
1162 JSParseNode * catch_node = scope->pn_expr;
1163 assert(catch_node->pn_type == TOK_CATCH);
1164 Stream_printf(f, "%*s", indent, "");
1165 Stream_write_string(f, "catch (");
1166 output_expression(catch_node->pn_kid1, f, false);
1167 if (catch_node->pn_kid2) {
1168 Stream_write_string(f, " if ");
1169 output_expression(catch_node->pn_kid2, f, false);
1170 }
1171 Stream_write_string(f, ") {\n");
1172 instrument_statement(catch_node->pn_kid3, f, indent + 2, false);
1173 Stream_printf(f, "%*s", indent, "");
1174 Stream_write_string(f, "}\n");
1175 }
1176 }
1177 if (node->pn_kid3) {
1178 Stream_printf(f, "%*s", indent, "");
1179 Stream_write_string(f, "finally {\n");
1180 instrument_statement(node->pn_kid3, f, indent + 2, false);
1181 Stream_printf(f, "%*s", indent, "");
1182 Stream_write_string(f, "}\n");
1183 }
1184 break;
1185 case TOK_CATCH:
1186 abort();
1187 break;
1188 case TOK_BREAK:
1189 case TOK_CONTINUE:
1190 assert(node->pn_arity == PN_NAME || node->pn_arity == PN_NULLARY);
1191 Stream_printf(f, "%*s", indent, "");
1192 Stream_write_string(f, node->pn_type == TOK_BREAK? "break": "continue");
1193 if (node->pn_atom != NULL) {
1194 Stream_write_char(f, ' ');
1195 print_string_atom(node->pn_atom, f);
1196 }
1197 Stream_write_string(f, ";\n");
1198 break;
1199 case TOK_WITH:
1200 assert(node->pn_arity == PN_BINARY);
1201 Stream_printf(f, "%*s", indent, "");
1202 Stream_write_string(f, "with (");
1203 output_expression(node->pn_left, f, false);
1204 Stream_write_string(f, ") {\n");
1205 instrument_statement(node->pn_right, f, indent + 2, false);
1206 Stream_printf(f, "%*s", indent, "");
1207 Stream_write_string(f, "}\n");
1208 break;
1209 case TOK_VAR:
1210 Stream_printf(f, "%*s", indent, "");
1211 output_expression(node, f, false);
1212 Stream_write_string(f, ";\n");
1213 break;
1214 case TOK_RETURN:
1215 assert(node->pn_arity == PN_UNARY);
1216 Stream_printf(f, "%*s", indent, "");
1217 Stream_write_string(f, "return");
1218 if (node->pn_kid != NULL) {
1219 Stream_write_char(f, ' ');
1220 output_expression(node->pn_kid, f, true);
1221 }
1222 Stream_write_string(f, ";\n");
1223 break;
1224 case TOK_SEMI:
1225 assert(node->pn_arity == PN_UNARY);
1226 Stream_printf(f, "%*s", indent, "");
1227 if (node->pn_kid != NULL) {
1228 output_expression(node->pn_kid, f, true);
1229 }
1230 Stream_write_string(f, ";\n");
1231 break;
1232 case TOK_COLON:
1233 {
1234 assert(node->pn_arity == PN_NAME);
1235 Stream_printf(f, "%*s", indent < 2? 0: indent - 2, "");
1236 print_string_atom(node->pn_atom, f);
1237 Stream_write_string(f, ":\n");
1238 JSParseNode * labelled = node->pn_expr;
1239 if (labelled->pn_type == TOK_LEXICALSCOPE) {
1240 labelled = labelled->pn_expr;
1241 }
1242 if (labelled->pn_type == TOK_LC) {
1243 /* labelled block */
1244 Stream_printf(f, "%*s", indent, "");
1245 Stream_write_string(f, "{\n");
1246 instrument_statement(labelled, f, indent + 2, false);
1247 Stream_printf(f, "%*s", indent, "");
1248 Stream_write_string(f, "}\n");
1249 }
1250 else {
1251 /*
1252 This one is tricky: can't output instrumentation between the label and the
1253 statement it's supposed to label, so use output_statement instead of
1254 instrument_statement.
1255 */
1256 output_statement(labelled, f, indent, false);
1257 }
1258 break;
1259 }
1260 case TOK_LEXICALSCOPE:
1261 /* let statement */
1262 assert(node->pn_arity == PN_NAME);
1263 switch (node->pn_expr->pn_type) {
1264 case TOK_LET:
1265 /* let statement */
1266 assert(node->pn_expr->pn_arity == PN_BINARY);
1267 instrument_statement(node->pn_expr, f, indent, false);
1268 break;
1269 case TOK_LC:
1270 /* block */
1271 Stream_printf(f, "%*s", indent, "");
1272 Stream_write_string(f, "{\n");
1273 instrument_statement(node->pn_expr, f, indent + 2, false);
1274 Stream_printf(f, "%*s", indent, "");
1275 Stream_write_string(f, "}\n");
1276 break;
1277 case TOK_FOR:
1278 instrument_statement(node->pn_expr, f, indent, false);
1279 break;
1280 default:
1281 abort();
1282 break;
1283 }
1284 break;
1285 case TOK_LET:
1286 switch (node->pn_arity) {
1287 case PN_BINARY:
1288 /* let statement */
1289 Stream_printf(f, "%*s", indent, "");
1290 Stream_write_string(f, "let (");
1291 assert(node->pn_left->pn_type == TOK_LP);
1292 assert(node->pn_left->pn_arity == PN_LIST);
1293 instrument_declarations(node->pn_left, f);
1294 Stream_write_string(f, ") {\n");
1295 instrument_statement(node->pn_right, f, indent + 2, false);
1296 Stream_printf(f, "%*s", indent, "");
1297 Stream_write_string(f, "}\n");
1298 break;
1299 case PN_LIST:
1300 /* let definition */
1301 Stream_printf(f, "%*s", indent, "");
1302 output_expression(node, f, false);
1303 Stream_write_string(f, ";\n");
1304 break;
1305 default:
1306 abort();
1307 break;
1308 }
1309 break;
1310 case TOK_DEBUGGER:
1311 Stream_printf(f, "%*s", indent, "");
1312 Stream_write_string(f, "debugger;\n");
1313 break;
1314 case TOK_SEQ:
1315 /*
1316 This occurs with the statement:
1317 for (var a = b in c) {}
1318 */
1319 assert(node->pn_arity == PN_LIST);
1320 for (JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
1321 instrument_statement(p, f, indent, false);
1322 }
1323 break;
1324 case TOK_NAME:
1325 // this is a duplicate function
1326 // FIXME
1327 break;
1328 default:
1329 fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%ld)", node->pn_type);
1330 }
1331 }
1332
1333 /*
1334 See <Statements> in jsparse.h.
1335 TOK_FUNCTION is handled as a statement and as an expression.
1336 TOK_EXPORT, TOK_IMPORT are not handled.
1337 */
1338 static void instrument_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
1339 if (node->pn_type != TOK_LC && node->pn_type != TOK_LEXICALSCOPE) {
1340 uint32_t line = node->pn_pos.begin.lineno;
1341 if (line > num_lines) {
1342 fatal("file %s contains more than 65,535 lines", file_id);
1343 }
1344
1345 /* the root node has line number 0 */
1346 if (line != 0) {
1347 Stream_printf(f, "%*s", indent, "");
1348 Stream_printf(f, "_$jscoverage['%s'][%d]++;\n", file_id, line);
1349 lines[line - 1] = 1;
1350 }
1351 }
1352 output_statement(node, f, indent, is_jscoverage_if);
1353 }
1354
1355 static bool characters_start_with(const jschar * characters, size_t line_start, size_t line_end, const char * prefix) {
1356 const jschar * characters_end = characters + line_end;
1357 const jschar * cp = characters + line_start;
1358 const char * bp = prefix;
1359 for (;;) {
1360 if (*bp == '\0') {
1361 return true;
1362 }
1363 else if (cp == characters_end) {
1364 return false;
1365 }
1366 else if (*cp != *bp) {
1367 return false;
1368 }
1369 bp++;
1370 cp++;
1371 }
1372 }
1373
1374 static bool characters_are_white_space(const jschar * characters, size_t line_start, size_t line_end) {
1375 /* XXX - other Unicode space */
1376 const jschar * end = characters + line_end;
1377 for (const jschar * p = characters + line_start; p < end; p++) {
1378 jschar c = *p;
1379 if (c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0) {
1380 continue;
1381 }
1382 else {
1383 return false;
1384 }
1385 }
1386 return true;
1387 }
1388
1389 static void error_reporter(JSContext * context, const char * message, JSErrorReport * report) {
1390 warn_source(file_id, report->lineno, "%s", message);
1391 }
1392
1393 void jscoverage_instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output) {
1394 file_id = id;
1395
1396 /* parse the javascript */
1397 JSCompiler compiler(context);
1398 if (! compiler.init(characters, num_characters, NULL, id, 1)) {
1399 fatal("cannot create token stream from file %s", file_id);
1400 }
1401 JSErrorReporter old_error_reporter = JS_SetErrorReporter(context, error_reporter);
1402 JSParseNode * node = compiler.parse(global);
1403 if (node == NULL) {
1404 js_ReportUncaughtException(context);
1405 fatal("parse error in file %s", file_id);
1406 }
1407 JS_SetErrorReporter(context, old_error_reporter);
1408 num_lines = node->pn_pos.end.lineno;
1409 lines = (char *) xmalloc(num_lines);
1410 for (unsigned int i = 0; i < num_lines; i++) {
1411 lines[i] = 0;
1412 }
1413
1414 /* search code for conditionals */
1415 exclusive_directives = xnew(bool, num_lines);
1416 for (unsigned int i = 0; i < num_lines; i++) {
1417 exclusive_directives[i] = false;
1418 }
1419
1420 bool has_conditionals = false;
1421 struct IfDirective * if_directives = NULL;
1422 size_t line_number = 0;
1423 size_t i = 0;
1424 while (i < num_characters) {
1425 if (line_number == UINT32_MAX) {
1426 fatal("file %s contains more than 65,535 lines", file_id);
1427 }
1428 line_number++;
1429 size_t line_start = i;
1430 jschar c;
1431 bool done = false;
1432 while (! done && i < num_characters) {
1433 c = characters[i];
1434 switch (c) {
1435 case '\r':
1436 case '\n':
1437 case 0x2028:
1438 case 0x2029:
1439 done = true;
1440 break;
1441 default:
1442 i++;
1443 }
1444 }
1445 size_t line_end = i;
1446 if (i < num_characters) {
1447 i++;
1448 if (c == '\r' && i < num_characters && characters[i] == '\n') {
1449 i++;
1450 }
1451 }
1452
1453 if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_IF")) {
1454 has_conditionals = true;
1455
1456 if (characters_are_white_space(characters, line_start + 16, line_end)) {
1457 exclusive_directives[line_number - 1] = true;
1458 }
1459 else {
1460 struct IfDirective * if_directive = xnew(struct IfDirective, 1);
1461 if_directive->condition_start = characters + line_start + 16;
1462 if_directive->condition_end = characters + line_end;
1463 if_directive->start_line = line_number;
1464 if_directive->end_line = 0;
1465 if_directive->next = if_directives;
1466 if_directives = if_directive;
1467 }
1468 }
1469 else if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_ENDIF")) {
1470 for (struct IfDirective * p = if_directives; p != NULL; p = p->next) {
1471 if (p->end_line == 0) {
1472 p->end_line = line_number;
1473 break;
1474 }
1475 }
1476 }
1477 }
1478
1479 /*
1480 An instrumented JavaScript file has 4 sections:
1481 1. initialization
1482 2. instrumented source code
1483 3. conditionals
1484 4. original source code
1485 */
1486
1487 Stream * instrumented = Stream_new(0);
1488 instrument_statement(node, instrumented, 0, false);
1489
1490 /* write line number info to the output */
1491 Stream_write_string(output, JSCOVERAGE_INSTRUMENTED_HEADER);
1492 switch (jscoverage_mode) {
1493 case JSCOVERAGE_MOZILLA:
1494 Stream_write_string(output, "try {\n");
1495 Stream_write_string(output, " Components.utils.import('resource://gre/modules/jscoverage.jsm');\n");
1496 Stream_printf(output, " dump('%s: successfully imported jscoverage module\\n');\n", id);
1497 Stream_write_string(output, "}\n");
1498 Stream_write_string(output, "catch (e) {\n");
1499 Stream_write_string(output, " _$jscoverage = {};\n");
1500 Stream_printf(output, " dump('%s: failed to import jscoverage module - coverage not available for this file\\n');\n", id);
1501 Stream_write_string(output, "}\n");
1502 break;
1503 case JSCOVERAGE_NORMAL:
1504 Stream_write_string(output, "if (! top._$jscoverage) {\n top._$jscoverage = {};\n}\n");
1505 Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n");
1506 break;
1507 case JSCOVERAGE_NO_BROWSER:
1508 Stream_write_string(output, "if (typeof _$jscoverage === 'undefined') {\n var _$jscoverage = {};\n}\n");
1509 break;
1510 }
1511 Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id);
1512 Stream_printf(output, " _$jscoverage['%s'] = [];\n", file_id);
1513 for (uint32_t i = 0; i < num_lines; i++) {
1514 if (lines[i]) {
1515 Stream_printf(output, " _$jscoverage['%s'][%d] = 0;\n", file_id, i + 1);
1516 }
1517 }
1518 Stream_write_string(output, "}\n");
1519 free(lines);
1520 lines = NULL;
1521 free(exclusive_directives);
1522 exclusive_directives = NULL;
1523
1524 /* copy the original source to the output */
1525 Stream_printf(output, "_$jscoverage['%s'].source = ", file_id);
1526 jscoverage_write_source(id, characters, num_characters, output);
1527 Stream_printf(output, ";\n");
1528
1529 /* conditionals */
1530 if (has_conditionals) {
1531 Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);
1532 }
1533
1534 /* copy the instrumented source code to the output */
1535 Stream_write(output, instrumented->data, instrumented->length);
1536
1537 /* conditionals */
1538 for (struct IfDirective * if_directive = if_directives; if_directive != NULL; if_directive = if_directive->next) {
1539 Stream_write_string(output, "if (!(");
1540 print_javascript(if_directive->condition_start, if_directive->condition_end - if_directive->condition_start, output);
1541 Stream_write_string(output, ")) {\n");
1542 Stream_printf(output, " _$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_directive->start_line, if_directive->end_line);
1543 Stream_write_string(output, "}\n");
1544 }
1545
1546 /* free */
1547 while (if_directives != NULL) {
1548 struct IfDirective * if_directive = if_directives;
1549 if_directives = if_directives->next;
1550 free(if_directive);
1551 }
1552
1553 Stream_delete(instrumented);
1554
1555 file_id = NULL;
1556 }
1557
1558 void jscoverage_write_source(const char * id, const jschar * characters, size_t num_characters, Stream * output) {
1559 Stream_write_string(output, "[");
1560 if (jscoverage_highlight) {
1561 Stream * highlighted_stream = Stream_new(num_characters);
1562 jscoverage_highlight_js(context, id, characters, num_characters, highlighted_stream);
1563 size_t i = 0;
1564 while (i < highlighted_stream->length) {
1565 if (i > 0) {
1566 Stream_write_char(output, ',');
1567 }
1568
1569 Stream_write_char(output, '"');
1570 bool done = false;
1571 while (! done) {
1572 char c = highlighted_stream->data[i];
1573 switch (c) {
1574 case 0x8:
1575 /* backspace */
1576 Stream_write_string(output, "\\b");
1577 break;
1578 case 0x9:
1579 /* horizontal tab */
1580 Stream_write_string(output, "\\t");
1581 break;
1582 case 0xa:
1583 /* line feed (new line) */
1584 done = true;
1585 break;
1586 /* IE doesn't support this */
1587 /*
1588 case 0xb:
1589 Stream_write_string(output, "\\v");
1590 break;
1591 */
1592 case 0xc:
1593 /* form feed */
1594 Stream_write_string(output, "\\f");
1595 break;
1596 case 0xd:
1597 /* carriage return */
1598 done = true;
1599 if (i + 1 < highlighted_stream->length && highlighted_stream->data[i + 1] == '\n') {
1600 i++;
1601 }
1602 break;
1603 case '"':
1604 Stream_write_string(output, "\\\"");
1605 break;
1606 case '\\':
1607 Stream_write_string(output, "\\\\");
1608 break;
1609 default:
1610 Stream_write_char(output, c);
1611 break;
1612 }
1613 i++;
1614 if (i >= highlighted_stream->length) {
1615 done = true;
1616 }
1617 }
1618 Stream_write_char(output, '"');
1619 }
1620 Stream_delete(highlighted_stream);
1621 }
1622 else {
1623 size_t i = 0;
1624 while (i < num_characters) {
1625 if (i > 0) {
1626 Stream_write_char(output, ',');
1627 }
1628
1629 Stream_write_char(output, '"');
1630 bool done = false;
1631 while (! done) {
1632 jschar c = characters[i];
1633 switch (c) {
1634 case 0x8:
1635 /* backspace */
1636 Stream_write_string(output, "\\b");
1637 break;
1638 case 0x9:
1639 /* horizontal tab */
1640 Stream_write_string(output, "\\t");
1641 break;
1642 case 0xa:
1643 /* line feed (new line) */
1644 done = true;
1645 break;
1646 /* IE doesn't support this */
1647 /*
1648 case 0xb:
1649 Stream_write_string(output, "\\v");
1650 break;
1651 */
1652 case 0xc:
1653 /* form feed */
1654 Stream_write_string(output, "\\f");
1655 break;
1656 case 0xd:
1657 /* carriage return */
1658 done = true;
1659 if (i + 1 < num_characters && characters[i + 1] == '\n') {
1660 i++;
1661 }
1662 break;
1663 case '"':
1664 Stream_write_string(output, "\\\"");
1665 break;
1666 case '\\':
1667 Stream_write_string(output, "\\\\");
1668 break;
1669 case '&':
1670 Stream_write_string(output, "&amp;");
1671 break;
1672 case '<':
1673 Stream_write_string(output, "&lt;");
1674 break;
1675 case '>':
1676 Stream_write_string(output, "&gt;");
1677 break;
1678 case 0x2028:
1679 case 0x2029:
1680 done = true;
1681 break;
1682 default:
1683 if (32 <= c && c <= 126) {
1684 Stream_write_char(output, c);
1685 }
1686 else {
1687 Stream_printf(output, "&#%d;", c);
1688 }
1689 break;
1690 }
1691 i++;
1692 if (i >= num_characters) {
1693 done = true;
1694 }
1695 }
1696 Stream_write_char(output, '"');
1697 }
1698 }
1699 Stream_write_string(output, "]");
1700 }
1701
1702 void jscoverage_copy_resources(const char * destination_directory) {
1703 copy_resource("jscoverage.html", destination_directory);
1704 copy_resource("jscoverage.css", destination_directory);
1705 copy_resource("jscoverage.js", destination_directory);
1706 copy_resource("jscoverage-ie.css", destination_directory);
1707 copy_resource("jscoverage-throbber.gif", destination_directory);
1708 copy_resource("jscoverage-highlight.css", destination_directory);
1709 }
1710
1711 /*
1712 coverage reports
1713 */
1714
1715 struct FileCoverageList {
1716 FileCoverage * file_coverage;
1717 struct FileCoverageList * next;
1718 };
1719
1720 struct Coverage {
1721 JSHashTable * coverage_table;
1722 struct FileCoverageList * coverage_list;
1723 };
1724
1725 static int compare_strings(const void * p1, const void * p2) {
1726 return strcmp((const char *) p1, (const char *) p2) == 0;
1727 }
1728
1729 Coverage * Coverage_new(void) {
1730 Coverage * result = (Coverage *) xmalloc(sizeof(Coverage));
1731 result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL);
1732 if (result->coverage_table == NULL) {
1733 fatal("cannot create hash table");
1734 }
1735 result->coverage_list = NULL;
1736 return result;
1737 }
1738
1739 void Coverage_delete(Coverage * coverage) {
1740 JS_HashTableDestroy(coverage->coverage_table);
1741 struct FileCoverageList * p = coverage->coverage_list;
1742 while (p != NULL) {
1743 free(p->file_coverage->coverage_lines);
1744 if (p->file_coverage->source_lines != NULL) {
1745 for (uint32 i = 0; i < p->file_coverage->num_source_lines; i++) {
1746 free(p->file_coverage->source_lines[i]);
1747 }
1748 free(p->file_coverage->source_lines);
1749 }
1750 free(p->file_coverage->id);
1751 free(p->file_coverage);
1752 struct FileCoverageList * q = p;
1753 p = p->next;
1754 free(q);
1755 }
1756 free(coverage);
1757 }
1758
1759 struct EnumeratorArg {
1760 CoverageForeachFunction f;
1761 void * p;
1762 };
1763
1764 static intN enumerator(JSHashEntry * entry, intN i, void * arg) {
1765 struct EnumeratorArg * enumerator_arg = (struct EnumeratorArg *) arg;
1766 enumerator_arg->f((FileCoverage *) entry->value, i, enumerator_arg->p);
1767 return 0;
1768 }
1769
1770 void Coverage_foreach_file(Coverage * coverage, CoverageForeachFunction f, void * p) {
1771 struct EnumeratorArg enumerator_arg;
1772 enumerator_arg.f = f;
1773 enumerator_arg.p = p;
1774 JS_HashTableEnumerateEntries(coverage->coverage_table, enumerator, &enumerator_arg);
1775 }
1776
1777 int jscoverage_parse_json(Coverage * coverage, const uint8_t * json, size_t length) {
1778 int result = 0;
1779
1780 jschar * base = js_InflateString(context, (char *) json, &length);
1781 if (base == NULL) {
1782 fatal("out of memory");
1783 }
1784
1785 jschar * parenthesized_json = xnew(jschar, addst(length, 2));
1786 parenthesized_json[0] = '(';
1787 memcpy(parenthesized_json + 1, base, mulst(length, sizeof(jschar)));
1788 parenthesized_json[length + 1] = ')';
1789
1790 JS_free(context, base);
1791
1792 JSCompiler compiler(context);
1793 if (! compiler.init(parenthesized_json, length + 2, NULL, NULL, 1)) {
1794 free(parenthesized_json);
1795 return -1;
1796 }
1797 JSParseNode * root = compiler.parse(global);
1798 free(parenthesized_json);
1799
1800 JSParseNode * semi = NULL;
1801 JSParseNode * object = NULL;
1802
1803 if (root == NULL) {
1804 result = -1;
1805 goto done;
1806 }
1807
1808 /* root node must be TOK_LC */
1809 if (root->pn_type != TOK_LC) {
1810 result = -1;
1811 goto done;
1812 }
1813 semi = root->pn_u.list.head;
1814
1815 /* the list must be TOK_SEMI and it must contain only one element */
1816 if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {
1817 result = -1;
1818 goto done;
1819 }
1820 object = semi->pn_kid;
1821
1822 /* this must be an object literal */
1823 if (object->pn_type != TOK_RC) {
1824 result = -1;
1825 goto done;
1826 }
1827
1828 for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {
1829 /* every element of this list must be TOK_COLON */
1830 if (p->pn_type != TOK_COLON) {
1831 result = -1;
1832 goto done;
1833 }
1834
1835 /* the key must be a string representing the file */
1836 JSParseNode * key = p->pn_left;
1837 if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {
1838 result = -1;
1839 goto done;
1840 }
1841 char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));
1842
1843 /* the value must be an object literal OR an array */
1844 JSParseNode * value = p->pn_right;
1845 if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {
1846 result = -1;
1847 goto done;
1848 }
1849
1850 JSParseNode * array = NULL;
1851 JSParseNode * source = NULL;
1852 if (value->pn_type == TOK_RB) {
1853 /* an array */
1854 array = value;
1855 }
1856 else if (value->pn_type == TOK_RC) {
1857 /* an object literal */
1858 if (value->pn_count != 2) {
1859 result = -1;
1860 goto done;
1861 }
1862 for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {
1863 if (element->pn_type != TOK_COLON) {
1864 result = -1;
1865 goto done;
1866 }
1867 JSParseNode * left = element->pn_left;
1868 if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {
1869 result = -1;
1870 goto done;
1871 }
1872 const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));
1873 if (strcmp(s, "coverage") == 0) {
1874 array = element->pn_right;
1875 if (array->pn_type != TOK_RB) {
1876 result = -1;
1877 goto done;
1878 }
1879 }
1880 else if (strcmp(s, "source") == 0) {
1881 source = element->pn_right;
1882 if (source->pn_type != TOK_RB) {
1883 result = -1;
1884 goto done;
1885 }
1886 }
1887 else {
1888 result = -1;
1889 goto done;
1890 }
1891 }
1892 }
1893 else {
1894 result = -1;
1895 goto done;
1896 }
1897
1898 if (array == NULL) {
1899 result = -1;
1900 goto done;
1901 }
1902
1903 /* look up the file in the coverage table */
1904 FileCoverage * file_coverage = (FileCoverage *) JS_HashTableLookup(coverage->coverage_table, id_bytes);
1905 if (file_coverage == NULL) {
1906 /* not there: create a new one */
1907 char * id = xstrdup(id_bytes);
1908 file_coverage = (FileCoverage *) xmalloc(sizeof(FileCoverage));
1909 file_coverage->id = id;
1910 file_coverage->num_coverage_lines = array->pn_count;
1911 file_coverage->coverage_lines = xnew(int, array->pn_count);
1912 file_coverage->source_lines = NULL;
1913
1914 /* set coverage for all lines */
1915 uint32 i = 0;
1916 for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1917 if (element->pn_type == TOK_NUMBER) {
1918 file_coverage->coverage_lines[i] = (int) element->pn_dval;
1919 }
1920 else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1921 file_coverage->coverage_lines[i] = -1;
1922 }
1923 else {
1924 result = -1;
1925 goto done;
1926 }
1927 }
1928 assert(i == array->pn_count);
1929
1930 /* add to the hash table */
1931 JS_HashTableAdd(coverage->coverage_table, id, file_coverage);
1932 struct FileCoverageList * coverage_list = (FileCoverageList *) xmalloc(sizeof(struct FileCoverageList));
1933 coverage_list->file_coverage = file_coverage;
1934 coverage_list->next = coverage->coverage_list;
1935 coverage->coverage_list = coverage_list;
1936 }
1937 else {
1938 /* sanity check */
1939 assert(strcmp(file_coverage->id, id_bytes) == 0);
1940 if (file_coverage->num_coverage_lines != array->pn_count) {
1941 result = -2;
1942 goto done;
1943 }
1944
1945 /* merge the coverage */
1946 uint32 i = 0;
1947 for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1948 if (element->pn_type == TOK_NUMBER) {
1949 if (file_coverage->coverage_lines[i] == -1) {
1950 result = -2;
1951 goto done;
1952 }
1953 file_coverage->coverage_lines[i] += (int) element->pn_dval;
1954 }
1955 else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1956 if (file_coverage->coverage_lines[i] != -1) {
1957 result = -2;
1958 goto done;
1959 }
1960 }
1961 else {
1962 result = -1;
1963 goto done;
1964 }
1965 }
1966 assert(i == array->pn_count);
1967 }
1968
1969 /* if this JSON file has source, use it */
1970 if (file_coverage->source_lines == NULL && source != NULL) {
1971 file_coverage->num_source_lines = source->pn_count;
1972 file_coverage->source_lines = xnew(char *, source->pn_count);
1973 uint32 i = 0;
1974 for (JSParseNode * element = source->pn_head; element != NULL; element = element->pn_next, i++) {
1975 if (element->pn_type != TOK_STRING) {
1976 result = -1;
1977 goto done;
1978 }
1979 file_coverage->source_lines[i] = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(element->pn_atom)));
1980 }
1981 assert(i == source->pn_count);
1982 }
1983 }
1984
1985 done:
1986 return result;
1987 }

Properties

Name Value
svn:mergeinfo

  ViewVC Help
Powered by ViewVC 1.1.24