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

Annotation of /trunk/instrument-js.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 157 - (hide annotations)
Sat Sep 13 03:56:45 2008 UTC (11 years, 1 month ago) by siliconforks
File MIME type: text/plain
File size: 35394 byte(s)
Add support for conditional directives.

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

  ViewVC Help
Powered by ViewVC 1.1.24