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

Contents of /trunk/instrument-js.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 507 - (show annotations)
Sun Jan 10 07:23:34 2010 UTC (9 years, 10 months ago) by siliconforks
File size: 58294 byte(s)
Update SpiderMonkey from Firefox 3.6rc1.

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

Properties

Name Value
svn:mergeinfo

  ViewVC Help
Powered by ViewVC 1.1.24