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

Contents of /trunk/instrument-js.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 521 - (show annotations)
Wed Jan 13 18:53:04 2010 UTC (8 years, 11 months ago) by siliconforks
File size: 59177 byte(s)
Parenthesize "let" and "yield" expressions.

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

Properties

Name Value
svn:mergeinfo

  ViewVC Help
Powered by ViewVC 1.1.24