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

Contents of /trunk/instrument-js.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 179 - (show annotations)
Sun Sep 21 18:35:21 2008 UTC (10 years, 7 months ago) by siliconforks
Original Path: trunk/instrument-js.c
File MIME type: text/plain
File size: 40629 byte(s)
Do source code highlighting during instrumentation.

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

  ViewVC Help
Powered by ViewVC 1.1.24