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

Annotation of /trunk/instrument-js.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 116 - (hide annotations)
Sat May 31 21:42:36 2008 UTC (11 years ago) by siliconforks
Original Path: trunk/instrument-js.c
File MIME type: text/plain
File size: 33619 byte(s)
Add jscoverage-server.

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

  ViewVC Help
Powered by ViewVC 1.1.24