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

Annotation of /trunk/instrument-js.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 169 - (hide annotations)
Mon Sep 15 16:22:41 2008 UTC (11 years, 2 months ago) by siliconforks
File MIME type: text/plain
File size: 35494 byte(s)
Fix compile error.

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 159 /* copy the instrumented source code to the output */
881     Stream_write(output, instrumented->data, instrumented->length);
882     Stream_write_char(output, '\n');
883    
884 siliconforks 157 /* conditionals */
885     bool has_conditionals = false;
886     size_t line_number = 0;
887     size_t i = 0;
888     while (i < input_length) {
889     line_number++;
890     size_t line_start = i;
891     while (i < input_length && base[i] != '\r' && base[i] != '\n') {
892     i++;
893     }
894     size_t line_end = i;
895     if (i < input_length) {
896     if (base[i] == '\r') {
897     line_end = i;
898     i++;
899     if (i < input_length && base[i] == '\n') {
900     i++;
901     }
902     }
903     else if (base[i] == '\n') {
904     line_end = i;
905     i++;
906     }
907     else {
908     abort();
909     }
910     }
911     char * line = js_DeflateString(context, base + line_start, line_end - line_start);
912     if (str_starts_with(line, "//#JSCOVERAGE_IF")) {
913     if (! has_conditionals) {
914     has_conditionals = true;
915     Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);
916     }
917 siliconforks 159 Stream_printf(output, "if (!%s) {\n", line + 16);
918     Stream_printf(output, " _$jscoverage['%s'].conditionals[%d] = ", file_id, line_number);
919 siliconforks 157 }
920     else if (str_starts_with(line, "//#JSCOVERAGE_ENDIF")) {
921 siliconforks 159 Stream_printf(output, "%d;\n", line_number);
922     Stream_printf(output, "}\n");
923 siliconforks 157 }
924     JS_free(context, line);
925     }
926    
927 siliconforks 95 /* copy the original source to the output */
928 siliconforks 157 i = 0;
929 siliconforks 95 while (i < input_length) {
930     Stream_write_string(output, "// ");
931     size_t line_start = i;
932     while (i < input_length && base[i] != '\r' && base[i] != '\n') {
933     i++;
934     }
935    
936     size_t line_end = i;
937     if (i < input_length) {
938     if (base[i] == '\r') {
939     line_end = i;
940     i++;
941     if (i < input_length && base[i] == '\n') {
942     i++;
943     }
944     }
945     else if (base[i] == '\n') {
946     line_end = i;
947     i++;
948     }
949     else {
950     abort();
951     }
952     }
953    
954     char * line = js_DeflateString(context, base + line_start, line_end - line_start);
955     Stream_write_string(output, line);
956     Stream_write_char(output, '\n');
957     JS_free(context, line);
958     }
959    
960 siliconforks 92 Stream_delete(instrumented);
961 siliconforks 2
962 siliconforks 92 JS_free(context, base);
963    
964 siliconforks 2 file_id = NULL;
965     }
966 siliconforks 116
967     void jscoverage_copy_resources(const char * destination_directory) {
968     copy_resource("jscoverage.html", destination_directory);
969     copy_resource("jscoverage.css", destination_directory);
970     copy_resource("jscoverage.js", destination_directory);
971 siliconforks 169 copy_resource("jscoverage-ie.css", destination_directory);
972 siliconforks 116 copy_resource("jscoverage-throbber.gif", destination_directory);
973     copy_resource("jscoverage-sh_main.js", destination_directory);
974     copy_resource("jscoverage-sh_javascript.js", destination_directory);
975     copy_resource("jscoverage-sh_nedit.css", destination_directory);
976     }
977    
978     /*
979     coverage reports
980     */
981    
982     struct FileCoverageList {
983     FileCoverage * file_coverage;
984     struct FileCoverageList * next;
985     };
986    
987     struct Coverage {
988     JSHashTable * coverage_table;
989     struct FileCoverageList * coverage_list;
990     };
991    
992     static int compare_strings(const void * p1, const void * p2) {
993     return strcmp(p1, p2) == 0;
994     }
995    
996     Coverage * Coverage_new(void) {
997     Coverage * result = xmalloc(sizeof(Coverage));
998     result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL);
999     if (result->coverage_table == NULL) {
1000     fatal("cannot create hash table");
1001     }
1002     result->coverage_list = NULL;
1003     return result;
1004     }
1005    
1006     void Coverage_delete(Coverage * coverage) {
1007     JS_HashTableDestroy(coverage->coverage_table);
1008     struct FileCoverageList * p = coverage->coverage_list;
1009     while (p != NULL) {
1010     free(p->file_coverage->lines);
1011     free(p->file_coverage->source);
1012     free(p->file_coverage->id);
1013     free(p->file_coverage);
1014     struct FileCoverageList * q = p;
1015     p = p->next;
1016     free(q);
1017     }
1018     free(coverage);
1019     }
1020    
1021     struct EnumeratorArg {
1022     CoverageForeachFunction f;
1023     void * p;
1024     };
1025    
1026     static intN enumerator(JSHashEntry * entry, intN i, void * arg) {
1027     struct EnumeratorArg * enumerator_arg = arg;
1028     enumerator_arg->f(entry->value, i, enumerator_arg->p);
1029     return 0;
1030     }
1031    
1032     void Coverage_foreach_file(Coverage * coverage, CoverageForeachFunction f, void * p) {
1033     struct EnumeratorArg enumerator_arg;
1034     enumerator_arg.f = f;
1035     enumerator_arg.p = p;
1036     JS_HashTableEnumerateEntries(coverage->coverage_table, enumerator, &enumerator_arg);
1037     }
1038    
1039     int jscoverage_parse_json(Coverage * coverage, const uint8_t * json, size_t length) {
1040     jschar * base = js_InflateString(context, (char *) json, &length);
1041     if (base == NULL) {
1042     fatal("out of memory");
1043     }
1044    
1045     jschar * parenthesized_json = xnew(jschar, addst(length, 2));
1046     parenthesized_json[0] = '(';
1047     memcpy(parenthesized_json + 1, base, mulst(length, sizeof(jschar)));
1048     parenthesized_json[length + 1] = ')';
1049    
1050     JS_free(context, base);
1051    
1052     JSTokenStream * token_stream = js_NewTokenStream(context, parenthesized_json, length + 2, NULL, 1, NULL);
1053     if (token_stream == NULL) {
1054     fatal("cannot create token stream");
1055     }
1056    
1057     JSParseNode * root = js_ParseTokenStream(context, global, token_stream);
1058     free(parenthesized_json);
1059     if (root == NULL) {
1060     return -1;
1061     }
1062    
1063     /* root node must be TOK_LC */
1064     if (root->pn_type != TOK_LC) {
1065     return -1;
1066     }
1067     JSParseNode * semi = root->pn_u.list.head;
1068    
1069     /* the list must be TOK_SEMI and it must contain only one element */
1070     if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {
1071     return -1;
1072     }
1073     JSParseNode * parenthesized = semi->pn_kid;
1074    
1075     /* this must be a parenthesized expression */
1076     if (parenthesized->pn_type != TOK_RP) {
1077     return -1;
1078     }
1079     JSParseNode * object = parenthesized->pn_kid;
1080    
1081     /* this must be an object literal */
1082     if (object->pn_type != TOK_RC) {
1083     return -1;
1084     }
1085    
1086     for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {
1087     /* every element of this list must be TOK_COLON */
1088     if (p->pn_type != TOK_COLON) {
1089     return -1;
1090     }
1091    
1092     /* the key must be a string representing the file */
1093     JSParseNode * key = p->pn_left;
1094     if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {
1095     return -1;
1096     }
1097     char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));
1098    
1099     /* the value must be an object literal OR an array */
1100     JSParseNode * value = p->pn_right;
1101     if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {
1102     return -1;
1103     }
1104    
1105     JSParseNode * array = NULL;
1106     JSParseNode * source = NULL;
1107     if (value->pn_type == TOK_RB) {
1108     /* an array */
1109     array = value;
1110     }
1111     else if (value->pn_type == TOK_RC) {
1112     /* an object literal */
1113     if (value->pn_count != 2) {
1114     return -1;
1115     }
1116     for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {
1117     if (element->pn_type != TOK_COLON) {
1118     return -1;
1119     }
1120     JSParseNode * left = element->pn_left;
1121     if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {
1122     return -1;
1123     }
1124     const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));
1125     if (strcmp(s, "coverage") == 0) {
1126     array = element->pn_right;
1127     if (array->pn_type != TOK_RB) {
1128     return -1;
1129     }
1130     }
1131     else if (strcmp(s, "source") == 0) {
1132     source = element->pn_right;
1133     if (source->pn_type != TOK_STRING || ! ATOM_IS_STRING(source->pn_atom)) {
1134     return -1;
1135     }
1136     }
1137     else {
1138     return -1;
1139     }
1140     }
1141     }
1142     else {
1143     return -1;
1144     }
1145    
1146     if (array == NULL) {
1147     return -1;
1148     }
1149    
1150     /* look up the file in the coverage table */
1151     FileCoverage * file_coverage = JS_HashTableLookup(coverage->coverage_table, id_bytes);
1152     if (file_coverage == NULL) {
1153     /* not there: create a new one */
1154     char * id = xstrdup(id_bytes);
1155     file_coverage = xmalloc(sizeof(FileCoverage));
1156     file_coverage->id = id;
1157     file_coverage->num_lines = array->pn_count - 1;
1158     file_coverage->lines = xnew(int, array->pn_count);
1159     if (source == NULL) {
1160     file_coverage->source = NULL;
1161     }
1162     else {
1163     file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom)));
1164     }
1165    
1166     /* set coverage for all lines */
1167     uint32 i = 0;
1168     for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1169     if (element->pn_type == TOK_NUMBER) {
1170     file_coverage->lines[i] = (int) element->pn_dval;
1171     }
1172     else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1173     file_coverage->lines[i] = -1;
1174     }
1175     else {
1176     return -1;
1177     }
1178     }
1179     assert(i == array->pn_count);
1180    
1181     /* add to the hash table */
1182     JS_HashTableAdd(coverage->coverage_table, id, file_coverage);
1183     struct FileCoverageList * coverage_list = xmalloc(sizeof(struct FileCoverageList));
1184     coverage_list->file_coverage = file_coverage;
1185     coverage_list->next = coverage->coverage_list;
1186     coverage->coverage_list = coverage_list;
1187     }
1188     else {
1189     /* sanity check */
1190     assert(strcmp(file_coverage->id, id_bytes) == 0);
1191     if (file_coverage->num_lines != array->pn_count - 1) {
1192     return -2;
1193     }
1194    
1195     /* merge the coverage */
1196     uint32 i = 0;
1197     for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1198     if (element->pn_type == TOK_NUMBER) {
1199     if (file_coverage->lines[i] == -1) {
1200     return -2;
1201     }
1202     file_coverage->lines[i] += (int) element->pn_dval;
1203     }
1204     else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1205     if (file_coverage->lines[i] != -1) {
1206     return -2;
1207     }
1208     }
1209     else {
1210     return -1;
1211     }
1212     }
1213     assert(i == array->pn_count);
1214    
1215     /* if this JSON file has source, use it */
1216     if (file_coverage->source == NULL && source != NULL) {
1217     file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom)));
1218     }
1219     }
1220     }
1221    
1222     return 0;
1223     }

  ViewVC Help
Powered by ViewVC 1.1.24