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

Annotation of /trunk/instrument-js.c

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.24