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

Contents of /trunk/instrument-js.cpp

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.24