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

Annotation of /trunk/instrument-js.cpp

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.24