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

Contents of /trunk/instrument-js.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 92 - (show annotations)
Wed May 7 04:21:22 2008 UTC (14 years, 10 months ago) by siliconforks
File MIME type: text/plain
File size: 25001 byte(s)
Add streams; don't use temporary files.

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 "instrument-js.h"
21
22 #include <assert.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include <jsapi.h>
27 #include <jsatom.h>
28 #include <jsfun.h>
29 #include <jsinterp.h>
30 #include <jsparse.h>
31 #include <jsregexp.h>
32 #include <jsscope.h>
33 #include <jsstr.h>
34
35 #include "util.h"
36
37 static JSRuntime * runtime = NULL;
38 static JSContext * context = NULL;
39 static JSObject * global = NULL;
40
41 /*
42 JSParseNode objects store line numbers starting from 1.
43 The lines array stores line numbers starting from 0.
44 */
45 static const char * file_id = NULL;
46 static char * lines = NULL;
47
48 void jscoverage_init(void) {
49 runtime = JS_NewRuntime(8L * 1024L * 1024L);
50 if (runtime == NULL) {
51 fatal("cannot create runtime");
52 }
53
54 context = JS_NewContext(runtime, 8192);
55 if (context == NULL) {
56 fatal("cannot create context");
57 }
58
59 global = JS_NewObject(context, NULL, NULL, NULL);
60 if (global == NULL) {
61 fatal("cannot create global object");
62 }
63
64 if (! JS_InitStandardClasses(context, global)) {
65 fatal("cannot initialize standard classes");
66 }
67 }
68
69 void jscoverage_cleanup(void) {
70 JS_DestroyContext(context);
71 JS_DestroyRuntime(runtime);
72 }
73
74 static void print_string(JSString * s, Stream * f) {
75 for (int i = 0; i < s->length; i++) {
76 char c = s->chars[i];
77 Stream_write_char(f, c);
78 }
79 }
80
81 static void print_string_atom(JSAtom * atom, Stream * f) {
82 assert(ATOM_IS_STRING(atom));
83 JSString * s = ATOM_TO_STRING(atom);
84 print_string(s, f);
85 }
86
87 static void print_string_jsval(jsval value, Stream * f) {
88 assert(JSVAL_IS_STRING(value));
89 JSString * s = JSVAL_TO_STRING(value);
90 print_string(s, f);
91 }
92
93 static void print_quoted_string_atom(JSAtom * atom, Stream * f) {
94 assert(ATOM_IS_STRING(atom));
95 JSString * s = ATOM_TO_STRING(atom);
96 JSString * quoted = js_QuoteString(context, s, '"');
97 print_string(quoted, f);
98 }
99
100 static const char * get_op(uint8 op) {
101 switch(op) {
102 case JSOP_BITOR:
103 return "|";
104 case JSOP_BITXOR:
105 return "^";
106 case JSOP_BITAND:
107 return "&";
108 case JSOP_EQ:
109 return "==";
110 case JSOP_NE:
111 return "!=";
112 case JSOP_NEW_EQ:
113 return "===";
114 case JSOP_NEW_NE:
115 return "!==";
116 case JSOP_LT:
117 return "<";
118 case JSOP_LE:
119 return "<=";
120 case JSOP_GT:
121 return ">";
122 case JSOP_GE:
123 return ">=";
124 case JSOP_LSH:
125 return "<<";
126 case JSOP_RSH:
127 return ">>";
128 case JSOP_URSH:
129 return ">>>";
130 case JSOP_ADD:
131 return "+";
132 case JSOP_SUB:
133 return "-";
134 case JSOP_MUL:
135 return "*";
136 case JSOP_DIV:
137 return "/";
138 case JSOP_MOD:
139 return "%";
140 default:
141 abort();
142 }
143 }
144
145 static void instrument_expression(JSParseNode * node, Stream * f);
146 static void instrument_statement(JSParseNode * node, Stream * f, int indent);
147
148 static void instrument_function(JSParseNode * node, Stream * f, int indent) {
149 assert(node->pn_arity == PN_FUNC);
150 assert(ATOM_IS_OBJECT(node->pn_funAtom));
151 JSObject * object = ATOM_TO_OBJECT(node->pn_funAtom);
152 assert(JS_ObjectIsFunction(context, object));
153 JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);
154 assert(function);
155 assert(object == function->object);
156 Stream_printf(f, "%*s", indent, "");
157 Stream_write_string(f, "function");
158
159 /* function name */
160 if (function->atom) {
161 Stream_write_char(f, ' ');
162 print_string_atom(function->atom, f);
163 }
164
165 /* function parameters */
166 Stream_write_string(f, "(");
167 JSAtom ** params = xmalloc(function->nargs * sizeof(JSAtom *));
168 for (int i = 0; i < function->nargs; i++) {
169 /* initialize to NULL for sanity check */
170 params[i] = NULL;
171 }
172 JSScope * scope = OBJ_SCOPE(object);
173 for (JSScopeProperty * scope_property = SCOPE_LAST_PROP(scope); scope_property != NULL; scope_property = scope_property->parent) {
174 if (scope_property->getter != js_GetArgument) {
175 continue;
176 }
177 assert(scope_property->flags & SPROP_HAS_SHORTID);
178 assert((uint16) scope_property->shortid < function->nargs);
179 assert(JSID_IS_ATOM(scope_property->id));
180 params[(uint16) scope_property->shortid] = JSID_TO_ATOM(scope_property->id);
181 }
182 for (int i = 0; i < function->nargs; i++) {
183 assert(params[i] != NULL);
184 if (i > 0) {
185 Stream_write_string(f, ", ");
186 }
187 if (ATOM_IS_STRING(params[i])) {
188 print_string_atom(params[i], f);
189 }
190 }
191 Stream_write_string(f, ") {\n");
192 free(params);
193
194 /* function body */
195 instrument_statement(node->pn_body, f, indent + 2);
196
197 Stream_write_string(f, "}\n");
198 }
199
200 static void instrument_function_call(JSParseNode * node, Stream * f) {
201 instrument_expression(node->pn_head, f);
202 Stream_write_char(f, '(');
203 for (struct JSParseNode * p = node->pn_head->pn_next; p != NULL; p = p->pn_next) {
204 if (p != node->pn_head->pn_next) {
205 Stream_write_string(f, ", ");
206 }
207 instrument_expression(p, f);
208 }
209 Stream_write_char(f, ')');
210 }
211
212 /*
213 See <Expressions> in jsparse.h.
214 TOK_FUNCTION is handled as a statement and as an expression.
215 TOK_DBLDOT is not handled (XML op).
216 TOK_DEFSHARP and TOK_USESHARP are not handled.
217 TOK_ANYNAME is not handled (XML op).
218 TOK_AT is not handled (XML op).
219 TOK_DBLCOLON is not handled.
220 TOK_XML* are not handled.
221 There seem to be some undocumented expressions:
222 TOK_INSTANCEOF binary
223 TOK_IN binary
224 */
225 static void instrument_expression(JSParseNode * node, Stream * f) {
226 switch (node->pn_type) {
227 case TOK_FUNCTION:
228 instrument_function(node, f, 0);
229 break;
230 case TOK_COMMA:
231 for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
232 if (p != node->pn_head) {
233 Stream_write_string(f, ", ");
234 }
235 instrument_expression(p, f);
236 }
237 break;
238 case TOK_ASSIGN:
239 instrument_expression(node->pn_left, f);
240 Stream_write_char(f, ' ');
241 switch (node->pn_op) {
242 case JSOP_ADD:
243 case JSOP_SUB:
244 case JSOP_MUL:
245 case JSOP_MOD:
246 case JSOP_LSH:
247 case JSOP_RSH:
248 case JSOP_URSH:
249 case JSOP_BITAND:
250 case JSOP_BITOR:
251 case JSOP_BITXOR:
252 case JSOP_DIV:
253 Stream_printf(f, "%s", get_op(node->pn_op));
254 break;
255 default:
256 /* do nothing - it must be a simple assignment */
257 break;
258 }
259 Stream_write_string(f, "= ");
260 instrument_expression(node->pn_right, f);
261 break;
262 case TOK_HOOK:
263 instrument_expression(node->pn_kid1, f);
264 Stream_write_string(f, "? ");
265 instrument_expression(node->pn_kid2, f);
266 Stream_write_string(f, ": ");
267 instrument_expression(node->pn_kid3, f);
268 break;
269 case TOK_OR:
270 instrument_expression(node->pn_left, f);
271 Stream_write_string(f, " || ");
272 instrument_expression(node->pn_right, f);
273 break;
274 case TOK_AND:
275 instrument_expression(node->pn_left, f);
276 Stream_write_string(f, " && ");
277 instrument_expression(node->pn_right, f);
278 break;
279 case TOK_BITOR:
280 case TOK_BITXOR:
281 case TOK_BITAND:
282 case TOK_EQOP:
283 case TOK_RELOP:
284 case TOK_SHOP:
285 case TOK_PLUS:
286 case TOK_MINUS:
287 case TOK_STAR:
288 case TOK_DIVOP:
289 switch (node->pn_arity) {
290 case PN_BINARY:
291 instrument_expression(node->pn_left, f);
292 Stream_printf(f, " %s ", get_op(node->pn_op));
293 instrument_expression(node->pn_right, f);
294 break;
295 case PN_LIST:
296 for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
297 if (p != node->pn_head) {
298 Stream_printf(f, " %s ", get_op(node->pn_op));
299 }
300 instrument_expression(p, f);
301 }
302 break;
303 default:
304 abort();
305 }
306 break;
307 case TOK_UNARYOP:
308 switch (node->pn_op) {
309 case JSOP_NEG:
310 Stream_write_char(f, '-');
311 instrument_expression(node->pn_kid, f);
312 break;
313 case JSOP_POS:
314 Stream_write_char(f, '+');
315 instrument_expression(node->pn_kid, f);
316 break;
317 case JSOP_NOT:
318 Stream_write_char(f, '!');
319 instrument_expression(node->pn_kid, f);
320 break;
321 case JSOP_BITNOT:
322 Stream_write_char(f, '~');
323 instrument_expression(node->pn_kid, f);
324 break;
325 case JSOP_TYPEOF:
326 Stream_write_string(f, "typeof ");
327 instrument_expression(node->pn_kid, f);
328 break;
329 case JSOP_VOID:
330 Stream_write_string(f, "void ");
331 instrument_expression(node->pn_kid, f);
332 break;
333 default:
334 abort();
335 break;
336 }
337 break;
338 case TOK_INC:
339 case TOK_DEC:
340 /*
341 This is not documented, but node->pn_op tells whether it is pre- or post-increment.
342 */
343 switch (node->pn_op) {
344 case JSOP_INCNAME:
345 case JSOP_INCPROP:
346 case JSOP_INCELEM:
347 Stream_write_string(f, "++");
348 instrument_expression(node->pn_kid, f);
349 break;
350 case JSOP_DECNAME:
351 case JSOP_DECPROP:
352 case JSOP_DECELEM:
353 Stream_write_string(f, "--");
354 instrument_expression(node->pn_kid, f);
355 break;
356 case JSOP_NAMEINC:
357 case JSOP_PROPINC:
358 case JSOP_ELEMINC:
359 instrument_expression(node->pn_kid, f);
360 Stream_write_string(f, "++");
361 break;
362 case JSOP_NAMEDEC:
363 case JSOP_PROPDEC:
364 case JSOP_ELEMDEC:
365 instrument_expression(node->pn_kid, f);
366 Stream_write_string(f, "--");
367 break;
368 default:
369 abort();
370 break;
371 }
372 break;
373 case TOK_NEW:
374 Stream_write_string(f, "new ");
375 instrument_function_call(node, f);
376 break;
377 case TOK_DELETE:
378 Stream_write_string(f, "delete ");
379 instrument_expression(node->pn_kid, f);
380 break;
381 case TOK_DOT:
382 /*
383 This may have originally been x['foo-bar']. Because the string 'foo-bar'
384 contains illegal characters, we have to use the subscript syntax instead of
385 the dot syntax.
386 */
387 instrument_expression(node->pn_expr, f);
388 assert(ATOM_IS_STRING(node->pn_atom));
389 {
390 JSString * s = ATOM_TO_STRING(node->pn_atom);
391 /* XXX - semantics changed in 1.7 */
392 if (! ATOM_KEYWORD(node->pn_atom) && js_IsIdentifier(s)) {
393 Stream_write_char(f, '.');
394 print_string_atom(node->pn_atom, f);
395 }
396 else {
397 Stream_write_char(f, '[');
398 print_quoted_string_atom(node->pn_atom, f);
399 Stream_write_char(f, ']');
400 }
401 }
402 break;
403 case TOK_LB:
404 instrument_expression(node->pn_left, f);
405 Stream_write_char(f, '[');
406 instrument_expression(node->pn_right, f);
407 Stream_write_char(f, ']');
408 break;
409 case TOK_LP:
410 instrument_function_call(node, f);
411 break;
412 case TOK_RB:
413 Stream_write_char(f, '[');
414 for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
415 if (p != node->pn_head) {
416 Stream_write_string(f, ", ");
417 }
418 /* TOK_COMMA is a special case: a hole in the array */
419 if (p->pn_type != TOK_COMMA) {
420 instrument_expression(p, f);
421 }
422 }
423 if (node->pn_extra == PNX_ENDCOMMA) {
424 Stream_write_char(f, ',');
425 }
426 Stream_write_char(f, ']');
427 break;
428 case TOK_RC:
429 Stream_write_char(f, '{');
430 for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
431 assert(p->pn_type == TOK_COLON);
432 if (p != node->pn_head) {
433 Stream_write_string(f, ", ");
434 }
435 instrument_expression(p->pn_left, f);
436 Stream_write_string(f, ": ");
437 instrument_expression(p->pn_right, f);
438 }
439 Stream_write_char(f, '}');
440 break;
441 case TOK_RP:
442 Stream_write_char(f, '(');
443 instrument_expression(node->pn_kid, f);
444 Stream_write_char(f, ')');
445 break;
446 case TOK_NAME:
447 print_string_atom(node->pn_atom, f);
448 break;
449 case TOK_STRING:
450 print_quoted_string_atom(node->pn_atom, f);
451 break;
452 case TOK_OBJECT:
453 switch (node->pn_op) {
454 case JSOP_OBJECT:
455 /* I assume this is JSOP_REGEXP */
456 abort();
457 break;
458 case JSOP_REGEXP:
459 assert(ATOM_IS_OBJECT(node->pn_atom));
460 {
461 JSObject * object = ATOM_TO_OBJECT(node->pn_atom);
462 jsval result;
463 js_regexp_toString(context, object, 0, NULL, &result);
464 print_string_jsval(result, f);
465 }
466 break;
467 default:
468 abort();
469 break;
470 }
471 break;
472 case TOK_NUMBER:
473 /*
474 A 64-bit IEEE 754 floating point number has a 52-bit fraction.
475 2^(-52) = 2.22 x 10^(-16)
476 Thus there are 16 significant digits.
477 To keep the output simple, special-case zero.
478 */
479 if (node->pn_dval == 0.0) {
480 Stream_write_string(f, "0");
481 }
482 else {
483 Stream_printf(f, "%.15g", node->pn_dval);
484 }
485 break;
486 case TOK_PRIMARY:
487 switch (node->pn_op) {
488 case JSOP_TRUE:
489 Stream_write_string(f, "true");
490 break;
491 case JSOP_FALSE:
492 Stream_write_string(f, "false");
493 break;
494 case JSOP_NULL:
495 Stream_write_string(f, "null");
496 break;
497 case JSOP_THIS:
498 Stream_write_string(f, "this");
499 break;
500 /* jsscan.h mentions `super' ??? */
501 default:
502 abort();
503 }
504 break;
505 case TOK_INSTANCEOF:
506 instrument_expression(node->pn_left, f);
507 Stream_write_string(f, " instanceof ");
508 instrument_expression(node->pn_right, f);
509 break;
510 case TOK_IN:
511 instrument_expression(node->pn_left, f);
512 Stream_write_string(f, " in ");
513 instrument_expression(node->pn_right, f);
514 break;
515 default:
516 fatal("unsupported node type in file %s: %d", file_id, node->pn_type);
517 }
518 }
519
520 static void instrument_var_statement(JSParseNode * node, Stream * f, int indent) {
521 assert(node->pn_arity == PN_LIST);
522 Stream_printf(f, "%*s", indent, "");
523 Stream_write_string(f, "var ");
524 for (struct JSParseNode * p = node->pn_u.list.head; p != NULL; p = p->pn_next) {
525 assert(p->pn_type == TOK_NAME);
526 assert(p->pn_arity == PN_NAME);
527 if (p != node->pn_head) {
528 Stream_write_string(f, ", ");
529 }
530 print_string_atom(p->pn_atom, f);
531 if (p->pn_expr != NULL) {
532 Stream_write_string(f, " = ");
533 instrument_expression(p->pn_expr, f);
534 }
535 }
536 }
537
538 static void output_statement(JSParseNode * node, Stream * f, int indent) {
539 switch (node->pn_type) {
540 case TOK_FUNCTION:
541 instrument_function(node, f, indent);
542 break;
543 case TOK_LC:
544 assert(node->pn_arity == PN_LIST);
545 /*
546 Stream_write_string(f, "{\n");
547 */
548 for (struct JSParseNode * p = node->pn_u.list.head; p != NULL; p = p->pn_next) {
549 instrument_statement(p, f, indent);
550 }
551 /*
552 Stream_printf(f, "%*s", indent, "");
553 Stream_write_string(f, "}\n");
554 */
555 break;
556 case TOK_IF:
557 assert(node->pn_arity == PN_TERNARY);
558 Stream_printf(f, "%*s", indent, "");
559 Stream_write_string(f, "if (");
560 instrument_expression(node->pn_kid1, f);
561 Stream_write_string(f, ") {\n");
562 instrument_statement(node->pn_kid2, f, indent + 2);
563 Stream_printf(f, "%*s", indent, "");
564 Stream_write_string(f, "}\n");
565 if (node->pn_kid3) {
566 Stream_printf(f, "%*s", indent, "");
567 Stream_write_string(f, "else {\n");
568 instrument_statement(node->pn_kid3, f, indent + 2);
569 Stream_printf(f, "%*s", indent, "");
570 Stream_write_string(f, "}\n");
571 }
572 break;
573 case TOK_SWITCH:
574 assert(node->pn_arity == PN_BINARY);
575 Stream_printf(f, "%*s", indent, "");
576 Stream_write_string(f, "switch (");
577 instrument_expression(node->pn_left, f);
578 Stream_write_string(f, ") {\n");
579 for (struct JSParseNode * p = node->pn_right->pn_head; p != NULL; p = p->pn_next) {
580 Stream_printf(f, "%*s", indent, "");
581 switch (p->pn_type) {
582 case TOK_CASE:
583 Stream_write_string(f, "case ");
584 instrument_expression(p->pn_left, f);
585 Stream_write_string(f, ":\n");
586 break;
587 case TOK_DEFAULT:
588 Stream_write_string(f, "default:\n");
589 break;
590 default:
591 abort();
592 break;
593 }
594 instrument_statement(p->pn_right, f, indent + 2);
595 }
596 Stream_printf(f, "%*s", indent, "");
597 Stream_write_string(f, "}\n");
598 break;
599 case TOK_CASE:
600 case TOK_DEFAULT:
601 abort();
602 break;
603 case TOK_WHILE:
604 assert(node->pn_arity == PN_BINARY);
605 Stream_printf(f, "%*s", indent, "");
606 Stream_write_string(f, "while (");
607 instrument_expression(node->pn_left, f);
608 Stream_write_string(f, ") {\n");
609 instrument_statement(node->pn_right, f, indent + 2);
610 Stream_write_string(f, "}\n");
611 break;
612 case TOK_DO:
613 assert(node->pn_arity == PN_BINARY);
614 Stream_printf(f, "%*s", indent, "");
615 Stream_write_string(f, "do {\n");
616 instrument_statement(node->pn_left, f, indent + 2);
617 Stream_write_string(f, "}\n");
618 Stream_printf(f, "%*s", indent, "");
619 Stream_write_string(f, "while (");
620 instrument_expression(node->pn_right, f);
621 Stream_write_string(f, ");\n");
622 break;
623 case TOK_FOR:
624 assert(node->pn_arity == PN_BINARY);
625 Stream_printf(f, "%*s", indent, "");
626 Stream_write_string(f, "for (");
627 switch (node->pn_left->pn_type) {
628 case TOK_IN:
629 /* for/in */
630 assert(node->pn_left->pn_arity == PN_BINARY);
631 switch (node->pn_left->pn_left->pn_type) {
632 case TOK_VAR:
633 instrument_var_statement(node->pn_left->pn_left, f, 0);
634 break;
635 case TOK_NAME:
636 instrument_expression(node->pn_left->pn_left, f);
637 break;
638 default:
639 /* this is undocumented: for (x.value in y) */
640 instrument_expression(node->pn_left->pn_left, f);
641 break;
642 /*
643 default:
644 fprintf(stderr, "unexpected node type: %d\n", node->pn_left->pn_left->pn_type);
645 abort();
646 break;
647 */
648 }
649 Stream_write_string(f, " in ");
650 instrument_expression(node->pn_left->pn_right, f);
651 break;
652 case TOK_RESERVED:
653 /* for (;;) */
654 assert(node->pn_left->pn_arity == PN_TERNARY);
655 if (node->pn_left->pn_kid1) {
656 if (node->pn_left->pn_kid1->pn_type == TOK_VAR) {
657 instrument_var_statement(node->pn_left->pn_kid1, f, 0);
658 }
659 else {
660 instrument_expression(node->pn_left->pn_kid1, f);
661 }
662 }
663 Stream_write_string(f, ";");
664 if (node->pn_left->pn_kid2) {
665 Stream_write_char(f, ' ');
666 instrument_expression(node->pn_left->pn_kid2, f);
667 }
668 Stream_write_string(f, ";");
669 if (node->pn_left->pn_kid3) {
670 Stream_write_char(f, ' ');
671 instrument_expression(node->pn_left->pn_kid3, f);
672 }
673 break;
674 default:
675 abort();
676 break;
677 }
678 Stream_write_string(f, ") {\n");
679 instrument_statement(node->pn_right, f, indent + 2);
680 Stream_write_string(f, "}\n");
681 break;
682 case TOK_THROW:
683 assert(node->pn_arity == PN_UNARY);
684 Stream_printf(f, "%*s", indent, "");
685 Stream_write_string(f, "throw ");
686 instrument_expression(node->pn_u.unary.kid, f);
687 Stream_write_string(f, ";\n");
688 break;
689 case TOK_TRY:
690 Stream_printf(f, "%*s", indent, "");
691 Stream_write_string(f, "try {\n");
692 instrument_statement(node->pn_kid1, f, indent + 2);
693 Stream_printf(f, "%*s", indent, "");
694 Stream_write_string(f, "}\n");
695 {
696 for (JSParseNode * catch = node->pn_kid2; catch != NULL; catch = catch->pn_kid2) {
697 assert(catch->pn_type == TOK_CATCH);
698 Stream_printf(f, "%*s", indent, "");
699 Stream_write_string(f, "catch (");
700 assert(catch->pn_kid1->pn_arity == PN_NAME);
701 print_string_atom(catch->pn_kid1->pn_atom, f);
702 if (catch->pn_kid1->pn_expr) {
703 Stream_write_string(f, " if ");
704 instrument_expression(catch->pn_kid1->pn_expr, f);
705 }
706 Stream_write_string(f, ") {\n");
707 instrument_statement(catch->pn_kid3, f, indent + 2);
708 Stream_printf(f, "%*s", indent, "");
709 Stream_write_string(f, "}\n");
710 }
711 }
712 if (node->pn_kid3) {
713 Stream_printf(f, "%*s", indent, "");
714 Stream_write_string(f, "finally {\n");
715 instrument_statement(node->pn_kid3, f, indent + 2);
716 Stream_printf(f, "%*s", indent, "");
717 Stream_write_string(f, "}\n");
718 }
719 break;
720 case TOK_CATCH:
721 abort();
722 break;
723 case TOK_BREAK:
724 case TOK_CONTINUE:
725 assert(node->pn_arity == PN_NAME || node->pn_arity == PN_NULLARY);
726 Stream_printf(f, "%*s", indent, "");
727 Stream_write_string(f, node->pn_type == TOK_BREAK? "break": "continue");
728 JSAtom * atom = node->pn_u.name.atom;
729 if (atom != NULL) {
730 Stream_write_char(f, ' ');
731 print_string_atom(node->pn_atom, f);
732 }
733 Stream_write_string(f, ";\n");
734 break;
735 case TOK_WITH:
736 assert(node->pn_arity == PN_BINARY);
737 Stream_printf(f, "%*s", indent, "");
738 Stream_write_string(f, "with (");
739 instrument_expression(node->pn_left, f);
740 Stream_write_string(f, ") {\n");
741 instrument_statement(node->pn_right, f, indent + 2);
742 Stream_printf(f, "%*s", indent, "");
743 Stream_write_string(f, "}\n");
744 break;
745 case TOK_VAR:
746 instrument_var_statement(node, f, indent);
747 Stream_write_string(f, ";\n");
748 break;
749 case TOK_RETURN:
750 assert(node->pn_arity == PN_UNARY);
751 Stream_printf(f, "%*s", indent, "");
752 Stream_write_string(f, "return");
753 if (node->pn_kid != NULL) {
754 Stream_write_char(f, ' ');
755 instrument_expression(node->pn_kid, f);
756 }
757 Stream_write_string(f, ";\n");
758 break;
759 case TOK_SEMI:
760 assert(node->pn_arity == PN_UNARY);
761 Stream_printf(f, "%*s", indent, "");
762 if (node->pn_kid != NULL) {
763 instrument_expression(node->pn_kid, f);
764 }
765 Stream_write_string(f, ";\n");
766 break;
767 case TOK_COLON:
768 assert(node->pn_arity == PN_NAME);
769 /*
770 This one is tricky: can't output instrumentation between the label and the
771 statement it's supposed to label ...
772 */
773 Stream_printf(f, "%*s", indent < 2? 0: indent - 2, "");
774 print_string_atom(node->pn_atom, f);
775 Stream_write_string(f, ":\n");
776 /*
777 ... use output_statement instead of instrument_statement.
778 */
779 output_statement(node->pn_expr, f, indent);
780 break;
781 default:
782 fatal("unsupported node type in file %s: %d", file_id, node->pn_type);
783 }
784 }
785
786 /*
787 See <Statements> in jsparse.h.
788 TOK_FUNCTION is handled as a statement and as an expression.
789 TOK_EXPORT, TOK_IMPORT are not handled.
790 */
791 static void instrument_statement(JSParseNode * node, Stream * f, int indent) {
792 if (node->pn_type != TOK_LC) {
793 int line = node->pn_pos.begin.lineno;
794 /* the root node has line number 0 */
795 if (line != 0) {
796 Stream_printf(f, "%*s", indent, "");
797 Stream_printf(f, "_$jscoverage['%s'][%d]++;\n", file_id, line);
798 lines[line - 1] = 1;
799 }
800 }
801 output_statement(node, f, indent);
802 }
803
804 void jscoverage_instrument_js(const char * id, Stream * input, Stream * output) {
805 file_id = id;
806
807 /* scan the javascript */
808 size_t input_length = input->length;
809 jschar * base = js_InflateString(context, input->data, &input_length);
810 if (base == NULL) {
811 fatal("out of memory");
812 }
813 JSTokenStream * token_stream = js_NewTokenStream(context, base, input_length, NULL, 1, NULL);
814 if (token_stream == NULL) {
815 fatal("cannot create token stream from file: %s", file_id);
816 }
817
818 /* parse the javascript */
819 JSParseNode * node = js_ParseTokenStream(context, global, token_stream);
820 if (node == NULL) {
821 fatal("parse error in file: %s", file_id);
822 }
823 int num_lines = node->pn_pos.end.lineno;
824 lines = xmalloc(num_lines);
825 for (int i = 0; i < num_lines; i++) {
826 lines[i] = 0;
827 }
828
829 /*
830 An instrumented JavaScript file has 3 sections:
831 1. initialization
832 2. instrumented source code
833 3. original source code (TODO)
834 */
835
836 Stream * instrumented = Stream_new(0);
837 instrument_statement(node, instrumented, 0);
838
839 /* write line number info to the output */
840 Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n");
841 Stream_write_string(output, "if (! top._$jscoverage) {\n top._$jscoverage = {};\n}\n");
842 Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n");
843 Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id);
844 Stream_printf(output, " _$jscoverage['%s'] = [];\n", file_id);
845 for (int i = 0; i < num_lines; i++) {
846 if (lines[i]) {
847 Stream_printf(output, " _$jscoverage['%s'][%d] = 0;\n", file_id, i + 1);
848 }
849 }
850 Stream_write_string(output, "}\n");
851 free(lines);
852 lines = NULL;
853
854 /* copy the instrumented source code to the output */
855 Stream_write(output, instrumented->data, instrumented->length);
856
857 Stream_delete(instrumented);
858
859 JS_free(context, base);
860
861 file_id = NULL;
862 }

  ViewVC Help
Powered by ViewVC 1.1.24