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

Contents of /trunk/instrument-js.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 176 - (show annotations)
Sat Sep 20 23:29:42 2008 UTC (9 years, 10 months ago) by siliconforks
Original Path: trunk/instrument-js.c
File MIME type: text/plain
File size: 36982 byte(s)
Rename variable.
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 <jsatom.h>
30 #include <jsfun.h>
31 #include <jsinterp.h>
32 #include <jsparse.h>
33 #include <jsregexp.h>
34 #include <jsscope.h>
35 #include <jsstr.h>
36
37 #include "encoding.h"
38 #include "resource-manager.h"
39 #include "util.h"
40
41 static JSRuntime * runtime = NULL;
42 static JSContext * context = NULL;
43 static JSObject * global = NULL;
44
45 /*
46 JSParseNode objects store line numbers starting from 1.
47 The lines array stores line numbers starting from 0.
48 */
49 static const char * file_id = NULL;
50 static char * lines = NULL;
51
52 void jscoverage_init(void) {
53 runtime = JS_NewRuntime(8L * 1024L * 1024L);
54 if (runtime == NULL) {
55 fatal("cannot create runtime");
56 }
57
58 context = JS_NewContext(runtime, 8192);
59 if (context == NULL) {
60 fatal("cannot create context");
61 }
62
63 global = JS_NewObject(context, NULL, NULL, NULL);
64 if (global == NULL) {
65 fatal("cannot create global object");
66 }
67
68 if (! JS_InitStandardClasses(context, global)) {
69 fatal("cannot initialize standard classes");
70 }
71 }
72
73 void jscoverage_cleanup(void) {
74 JS_DestroyContext(context);
75 JS_DestroyRuntime(runtime);
76 }
77
78 static void print_string(JSString * s, Stream * f) {
79 size_t length = JSSTRING_LENGTH(s);
80 jschar * characters = JSSTRING_CHARS(s);
81 for (size_t i = 0; i < length; i++) {
82 jschar c = characters[i];
83 if (32 <= c && c <= 126) {
84 switch (c) {
85 case '"':
86 Stream_write_string(f, "\\\"");
87 break;
88 /*
89 case '\'':
90 Stream_write_string(f, "\\'");
91 break;
92 */
93 case '\\':
94 Stream_write_string(f, "\\\\");
95 break;
96 default:
97 Stream_write_char(f, c);
98 break;
99 }
100 }
101 else {
102 switch (c) {
103 case 0x8:
104 Stream_write_string(f, "\\b");
105 break;
106 case 0x9:
107 Stream_write_string(f, "\\t");
108 break;
109 case 0xa:
110 Stream_write_string(f, "\\n");
111 break;
112 case 0xb:
113 Stream_write_string(f, "\\v");
114 break;
115 case 0xc:
116 Stream_write_string(f, "\\f");
117 break;
118 case 0xd:
119 Stream_write_string(f, "\\r");
120 break;
121 default:
122 Stream_printf(f, "\\u%04x", c);
123 break;
124 }
125 }
126 }
127 }
128
129 static void print_string_atom(JSAtom * atom, Stream * f) {
130 assert(ATOM_IS_STRING(atom));
131 JSString * s = ATOM_TO_STRING(atom);
132 print_string(s, f);
133 }
134
135 static void print_regex(jsval value, Stream * f) {
136 assert(JSVAL_IS_STRING(value));
137 JSString * s = JSVAL_TO_STRING(value);
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 Stream_write_char(f, c);
144 }
145 else {
146 Stream_printf(f, "\\u%04x", c);
147 }
148 }
149 }
150
151 static void print_quoted_string_atom(JSAtom * atom, Stream * f) {
152 assert(ATOM_IS_STRING(atom));
153 JSString * s = ATOM_TO_STRING(atom);
154 Stream_write_char(f, '"');
155 print_string(s, f);
156 Stream_write_char(f, '"');
157 }
158
159 static const char * get_op(uint8 op) {
160 switch(op) {
161 case JSOP_BITOR:
162 return "|";
163 case JSOP_BITXOR:
164 return "^";
165 case JSOP_BITAND:
166 return "&";
167 case JSOP_EQ:
168 return "==";
169 case JSOP_NE:
170 return "!=";
171 case JSOP_NEW_EQ:
172 return "===";
173 case JSOP_NEW_NE:
174 return "!==";
175 case JSOP_LT:
176 return "<";
177 case JSOP_LE:
178 return "<=";
179 case JSOP_GT:
180 return ">";
181 case JSOP_GE:
182 return ">=";
183 case JSOP_LSH:
184 return "<<";
185 case JSOP_RSH:
186 return ">>";
187 case JSOP_URSH:
188 return ">>>";
189 case JSOP_ADD:
190 return "+";
191 case JSOP_SUB:
192 return "-";
193 case JSOP_MUL:
194 return "*";
195 case JSOP_DIV:
196 return "/";
197 case JSOP_MOD:
198 return "%";
199 default:
200 abort();
201 }
202 }
203
204 static void instrument_expression(JSParseNode * node, Stream * f);
205 static void instrument_statement(JSParseNode * node, Stream * f, int indent);
206
207 enum FunctionType {
208 FUNCTION_NORMAL,
209 FUNCTION_GETTER_OR_SETTER
210 };
211
212 static void instrument_function(JSParseNode * node, Stream * f, int indent, enum FunctionType type) {
213 assert(node->pn_arity == PN_FUNC);
214 assert(ATOM_IS_OBJECT(node->pn_funAtom));
215 JSObject * object = ATOM_TO_OBJECT(node->pn_funAtom);
216 assert(JS_ObjectIsFunction(context, object));
217 JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);
218 assert(function);
219 assert(object == function->object);
220 Stream_printf(f, "%*s", indent, "");
221 if (type == FUNCTION_NORMAL) {
222 Stream_write_string(f, "function");
223 }
224
225 /* function name */
226 if (function->atom) {
227 Stream_write_char(f, ' ');
228 print_string_atom(function->atom, f);
229 }
230
231 /* function parameters */
232 Stream_write_string(f, "(");
233 JSAtom ** params = xnew(JSAtom *, function->nargs);
234 for (int i = 0; i < function->nargs; i++) {
235 /* initialize to NULL for sanity check */
236 params[i] = NULL;
237 }
238 JSScope * scope = OBJ_SCOPE(object);
239 for (JSScopeProperty * scope_property = SCOPE_LAST_PROP(scope); scope_property != NULL; scope_property = scope_property->parent) {
240 if (scope_property->getter != js_GetArgument) {
241 continue;
242 }
243 assert(scope_property->flags & SPROP_HAS_SHORTID);
244 assert((uint16) scope_property->shortid < function->nargs);
245 assert(JSID_IS_ATOM(scope_property->id));
246 params[(uint16) scope_property->shortid] = JSID_TO_ATOM(scope_property->id);
247 }
248 for (int i = 0; i < function->nargs; i++) {
249 assert(params[i] != NULL);
250 if (i > 0) {
251 Stream_write_string(f, ", ");
252 }
253 if (ATOM_IS_STRING(params[i])) {
254 print_string_atom(params[i], f);
255 }
256 }
257 Stream_write_string(f, ") {\n");
258 free(params);
259
260 /* function body */
261 instrument_statement(node->pn_body, f, indent + 2);
262
263 Stream_write_string(f, "}\n");
264 }
265
266 static void instrument_function_call(JSParseNode * node, Stream * f) {
267 instrument_expression(node->pn_head, f);
268 Stream_write_char(f, '(');
269 for (struct JSParseNode * p = node->pn_head->pn_next; p != NULL; p = p->pn_next) {
270 if (p != node->pn_head->pn_next) {
271 Stream_write_string(f, ", ");
272 }
273 instrument_expression(p, f);
274 }
275 Stream_write_char(f, ')');
276 }
277
278 /*
279 See <Expressions> in jsparse.h.
280 TOK_FUNCTION is handled as a statement and as an expression.
281 TOK_DBLDOT is not handled (XML op).
282 TOK_DEFSHARP and TOK_USESHARP are not handled.
283 TOK_ANYNAME is not handled (XML op).
284 TOK_AT is not handled (XML op).
285 TOK_DBLCOLON is not handled.
286 TOK_XML* are not handled.
287 There seem to be some undocumented expressions:
288 TOK_INSTANCEOF binary
289 TOK_IN binary
290 */
291 static void instrument_expression(JSParseNode * node, Stream * f) {
292 switch (node->pn_type) {
293 case TOK_FUNCTION:
294 instrument_function(node, f, 0, FUNCTION_NORMAL);
295 break;
296 case TOK_COMMA:
297 for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
298 if (p != node->pn_head) {
299 Stream_write_string(f, ", ");
300 }
301 instrument_expression(p, f);
302 }
303 break;
304 case TOK_ASSIGN:
305 instrument_expression(node->pn_left, f);
306 Stream_write_char(f, ' ');
307 switch (node->pn_op) {
308 case JSOP_ADD:
309 case JSOP_SUB:
310 case JSOP_MUL:
311 case JSOP_MOD:
312 case JSOP_LSH:
313 case JSOP_RSH:
314 case JSOP_URSH:
315 case JSOP_BITAND:
316 case JSOP_BITOR:
317 case JSOP_BITXOR:
318 case JSOP_DIV:
319 Stream_printf(f, "%s", get_op(node->pn_op));
320 break;
321 default:
322 /* do nothing - it must be a simple assignment */
323 break;
324 }
325 Stream_write_string(f, "= ");
326 instrument_expression(node->pn_right, f);
327 break;
328 case TOK_HOOK:
329 instrument_expression(node->pn_kid1, f);
330 Stream_write_string(f, "? ");
331 instrument_expression(node->pn_kid2, f);
332 Stream_write_string(f, ": ");
333 instrument_expression(node->pn_kid3, f);
334 break;
335 case TOK_OR:
336 instrument_expression(node->pn_left, f);
337 Stream_write_string(f, " || ");
338 instrument_expression(node->pn_right, f);
339 break;
340 case TOK_AND:
341 instrument_expression(node->pn_left, f);
342 Stream_write_string(f, " && ");
343 instrument_expression(node->pn_right, f);
344 break;
345 case TOK_BITOR:
346 case TOK_BITXOR:
347 case TOK_BITAND:
348 case TOK_EQOP:
349 case TOK_RELOP:
350 case TOK_SHOP:
351 case TOK_PLUS:
352 case TOK_MINUS:
353 case TOK_STAR:
354 case TOK_DIVOP:
355 switch (node->pn_arity) {
356 case PN_BINARY:
357 instrument_expression(node->pn_left, f);
358 Stream_printf(f, " %s ", get_op(node->pn_op));
359 instrument_expression(node->pn_right, f);
360 break;
361 case PN_LIST:
362 for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
363 if (p != node->pn_head) {
364 Stream_printf(f, " %s ", get_op(node->pn_op));
365 }
366 instrument_expression(p, f);
367 }
368 break;
369 default:
370 abort();
371 }
372 break;
373 case TOK_UNARYOP:
374 switch (node->pn_op) {
375 case JSOP_NEG:
376 Stream_write_char(f, '-');
377 instrument_expression(node->pn_kid, f);
378 break;
379 case JSOP_POS:
380 Stream_write_char(f, '+');
381 instrument_expression(node->pn_kid, f);
382 break;
383 case JSOP_NOT:
384 Stream_write_char(f, '!');
385 instrument_expression(node->pn_kid, f);
386 break;
387 case JSOP_BITNOT:
388 Stream_write_char(f, '~');
389 instrument_expression(node->pn_kid, f);
390 break;
391 case JSOP_TYPEOF:
392 Stream_write_string(f, "typeof ");
393 instrument_expression(node->pn_kid, f);
394 break;
395 case JSOP_VOID:
396 Stream_write_string(f, "void ");
397 instrument_expression(node->pn_kid, f);
398 break;
399 default:
400 abort();
401 break;
402 }
403 break;
404 case TOK_INC:
405 case TOK_DEC:
406 /*
407 This is not documented, but node->pn_op tells whether it is pre- or post-increment.
408 */
409 switch (node->pn_op) {
410 case JSOP_INCNAME:
411 case JSOP_INCPROP:
412 case JSOP_INCELEM:
413 Stream_write_string(f, "++");
414 instrument_expression(node->pn_kid, f);
415 break;
416 case JSOP_DECNAME:
417 case JSOP_DECPROP:
418 case JSOP_DECELEM:
419 Stream_write_string(f, "--");
420 instrument_expression(node->pn_kid, f);
421 break;
422 case JSOP_NAMEINC:
423 case JSOP_PROPINC:
424 case JSOP_ELEMINC:
425 instrument_expression(node->pn_kid, f);
426 Stream_write_string(f, "++");
427 break;
428 case JSOP_NAMEDEC:
429 case JSOP_PROPDEC:
430 case JSOP_ELEMDEC:
431 instrument_expression(node->pn_kid, f);
432 Stream_write_string(f, "--");
433 break;
434 default:
435 abort();
436 break;
437 }
438 break;
439 case TOK_NEW:
440 Stream_write_string(f, "new ");
441 instrument_function_call(node, f);
442 break;
443 case TOK_DELETE:
444 Stream_write_string(f, "delete ");
445 instrument_expression(node->pn_kid, f);
446 break;
447 case TOK_DOT:
448 /*
449 This may have originally been x['foo-bar']. Because the string 'foo-bar'
450 contains illegal characters, we have to use the subscript syntax instead of
451 the dot syntax.
452 */
453 instrument_expression(node->pn_expr, f);
454 assert(ATOM_IS_STRING(node->pn_atom));
455 {
456 JSString * s = ATOM_TO_STRING(node->pn_atom);
457 /* XXX - semantics changed in 1.7 */
458 if (! ATOM_KEYWORD(node->pn_atom) && js_IsIdentifier(s)) {
459 Stream_write_char(f, '.');
460 print_string_atom(node->pn_atom, f);
461 }
462 else {
463 Stream_write_char(f, '[');
464 print_quoted_string_atom(node->pn_atom, f);
465 Stream_write_char(f, ']');
466 }
467 }
468 break;
469 case TOK_LB:
470 instrument_expression(node->pn_left, f);
471 Stream_write_char(f, '[');
472 instrument_expression(node->pn_right, f);
473 Stream_write_char(f, ']');
474 break;
475 case TOK_LP:
476 instrument_function_call(node, f);
477 break;
478 case TOK_RB:
479 Stream_write_char(f, '[');
480 for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
481 if (p != node->pn_head) {
482 Stream_write_string(f, ", ");
483 }
484 /* TOK_COMMA is a special case: a hole in the array */
485 if (p->pn_type != TOK_COMMA) {
486 instrument_expression(p, f);
487 }
488 }
489 if (node->pn_extra == PNX_ENDCOMMA) {
490 Stream_write_char(f, ',');
491 }
492 Stream_write_char(f, ']');
493 break;
494 case TOK_RC:
495 Stream_write_char(f, '{');
496 for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
497 assert(p->pn_type == TOK_COLON);
498 if (p != node->pn_head) {
499 Stream_write_string(f, ", ");
500 }
501
502 /* check whether this is a getter or setter */
503 switch (p->pn_op) {
504 case JSOP_GETTER:
505 Stream_write_string(f, "get ");
506 instrument_expression(p->pn_left, f);
507 instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER);
508 break;
509 case JSOP_SETTER:
510 Stream_write_string(f, "set ");
511 instrument_expression(p->pn_left, f);
512 instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER);
513 break;
514 default:
515 instrument_expression(p->pn_left, f);
516 Stream_write_string(f, ": ");
517 instrument_expression(p->pn_right, f);
518 break;
519 }
520 }
521 Stream_write_char(f, '}');
522 break;
523 case TOK_RP:
524 Stream_write_char(f, '(');
525 instrument_expression(node->pn_kid, f);
526 Stream_write_char(f, ')');
527 break;
528 case TOK_NAME:
529 print_string_atom(node->pn_atom, f);
530 break;
531 case TOK_STRING:
532 print_quoted_string_atom(node->pn_atom, f);
533 break;
534 case TOK_OBJECT:
535 switch (node->pn_op) {
536 case JSOP_OBJECT:
537 /* I assume this is JSOP_REGEXP */
538 abort();
539 break;
540 case JSOP_REGEXP:
541 assert(ATOM_IS_OBJECT(node->pn_atom));
542 {
543 JSObject * object = ATOM_TO_OBJECT(node->pn_atom);
544 jsval result;
545 js_regexp_toString(context, object, 0, NULL, &result);
546 print_regex(result, f);
547 }
548 break;
549 default:
550 abort();
551 break;
552 }
553 break;
554 case TOK_NUMBER:
555 /*
556 A 64-bit IEEE 754 floating point number has a 52-bit fraction.
557 2^(-52) = 2.22 x 10^(-16)
558 Thus there are 16 significant digits.
559 To keep the output simple, special-case zero.
560 */
561 if (node->pn_dval == 0.0) {
562 Stream_write_string(f, "0");
563 }
564 else {
565 Stream_printf(f, "%.15g", node->pn_dval);
566 }
567 break;
568 case TOK_PRIMARY:
569 switch (node->pn_op) {
570 case JSOP_TRUE:
571 Stream_write_string(f, "true");
572 break;
573 case JSOP_FALSE:
574 Stream_write_string(f, "false");
575 break;
576 case JSOP_NULL:
577 Stream_write_string(f, "null");
578 break;
579 case JSOP_THIS:
580 Stream_write_string(f, "this");
581 break;
582 /* jsscan.h mentions `super' ??? */
583 default:
584 abort();
585 }
586 break;
587 case TOK_INSTANCEOF:
588 instrument_expression(node->pn_left, f);
589 Stream_write_string(f, " instanceof ");
590 instrument_expression(node->pn_right, f);
591 break;
592 case TOK_IN:
593 instrument_expression(node->pn_left, f);
594 Stream_write_string(f, " in ");
595 instrument_expression(node->pn_right, f);
596 break;
597 default:
598 fatal("unsupported node type in file %s: %d", file_id, node->pn_type);
599 }
600 }
601
602 static void instrument_var_statement(JSParseNode * node, Stream * f, int indent) {
603 assert(node->pn_arity == PN_LIST);
604 Stream_printf(f, "%*s", indent, "");
605 Stream_write_string(f, "var ");
606 for (struct JSParseNode * p = node->pn_u.list.head; p != NULL; p = p->pn_next) {
607 assert(p->pn_type == TOK_NAME);
608 assert(p->pn_arity == PN_NAME);
609 if (p != node->pn_head) {
610 Stream_write_string(f, ", ");
611 }
612 print_string_atom(p->pn_atom, f);
613 if (p->pn_expr != NULL) {
614 Stream_write_string(f, " = ");
615 instrument_expression(p->pn_expr, f);
616 }
617 }
618 }
619
620 static void output_statement(JSParseNode * node, Stream * f, int indent) {
621 switch (node->pn_type) {
622 case TOK_FUNCTION:
623 instrument_function(node, f, indent, FUNCTION_NORMAL);
624 break;
625 case TOK_LC:
626 assert(node->pn_arity == PN_LIST);
627 /*
628 Stream_write_string(f, "{\n");
629 */
630 for (struct JSParseNode * p = node->pn_u.list.head; p != NULL; p = p->pn_next) {
631 instrument_statement(p, f, indent);
632 }
633 /*
634 Stream_printf(f, "%*s", indent, "");
635 Stream_write_string(f, "}\n");
636 */
637 break;
638 case TOK_IF:
639 assert(node->pn_arity == PN_TERNARY);
640 Stream_printf(f, "%*s", indent, "");
641 Stream_write_string(f, "if (");
642 instrument_expression(node->pn_kid1, f);
643 Stream_write_string(f, ") {\n");
644 instrument_statement(node->pn_kid2, f, indent + 2);
645 Stream_printf(f, "%*s", indent, "");
646 Stream_write_string(f, "}\n");
647 if (node->pn_kid3) {
648 Stream_printf(f, "%*s", indent, "");
649 Stream_write_string(f, "else {\n");
650 instrument_statement(node->pn_kid3, f, indent + 2);
651 Stream_printf(f, "%*s", indent, "");
652 Stream_write_string(f, "}\n");
653 }
654 break;
655 case TOK_SWITCH:
656 assert(node->pn_arity == PN_BINARY);
657 Stream_printf(f, "%*s", indent, "");
658 Stream_write_string(f, "switch (");
659 instrument_expression(node->pn_left, f);
660 Stream_write_string(f, ") {\n");
661 for (struct JSParseNode * p = node->pn_right->pn_head; p != NULL; p = p->pn_next) {
662 Stream_printf(f, "%*s", indent, "");
663 switch (p->pn_type) {
664 case TOK_CASE:
665 Stream_write_string(f, "case ");
666 instrument_expression(p->pn_left, f);
667 Stream_write_string(f, ":\n");
668 break;
669 case TOK_DEFAULT:
670 Stream_write_string(f, "default:\n");
671 break;
672 default:
673 abort();
674 break;
675 }
676 instrument_statement(p->pn_right, f, indent + 2);
677 }
678 Stream_printf(f, "%*s", indent, "");
679 Stream_write_string(f, "}\n");
680 break;
681 case TOK_CASE:
682 case TOK_DEFAULT:
683 abort();
684 break;
685 case TOK_WHILE:
686 assert(node->pn_arity == PN_BINARY);
687 Stream_printf(f, "%*s", indent, "");
688 Stream_write_string(f, "while (");
689 instrument_expression(node->pn_left, f);
690 Stream_write_string(f, ") {\n");
691 instrument_statement(node->pn_right, f, indent + 2);
692 Stream_write_string(f, "}\n");
693 break;
694 case TOK_DO:
695 assert(node->pn_arity == PN_BINARY);
696 Stream_printf(f, "%*s", indent, "");
697 Stream_write_string(f, "do {\n");
698 instrument_statement(node->pn_left, f, indent + 2);
699 Stream_write_string(f, "}\n");
700 Stream_printf(f, "%*s", indent, "");
701 Stream_write_string(f, "while (");
702 instrument_expression(node->pn_right, f);
703 Stream_write_string(f, ");\n");
704 break;
705 case TOK_FOR:
706 assert(node->pn_arity == PN_BINARY);
707 Stream_printf(f, "%*s", indent, "");
708 Stream_write_string(f, "for (");
709 switch (node->pn_left->pn_type) {
710 case TOK_IN:
711 /* for/in */
712 assert(node->pn_left->pn_arity == PN_BINARY);
713 switch (node->pn_left->pn_left->pn_type) {
714 case TOK_VAR:
715 instrument_var_statement(node->pn_left->pn_left, f, 0);
716 break;
717 case TOK_NAME:
718 instrument_expression(node->pn_left->pn_left, f);
719 break;
720 default:
721 /* this is undocumented: for (x.value in y) */
722 instrument_expression(node->pn_left->pn_left, f);
723 break;
724 /*
725 default:
726 fprintf(stderr, "unexpected node type: %d\n", node->pn_left->pn_left->pn_type);
727 abort();
728 break;
729 */
730 }
731 Stream_write_string(f, " in ");
732 instrument_expression(node->pn_left->pn_right, f);
733 break;
734 case TOK_RESERVED:
735 /* for (;;) */
736 assert(node->pn_left->pn_arity == PN_TERNARY);
737 if (node->pn_left->pn_kid1) {
738 if (node->pn_left->pn_kid1->pn_type == TOK_VAR) {
739 instrument_var_statement(node->pn_left->pn_kid1, f, 0);
740 }
741 else {
742 instrument_expression(node->pn_left->pn_kid1, f);
743 }
744 }
745 Stream_write_string(f, ";");
746 if (node->pn_left->pn_kid2) {
747 Stream_write_char(f, ' ');
748 instrument_expression(node->pn_left->pn_kid2, f);
749 }
750 Stream_write_string(f, ";");
751 if (node->pn_left->pn_kid3) {
752 Stream_write_char(f, ' ');
753 instrument_expression(node->pn_left->pn_kid3, f);
754 }
755 break;
756 default:
757 abort();
758 break;
759 }
760 Stream_write_string(f, ") {\n");
761 instrument_statement(node->pn_right, f, indent + 2);
762 Stream_write_string(f, "}\n");
763 break;
764 case TOK_THROW:
765 assert(node->pn_arity == PN_UNARY);
766 Stream_printf(f, "%*s", indent, "");
767 Stream_write_string(f, "throw ");
768 instrument_expression(node->pn_u.unary.kid, f);
769 Stream_write_string(f, ";\n");
770 break;
771 case TOK_TRY:
772 Stream_printf(f, "%*s", indent, "");
773 Stream_write_string(f, "try {\n");
774 instrument_statement(node->pn_kid1, f, indent + 2);
775 Stream_printf(f, "%*s", indent, "");
776 Stream_write_string(f, "}\n");
777 {
778 for (JSParseNode * catch = node->pn_kid2; catch != NULL; catch = catch->pn_kid2) {
779 assert(catch->pn_type == TOK_CATCH);
780 Stream_printf(f, "%*s", indent, "");
781 Stream_write_string(f, "catch (");
782 assert(catch->pn_kid1->pn_arity == PN_NAME);
783 print_string_atom(catch->pn_kid1->pn_atom, f);
784 if (catch->pn_kid1->pn_expr) {
785 Stream_write_string(f, " if ");
786 instrument_expression(catch->pn_kid1->pn_expr, f);
787 }
788 Stream_write_string(f, ") {\n");
789 instrument_statement(catch->pn_kid3, f, indent + 2);
790 Stream_printf(f, "%*s", indent, "");
791 Stream_write_string(f, "}\n");
792 }
793 }
794 if (node->pn_kid3) {
795 Stream_printf(f, "%*s", indent, "");
796 Stream_write_string(f, "finally {\n");
797 instrument_statement(node->pn_kid3, f, indent + 2);
798 Stream_printf(f, "%*s", indent, "");
799 Stream_write_string(f, "}\n");
800 }
801 break;
802 case TOK_CATCH:
803 abort();
804 break;
805 case TOK_BREAK:
806 case TOK_CONTINUE:
807 assert(node->pn_arity == PN_NAME || node->pn_arity == PN_NULLARY);
808 Stream_printf(f, "%*s", indent, "");
809 Stream_write_string(f, node->pn_type == TOK_BREAK? "break": "continue");
810 JSAtom * atom = node->pn_u.name.atom;
811 if (atom != NULL) {
812 Stream_write_char(f, ' ');
813 print_string_atom(node->pn_atom, f);
814 }
815 Stream_write_string(f, ";\n");
816 break;
817 case TOK_WITH:
818 assert(node->pn_arity == PN_BINARY);
819 Stream_printf(f, "%*s", indent, "");
820 Stream_write_string(f, "with (");
821 instrument_expression(node->pn_left, f);
822 Stream_write_string(f, ") {\n");
823 instrument_statement(node->pn_right, f, indent + 2);
824 Stream_printf(f, "%*s", indent, "");
825 Stream_write_string(f, "}\n");
826 break;
827 case TOK_VAR:
828 instrument_var_statement(node, f, indent);
829 Stream_write_string(f, ";\n");
830 break;
831 case TOK_RETURN:
832 assert(node->pn_arity == PN_UNARY);
833 Stream_printf(f, "%*s", indent, "");
834 Stream_write_string(f, "return");
835 if (node->pn_kid != NULL) {
836 Stream_write_char(f, ' ');
837 instrument_expression(node->pn_kid, f);
838 }
839 Stream_write_string(f, ";\n");
840 break;
841 case TOK_SEMI:
842 assert(node->pn_arity == PN_UNARY);
843 Stream_printf(f, "%*s", indent, "");
844 if (node->pn_kid != NULL) {
845 instrument_expression(node->pn_kid, f);
846 }
847 Stream_write_string(f, ";\n");
848 break;
849 case TOK_COLON:
850 assert(node->pn_arity == PN_NAME);
851 /*
852 This one is tricky: can't output instrumentation between the label and the
853 statement it's supposed to label ...
854 */
855 Stream_printf(f, "%*s", indent < 2? 0: indent - 2, "");
856 print_string_atom(node->pn_atom, f);
857 Stream_write_string(f, ":\n");
858 /*
859 ... use output_statement instead of instrument_statement.
860 */
861 output_statement(node->pn_expr, f, indent);
862 break;
863 default:
864 fatal("unsupported node type in file %s: %d", file_id, node->pn_type);
865 }
866 }
867
868 /*
869 See <Statements> in jsparse.h.
870 TOK_FUNCTION is handled as a statement and as an expression.
871 TOK_EXPORT, TOK_IMPORT are not handled.
872 */
873 static void instrument_statement(JSParseNode * node, Stream * f, int indent) {
874 if (node->pn_type != TOK_LC) {
875 int line = node->pn_pos.begin.lineno;
876 /* the root node has line number 0 */
877 if (line != 0) {
878 Stream_printf(f, "%*s", indent, "");
879 Stream_printf(f, "_$jscoverage['%s'][%d]++;\n", file_id, line);
880 lines[line - 1] = 1;
881 }
882 }
883 output_statement(node, f, indent);
884 }
885
886 void jscoverage_instrument_js(const char * id, const char * encoding, Stream * input, Stream * output) {
887 file_id = id;
888
889 /* scan the javascript */
890 size_t num_characters = input->length;
891 jschar * base = NULL;
892 int result = jscoverage_bytes_to_characters(encoding, input->data, input->length, &base, &num_characters);
893 if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) {
894 fatal("encoding %s not supported in file %s", encoding, id);
895 }
896 else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) {
897 fatal("error decoding %s in file %s", encoding, id);
898 }
899 JSTokenStream * token_stream = js_NewTokenStream(context, base, num_characters, NULL, 1, NULL);
900 if (token_stream == NULL) {
901 fatal("cannot create token stream from file: %s", file_id);
902 }
903
904 /* parse the javascript */
905 JSParseNode * node = js_ParseTokenStream(context, global, token_stream);
906 if (node == NULL) {
907 fatal("parse error in file: %s", file_id);
908 }
909 int num_lines = node->pn_pos.end.lineno;
910 lines = xmalloc(num_lines);
911 for (int i = 0; i < num_lines; i++) {
912 lines[i] = 0;
913 }
914
915 /*
916 An instrumented JavaScript file has 3 sections:
917 1. initialization
918 2. instrumented source code
919 3. original source code
920 */
921
922 Stream * instrumented = Stream_new(0);
923 instrument_statement(node, instrumented, 0);
924
925 /* write line number info to the output */
926 Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n");
927 Stream_write_string(output, "if (! top._$jscoverage) {\n top._$jscoverage = {};\n}\n");
928 Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n");
929 Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id);
930 Stream_printf(output, " _$jscoverage['%s'] = [];\n", file_id);
931 for (int i = 0; i < num_lines; i++) {
932 if (lines[i]) {
933 Stream_printf(output, " _$jscoverage['%s'][%d] = 0;\n", file_id, i + 1);
934 }
935 }
936 Stream_write_string(output, "}\n");
937 free(lines);
938 lines = NULL;
939
940 /* copy the instrumented source code to the output */
941 Stream_write(output, instrumented->data, instrumented->length);
942 Stream_write_char(output, '\n');
943
944 /* conditionals */
945 bool has_conditionals = false;
946 size_t line_number = 0;
947 size_t i = 0;
948 while (i < num_characters) {
949 line_number++;
950 size_t line_start = i;
951 while (i < num_characters && base[i] != '\r' && base[i] != '\n') {
952 i++;
953 }
954 size_t line_end = i;
955 if (i < num_characters) {
956 if (base[i] == '\r') {
957 line_end = i;
958 i++;
959 if (i < num_characters && base[i] == '\n') {
960 i++;
961 }
962 }
963 else if (base[i] == '\n') {
964 line_end = i;
965 i++;
966 }
967 else {
968 abort();
969 }
970 }
971 char * line = js_DeflateString(context, base + line_start, line_end - line_start);
972 if (str_starts_with(line, "//#JSCOVERAGE_IF")) {
973 if (! has_conditionals) {
974 has_conditionals = true;
975 Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);
976 }
977 Stream_printf(output, "if (!%s) {\n", line + 16);
978 Stream_printf(output, " _$jscoverage['%s'].conditionals[%d] = ", file_id, line_number);
979 }
980 else if (str_starts_with(line, "//#JSCOVERAGE_ENDIF")) {
981 Stream_printf(output, "%d;\n", line_number);
982 Stream_printf(output, "}\n");
983 }
984 JS_free(context, line);
985 }
986
987 /* copy the original source to the output */
988 i = 0;
989 while (i < num_characters) {
990 Stream_write_string(output, "// ");
991 size_t line_start = i;
992 while (i < num_characters && base[i] != '\r' && base[i] != '\n') {
993 i++;
994 }
995
996 size_t line_end = i;
997 if (i < num_characters) {
998 if (base[i] == '\r') {
999 line_end = i;
1000 i++;
1001 if (i < num_characters && base[i] == '\n') {
1002 i++;
1003 }
1004 }
1005 else if (base[i] == '\n') {
1006 line_end = i;
1007 i++;
1008 }
1009 else {
1010 abort();
1011 }
1012 }
1013
1014 char * line = js_DeflateString(context, base + line_start, line_end - line_start);
1015 Stream_write_string(output, line);
1016 Stream_write_char(output, '\n');
1017 JS_free(context, line);
1018 }
1019
1020 Stream_delete(instrumented);
1021
1022 JS_free(context, base);
1023
1024 file_id = NULL;
1025 }
1026
1027 void jscoverage_copy_resources(const char * destination_directory) {
1028 copy_resource("jscoverage.html", destination_directory);
1029 copy_resource("jscoverage.css", destination_directory);
1030 copy_resource("jscoverage.js", destination_directory);
1031 copy_resource("jscoverage-ie.css", destination_directory);
1032 copy_resource("jscoverage-throbber.gif", destination_directory);
1033 copy_resource("jscoverage-sh_main.js", destination_directory);
1034 copy_resource("jscoverage-sh_javascript.js", destination_directory);
1035 copy_resource("jscoverage-sh_nedit.css", destination_directory);
1036 }
1037
1038 /*
1039 coverage reports
1040 */
1041
1042 struct FileCoverageList {
1043 FileCoverage * file_coverage;
1044 struct FileCoverageList * next;
1045 };
1046
1047 struct Coverage {
1048 JSHashTable * coverage_table;
1049 struct FileCoverageList * coverage_list;
1050 };
1051
1052 static int compare_strings(const void * p1, const void * p2) {
1053 return strcmp(p1, p2) == 0;
1054 }
1055
1056 Coverage * Coverage_new(void) {
1057 Coverage * result = xmalloc(sizeof(Coverage));
1058 result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL);
1059 if (result->coverage_table == NULL) {
1060 fatal("cannot create hash table");
1061 }
1062 result->coverage_list = NULL;
1063 return result;
1064 }
1065
1066 void Coverage_delete(Coverage * coverage) {
1067 JS_HashTableDestroy(coverage->coverage_table);
1068 struct FileCoverageList * p = coverage->coverage_list;
1069 while (p != NULL) {
1070 free(p->file_coverage->lines);
1071 free(p->file_coverage->source);
1072 free(p->file_coverage->id);
1073 free(p->file_coverage);
1074 struct FileCoverageList * q = p;
1075 p = p->next;
1076 free(q);
1077 }
1078 free(coverage);
1079 }
1080
1081 struct EnumeratorArg {
1082 CoverageForeachFunction f;
1083 void * p;
1084 };
1085
1086 static intN enumerator(JSHashEntry * entry, intN i, void * arg) {
1087 struct EnumeratorArg * enumerator_arg = arg;
1088 enumerator_arg->f(entry->value, i, enumerator_arg->p);
1089 return 0;
1090 }
1091
1092 void Coverage_foreach_file(Coverage * coverage, CoverageForeachFunction f, void * p) {
1093 struct EnumeratorArg enumerator_arg;
1094 enumerator_arg.f = f;
1095 enumerator_arg.p = p;
1096 JS_HashTableEnumerateEntries(coverage->coverage_table, enumerator, &enumerator_arg);
1097 }
1098
1099 int jscoverage_parse_json(Coverage * coverage, const uint8_t * json, size_t length) {
1100 jschar * base = js_InflateString(context, (char *) json, &length);
1101 if (base == NULL) {
1102 fatal("out of memory");
1103 }
1104
1105 jschar * parenthesized_json = xnew(jschar, addst(length, 2));
1106 parenthesized_json[0] = '(';
1107 memcpy(parenthesized_json + 1, base, mulst(length, sizeof(jschar)));
1108 parenthesized_json[length + 1] = ')';
1109
1110 JS_free(context, base);
1111
1112 JSTokenStream * token_stream = js_NewTokenStream(context, parenthesized_json, length + 2, NULL, 1, NULL);
1113 if (token_stream == NULL) {
1114 fatal("cannot create token stream");
1115 }
1116
1117 JSParseNode * root = js_ParseTokenStream(context, global, token_stream);
1118 free(parenthesized_json);
1119 if (root == NULL) {
1120 return -1;
1121 }
1122
1123 /* root node must be TOK_LC */
1124 if (root->pn_type != TOK_LC) {
1125 return -1;
1126 }
1127 JSParseNode * semi = root->pn_u.list.head;
1128
1129 /* the list must be TOK_SEMI and it must contain only one element */
1130 if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {
1131 return -1;
1132 }
1133 JSParseNode * parenthesized = semi->pn_kid;
1134
1135 /* this must be a parenthesized expression */
1136 if (parenthesized->pn_type != TOK_RP) {
1137 return -1;
1138 }
1139 JSParseNode * object = parenthesized->pn_kid;
1140
1141 /* this must be an object literal */
1142 if (object->pn_type != TOK_RC) {
1143 return -1;
1144 }
1145
1146 for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {
1147 /* every element of this list must be TOK_COLON */
1148 if (p->pn_type != TOK_COLON) {
1149 return -1;
1150 }
1151
1152 /* the key must be a string representing the file */
1153 JSParseNode * key = p->pn_left;
1154 if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {
1155 return -1;
1156 }
1157 char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));
1158
1159 /* the value must be an object literal OR an array */
1160 JSParseNode * value = p->pn_right;
1161 if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {
1162 return -1;
1163 }
1164
1165 JSParseNode * array = NULL;
1166 JSParseNode * source = NULL;
1167 if (value->pn_type == TOK_RB) {
1168 /* an array */
1169 array = value;
1170 }
1171 else if (value->pn_type == TOK_RC) {
1172 /* an object literal */
1173 if (value->pn_count != 2) {
1174 return -1;
1175 }
1176 for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {
1177 if (element->pn_type != TOK_COLON) {
1178 return -1;
1179 }
1180 JSParseNode * left = element->pn_left;
1181 if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {
1182 return -1;
1183 }
1184 const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));
1185 if (strcmp(s, "coverage") == 0) {
1186 array = element->pn_right;
1187 if (array->pn_type != TOK_RB) {
1188 return -1;
1189 }
1190 }
1191 else if (strcmp(s, "source") == 0) {
1192 source = element->pn_right;
1193 if (source->pn_type != TOK_STRING || ! ATOM_IS_STRING(source->pn_atom)) {
1194 return -1;
1195 }
1196 }
1197 else {
1198 return -1;
1199 }
1200 }
1201 }
1202 else {
1203 return -1;
1204 }
1205
1206 if (array == NULL) {
1207 return -1;
1208 }
1209
1210 /* look up the file in the coverage table */
1211 FileCoverage * file_coverage = JS_HashTableLookup(coverage->coverage_table, id_bytes);
1212 if (file_coverage == NULL) {
1213 /* not there: create a new one */
1214 char * id = xstrdup(id_bytes);
1215 file_coverage = xmalloc(sizeof(FileCoverage));
1216 file_coverage->id = id;
1217 file_coverage->num_lines = array->pn_count - 1;
1218 file_coverage->lines = xnew(int, array->pn_count);
1219 if (source == NULL) {
1220 file_coverage->source = NULL;
1221 }
1222 else {
1223 file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom)));
1224 }
1225
1226 /* set coverage for all lines */
1227 uint32 i = 0;
1228 for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1229 if (element->pn_type == TOK_NUMBER) {
1230 file_coverage->lines[i] = (int) element->pn_dval;
1231 }
1232 else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1233 file_coverage->lines[i] = -1;
1234 }
1235 else {
1236 return -1;
1237 }
1238 }
1239 assert(i == array->pn_count);
1240
1241 /* add to the hash table */
1242 JS_HashTableAdd(coverage->coverage_table, id, file_coverage);
1243 struct FileCoverageList * coverage_list = xmalloc(sizeof(struct FileCoverageList));
1244 coverage_list->file_coverage = file_coverage;
1245 coverage_list->next = coverage->coverage_list;
1246 coverage->coverage_list = coverage_list;
1247 }
1248 else {
1249 /* sanity check */
1250 assert(strcmp(file_coverage->id, id_bytes) == 0);
1251 if (file_coverage->num_lines != array->pn_count - 1) {
1252 return -2;
1253 }
1254
1255 /* merge the coverage */
1256 uint32 i = 0;
1257 for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1258 if (element->pn_type == TOK_NUMBER) {
1259 if (file_coverage->lines[i] == -1) {
1260 return -2;
1261 }
1262 file_coverage->lines[i] += (int) element->pn_dval;
1263 }
1264 else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1265 if (file_coverage->lines[i] != -1) {
1266 return -2;
1267 }
1268 }
1269 else {
1270 return -1;
1271 }
1272 }
1273 assert(i == array->pn_count);
1274
1275 /* if this JSON file has source, use it */
1276 if (file_coverage->source == NULL && source != NULL) {
1277 file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom)));
1278 }
1279 }
1280 }
1281
1282 return 0;
1283 }

  ViewVC Help
Powered by ViewVC 1.1.24