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

Contents of /trunk/instrument-js.c

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.24