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

Contents of /trunk/instrument-js.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 516 - (show annotations)
Wed Jan 13 08:45:10 2010 UTC (8 years, 11 months ago) by siliconforks
File size: 58320 byte(s)
Fix bug when "typeof" operator is represented by JSOP_TYPEOFEXPR.

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

Properties

Name Value
svn:mergeinfo

  ViewVC Help
Powered by ViewVC 1.1.24