1 |
/* |
/* |
2 |
instrument-js.c - JavaScript instrumentation routines |
instrument-js.cpp - JavaScript instrumentation routines |
3 |
Copyright (C) 2007, 2008 siliconforks.com |
Copyright (C) 2007, 2008, 2009 siliconforks.com |
4 |
|
|
5 |
This program is free software; you can redistribute it and/or modify |
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 |
it under the terms of the GNU General Public License as published by |
22 |
#include "instrument-js.h" |
#include "instrument-js.h" |
23 |
|
|
24 |
#include <assert.h> |
#include <assert.h> |
25 |
|
#include <math.h> |
26 |
#include <stdlib.h> |
#include <stdlib.h> |
27 |
#include <string.h> |
#include <string.h> |
28 |
|
|
29 |
#include <jsapi.h> |
#include <jsapi.h> |
30 |
#include <jsarena.h> |
#include <jsarena.h> |
31 |
#include <jsatom.h> |
#include <jsatom.h> |
32 |
|
#include <jsemit.h> |
33 |
#include <jsexn.h> |
#include <jsexn.h> |
34 |
#include <jsfun.h> |
#include <jsfun.h> |
35 |
#include <jsinterp.h> |
#include <jsinterp.h> |
53 |
struct IfDirective * next; |
struct IfDirective * next; |
54 |
}; |
}; |
55 |
|
|
56 |
|
enum JSCoverageMode jscoverage_mode = JSCOVERAGE_NORMAL; |
57 |
|
|
58 |
static bool * exclusive_directives = NULL; |
static bool * exclusive_directives = NULL; |
59 |
|
|
60 |
static JSRuntime * runtime = NULL; |
static JSRuntime * runtime = NULL; |
71 |
static uint16_t num_lines = 0; |
static uint16_t num_lines = 0; |
72 |
|
|
73 |
void jscoverage_set_js_version(const char * version) { |
void jscoverage_set_js_version(const char * version) { |
74 |
js_version = atoi(version); |
js_version = JS_StringToVersion(version); |
75 |
|
if (js_version != JSVERSION_UNKNOWN) { |
76 |
|
return; |
77 |
|
} |
78 |
|
|
79 |
|
char * end; |
80 |
|
js_version = (JSVersion) strtol(version, &end, 10); |
81 |
|
if ((size_t) (end - version) != strlen(version)) { |
82 |
|
fatal("invalid version: %s", version); |
83 |
|
} |
84 |
} |
} |
85 |
|
|
86 |
void jscoverage_init(void) { |
void jscoverage_init(void) { |
268 |
} |
} |
269 |
} |
} |
270 |
|
|
271 |
static void instrument_expression(JSParseNode * node, Stream * f); |
static void output_expression(JSParseNode * node, Stream * f, bool parenthesize_object_literals); |
272 |
static void instrument_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if); |
static void instrument_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if); |
273 |
static void output_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if); |
static void output_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if); |
274 |
|
|
285 |
Stream_write_string(f, "each "); |
Stream_write_string(f, "each "); |
286 |
} |
} |
287 |
Stream_write_char(f, '('); |
Stream_write_char(f, '('); |
288 |
instrument_expression(node->pn_left, f); |
output_expression(node->pn_left, f, false); |
289 |
Stream_write_char(f, ')'); |
Stream_write_char(f, ')'); |
290 |
} |
} |
291 |
|
|
292 |
|
static void output_array_comprehension_or_generator_expression(JSParseNode * node, Stream * f) { |
293 |
|
assert(node->pn_type == TOK_LEXICALSCOPE); |
294 |
|
assert(node->pn_arity == PN_NAME); |
295 |
|
JSParseNode * for_node = node->pn_expr; |
296 |
|
assert(for_node->pn_type == TOK_FOR); |
297 |
|
assert(for_node->pn_arity == PN_BINARY); |
298 |
|
JSParseNode * p = for_node; |
299 |
|
while (p->pn_type == TOK_FOR) { |
300 |
|
p = p->pn_right; |
301 |
|
} |
302 |
|
JSParseNode * if_node = NULL; |
303 |
|
if (p->pn_type == TOK_IF) { |
304 |
|
if_node = p; |
305 |
|
assert(if_node->pn_arity == PN_TERNARY); |
306 |
|
p = if_node->pn_kid2; |
307 |
|
} |
308 |
|
switch (p->pn_arity) { |
309 |
|
case PN_UNARY: |
310 |
|
p = p->pn_kid; |
311 |
|
if (p->pn_type == TOK_YIELD) { |
312 |
|
/* for generator expressions */ |
313 |
|
p = p->pn_kid; |
314 |
|
} |
315 |
|
output_expression(p, f, false); |
316 |
|
break; |
317 |
|
case PN_LIST: |
318 |
|
/* |
319 |
|
When the array comprehension contains "if (0)", it will be optimized away and |
320 |
|
the result will be an empty TOK_LC list. |
321 |
|
*/ |
322 |
|
assert(p->pn_type == TOK_LC); |
323 |
|
assert(p->pn_head == NULL); |
324 |
|
/* the "1" is arbitrary (since the list is empty) */ |
325 |
|
Stream_write_char(f, '1'); |
326 |
|
break; |
327 |
|
default: |
328 |
|
abort(); |
329 |
|
break; |
330 |
|
} |
331 |
|
|
332 |
|
p = for_node; |
333 |
|
while (p->pn_type == TOK_FOR) { |
334 |
|
Stream_write_char(f, ' '); |
335 |
|
output_for_in(p, f); |
336 |
|
p = p->pn_right; |
337 |
|
} |
338 |
|
if (p->pn_type == TOK_LC) { |
339 |
|
/* this is the optimized-away "if (0)" */ |
340 |
|
Stream_write_string(f, " if (0)"); |
341 |
|
} |
342 |
|
else if (if_node) { |
343 |
|
Stream_write_string(f, " if ("); |
344 |
|
output_expression(if_node->pn_kid1, f, false); |
345 |
|
Stream_write_char(f, ')'); |
346 |
|
} |
347 |
|
} |
348 |
|
|
349 |
static void instrument_function(JSParseNode * node, Stream * f, int indent, enum FunctionType type) { |
static void instrument_function(JSParseNode * node, Stream * f, int indent, enum FunctionType type) { |
350 |
assert(node->pn_type == TOK_FUNCTION); |
assert(node->pn_type == TOK_FUNCTION); |
351 |
assert(node->pn_arity == PN_FUNC); |
assert(node->pn_arity == PN_FUNC); |
356 |
assert(object == &function->object); |
assert(object == &function->object); |
357 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
358 |
if (type == FUNCTION_NORMAL) { |
if (type == FUNCTION_NORMAL) { |
359 |
Stream_write_string(f, "function"); |
Stream_write_string(f, "function "); |
360 |
} |
} |
361 |
|
|
362 |
/* function name */ |
/* function name */ |
363 |
if (function->atom) { |
if (function->atom) { |
|
Stream_write_char(f, ' '); |
|
364 |
print_string_atom(function->atom, f); |
print_string_atom(function->atom, f); |
365 |
} |
} |
366 |
|
|
368 |
function parameters - see JS_DecompileFunction in jsapi.cpp, which calls |
function parameters - see JS_DecompileFunction in jsapi.cpp, which calls |
369 |
js_DecompileFunction in jsopcode.cpp |
js_DecompileFunction in jsopcode.cpp |
370 |
*/ |
*/ |
371 |
Stream_write_string(f, "("); |
Stream_write_char(f, '('); |
372 |
JSArenaPool pool; |
JSArenaPool pool; |
373 |
JS_INIT_ARENA_POOL(&pool, "instrument_function", 256, 1, &context->scriptStackQuota); |
JS_INIT_ARENA_POOL(&pool, "instrument_function", 256, 1, &context->scriptStackQuota); |
374 |
jsuword * local_names = NULL; |
jsuword * local_names = NULL; |
378 |
fatal("out of memory"); |
fatal("out of memory"); |
379 |
} |
} |
380 |
} |
} |
381 |
|
bool destructuring = false; |
382 |
for (int i = 0; i < function->nargs; i++) { |
for (int i = 0; i < function->nargs; i++) { |
383 |
if (i > 0) { |
if (i > 0) { |
384 |
Stream_write_string(f, ", "); |
Stream_write_string(f, ", "); |
385 |
} |
} |
386 |
JSAtom * param = JS_LOCAL_NAME_TO_ATOM(local_names[i]); |
JSAtom * param = JS_LOCAL_NAME_TO_ATOM(local_names[i]); |
387 |
print_string_atom(param, f); |
if (param == NULL) { |
388 |
|
destructuring = true; |
389 |
|
JSParseNode * expression = NULL; |
390 |
|
assert(node->pn_body->pn_type == TOK_LC || node->pn_body->pn_type == TOK_SEQ); |
391 |
|
JSParseNode * semi = node->pn_body->pn_head; |
392 |
|
assert(semi->pn_type == TOK_SEMI); |
393 |
|
JSParseNode * comma = semi->pn_kid; |
394 |
|
assert(comma->pn_type == TOK_COMMA); |
395 |
|
for (JSParseNode * p = comma->pn_head; p != NULL; p = p->pn_next) { |
396 |
|
assert(p->pn_type == TOK_ASSIGN); |
397 |
|
JSParseNode * rhs = p->pn_right; |
398 |
|
assert(JSSTRING_LENGTH(ATOM_TO_STRING(rhs->pn_atom)) == 0); |
399 |
|
if (rhs->pn_slot == i) { |
400 |
|
expression = p->pn_left; |
401 |
|
break; |
402 |
|
} |
403 |
|
} |
404 |
|
assert(expression != NULL); |
405 |
|
output_expression(expression, f, false); |
406 |
|
} |
407 |
|
else { |
408 |
|
print_string_atom(param, f); |
409 |
|
} |
410 |
} |
} |
411 |
JS_FinishArenaPool(&pool); |
JS_FinishArenaPool(&pool); |
412 |
Stream_write_string(f, ") {\n"); |
Stream_write_string(f, ") {\n"); |
413 |
|
|
414 |
/* function body */ |
/* function body */ |
415 |
if (function->flags & JSFUN_EXPR_CLOSURE) { |
if (function->flags & JSFUN_EXPR_CLOSURE) { |
416 |
/* expression closure */ |
/* expression closure - use output_statement instead of instrument_statement */ |
417 |
output_statement(node->pn_body, f, indent + 2, false); |
if (node->pn_body->pn_type == TOK_SEQ) { |
418 |
|
assert(node->pn_body->pn_arity == PN_LIST); |
419 |
|
assert(node->pn_body->pn_count == 2); |
420 |
|
output_statement(node->pn_body->pn_head->pn_next, f, indent + 2, false); |
421 |
|
} |
422 |
|
else { |
423 |
|
output_statement(node->pn_body, f, indent + 2, false); |
424 |
|
} |
425 |
} |
} |
426 |
else { |
else { |
427 |
instrument_statement(node->pn_body, f, indent + 2, false); |
assert(node->pn_body->pn_type == TOK_LC); |
428 |
|
assert(node->pn_body->pn_arity == PN_LIST); |
429 |
|
JSParseNode * p = node->pn_body->pn_head; |
430 |
|
if (destructuring) { |
431 |
|
p = p->pn_next; |
432 |
|
} |
433 |
|
for (; p != NULL; p = p->pn_next) { |
434 |
|
instrument_statement(p, f, indent + 2, false); |
435 |
|
} |
436 |
} |
} |
437 |
|
|
438 |
Stream_write_string(f, "}\n"); |
Stream_write_char(f, '}'); |
439 |
} |
} |
440 |
|
|
441 |
static void instrument_function_call(JSParseNode * node, Stream * f) { |
static void instrument_function_call(JSParseNode * node, Stream * f) { |
442 |
if (node->pn_head->pn_type == TOK_FUNCTION) { |
JSParseNode * function_node = node->pn_head; |
443 |
/* it's a generator expression */ |
if (function_node->pn_type == TOK_FUNCTION) { |
444 |
JSParseNode * function_node = node->pn_head; |
JSObject * object = function_node->pn_funpob->object; |
445 |
JSParseNode * lexical_scope_node = function_node->pn_body; |
assert(JS_ObjectIsFunction(context, object)); |
446 |
assert(lexical_scope_node->pn_type == TOK_LEXICALSCOPE); |
JSFunction * function = (JSFunction *) JS_GetPrivate(context, object); |
447 |
assert(lexical_scope_node->pn_arity == PN_NAME); |
assert(function); |
448 |
JSParseNode * for_node = lexical_scope_node->pn_body; |
assert(object == &function->object); |
449 |
assert(for_node->pn_type == TOK_FOR); |
|
450 |
assert(for_node->pn_arity == PN_BINARY); |
if (function_node->pn_flags & TCF_GENEXP_LAMBDA) { |
451 |
JSParseNode * if_node = NULL; |
/* it's a generator expression */ |
452 |
JSParseNode * semi_node; |
Stream_write_char(f, '('); |
453 |
switch (for_node->pn_right->pn_type) { |
output_array_comprehension_or_generator_expression(function_node->pn_body, f); |
|
case TOK_SEMI: |
|
|
semi_node = for_node->pn_right; |
|
|
break; |
|
|
case TOK_IF: |
|
|
if_node = for_node->pn_right; |
|
|
assert(if_node->pn_arity == PN_TERNARY); |
|
|
semi_node = if_node->pn_kid2; |
|
|
assert(semi_node->pn_type == TOK_SEMI); |
|
|
break; |
|
|
default: |
|
|
abort(); |
|
|
break; |
|
|
} |
|
|
assert(semi_node->pn_arity == PN_UNARY); |
|
|
JSParseNode * yield_node = semi_node->pn_kid; |
|
|
assert(yield_node->pn_type == TOK_YIELD); |
|
|
Stream_write_char(f, '('); |
|
|
instrument_expression(yield_node->pn_kid, f); |
|
|
Stream_write_char(f, ' '); |
|
|
output_for_in(for_node, f); |
|
|
if (if_node) { |
|
|
Stream_write_string(f, " if ("); |
|
|
instrument_expression(if_node->pn_kid1, f); |
|
454 |
Stream_write_char(f, ')'); |
Stream_write_char(f, ')'); |
455 |
|
return; |
456 |
} |
} |
|
Stream_write_char(f, ')'); |
|
457 |
} |
} |
458 |
else { |
output_expression(function_node, f, false); |
459 |
instrument_expression(node->pn_head, f); |
Stream_write_char(f, '('); |
460 |
Stream_write_char(f, '('); |
for (struct JSParseNode * p = function_node->pn_next; p != NULL; p = p->pn_next) { |
461 |
for (struct JSParseNode * p = node->pn_head->pn_next; p != NULL; p = p->pn_next) { |
if (p != node->pn_head->pn_next) { |
462 |
if (p != node->pn_head->pn_next) { |
Stream_write_string(f, ", "); |
|
Stream_write_string(f, ", "); |
|
|
} |
|
|
instrument_expression(p, f); |
|
463 |
} |
} |
464 |
Stream_write_char(f, ')'); |
output_expression(p, f, false); |
465 |
} |
} |
466 |
|
Stream_write_char(f, ')'); |
467 |
} |
} |
468 |
|
|
469 |
static void instrument_declarations(JSParseNode * list, Stream * f) { |
static void instrument_declarations(JSParseNode * list, Stream * f) { |
470 |
assert(list->pn_arity == PN_LIST); |
assert(list->pn_arity == PN_LIST); |
471 |
for (JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) { |
for (JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) { |
472 |
switch (p->pn_type) { |
if (p != list->pn_head) { |
473 |
case TOK_NAME: |
Stream_write_string(f, ", "); |
|
assert(p->pn_arity == PN_NAME); |
|
|
if (p != list->pn_head) { |
|
|
Stream_write_string(f, ", "); |
|
|
} |
|
|
print_string_atom(p->pn_atom, f); |
|
|
if (p->pn_expr != NULL) { |
|
|
Stream_write_string(f, " = "); |
|
|
instrument_expression(p->pn_expr, f); |
|
|
} |
|
|
break; |
|
|
case TOK_ASSIGN: |
|
|
case TOK_RB: |
|
|
case TOK_RC: |
|
|
/* destructuring */ |
|
|
instrument_expression(p, f); |
|
|
break; |
|
|
default: |
|
|
abort(); |
|
|
break; |
|
474 |
} |
} |
475 |
|
output_expression(p, f, false); |
476 |
} |
} |
477 |
} |
} |
478 |
|
|
489 |
TOK_INSTANCEOF binary |
TOK_INSTANCEOF binary |
490 |
TOK_IN binary |
TOK_IN binary |
491 |
*/ |
*/ |
492 |
static void instrument_expression(JSParseNode * node, Stream * f) { |
static void output_expression(JSParseNode * node, Stream * f, bool parenthesize_object_literals) { |
493 |
switch (node->pn_type) { |
switch (node->pn_type) { |
494 |
case TOK_FUNCTION: |
case TOK_FUNCTION: |
495 |
|
Stream_write_char(f, '('); |
496 |
instrument_function(node, f, 0, FUNCTION_NORMAL); |
instrument_function(node, f, 0, FUNCTION_NORMAL); |
497 |
|
Stream_write_char(f, ')'); |
498 |
break; |
break; |
499 |
case TOK_COMMA: |
case TOK_COMMA: |
500 |
for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) { |
for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) { |
501 |
if (p != node->pn_head) { |
if (p != node->pn_head) { |
502 |
Stream_write_string(f, ", "); |
Stream_write_string(f, ", "); |
503 |
} |
} |
504 |
instrument_expression(p, f); |
output_expression(p, f, parenthesize_object_literals); |
505 |
} |
} |
506 |
break; |
break; |
507 |
case TOK_ASSIGN: |
case TOK_ASSIGN: |
508 |
instrument_expression(node->pn_left, f); |
output_expression(node->pn_left, f, parenthesize_object_literals); |
509 |
Stream_write_char(f, ' '); |
Stream_write_char(f, ' '); |
510 |
switch (node->pn_op) { |
switch (node->pn_op) { |
511 |
case JSOP_ADD: |
case JSOP_ADD: |
526 |
break; |
break; |
527 |
} |
} |
528 |
Stream_write_string(f, "= "); |
Stream_write_string(f, "= "); |
529 |
instrument_expression(node->pn_right, f); |
output_expression(node->pn_right, f, false); |
530 |
break; |
break; |
531 |
case TOK_HOOK: |
case TOK_HOOK: |
532 |
instrument_expression(node->pn_kid1, f); |
output_expression(node->pn_kid1, f, parenthesize_object_literals); |
533 |
Stream_write_string(f, "? "); |
Stream_write_string(f, "? "); |
534 |
instrument_expression(node->pn_kid2, f); |
output_expression(node->pn_kid2, f, false); |
535 |
Stream_write_string(f, ": "); |
Stream_write_string(f, ": "); |
536 |
instrument_expression(node->pn_kid3, f); |
output_expression(node->pn_kid3, f, false); |
537 |
break; |
break; |
538 |
case TOK_OR: |
case TOK_OR: |
539 |
case TOK_AND: |
case TOK_AND: |
549 |
case TOK_DIVOP: |
case TOK_DIVOP: |
550 |
switch (node->pn_arity) { |
switch (node->pn_arity) { |
551 |
case PN_BINARY: |
case PN_BINARY: |
552 |
instrument_expression(node->pn_left, f); |
output_expression(node->pn_left, f, parenthesize_object_literals); |
553 |
Stream_printf(f, " %s ", get_op(node->pn_op)); |
Stream_printf(f, " %s ", get_op(node->pn_op)); |
554 |
instrument_expression(node->pn_right, f); |
output_expression(node->pn_right, f, false); |
555 |
break; |
break; |
556 |
case PN_LIST: |
case PN_LIST: |
557 |
for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) { |
for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) { |
558 |
if (p != node->pn_head) { |
if (p == node->pn_head) { |
559 |
|
output_expression(p, f, parenthesize_object_literals); |
560 |
|
} |
561 |
|
else { |
562 |
Stream_printf(f, " %s ", get_op(node->pn_op)); |
Stream_printf(f, " %s ", get_op(node->pn_op)); |
563 |
|
output_expression(p, f, false); |
564 |
} |
} |
|
instrument_expression(p, f); |
|
565 |
} |
} |
566 |
break; |
break; |
567 |
default: |
default: |
571 |
case TOK_UNARYOP: |
case TOK_UNARYOP: |
572 |
switch (node->pn_op) { |
switch (node->pn_op) { |
573 |
case JSOP_NEG: |
case JSOP_NEG: |
574 |
Stream_write_char(f, '-'); |
Stream_write_string(f, "- "); |
575 |
instrument_expression(node->pn_kid, f); |
output_expression(node->pn_kid, f, false); |
576 |
break; |
break; |
577 |
case JSOP_POS: |
case JSOP_POS: |
578 |
Stream_write_char(f, '+'); |
Stream_write_string(f, "+ "); |
579 |
instrument_expression(node->pn_kid, f); |
output_expression(node->pn_kid, f, false); |
580 |
break; |
break; |
581 |
case JSOP_NOT: |
case JSOP_NOT: |
582 |
Stream_write_char(f, '!'); |
Stream_write_string(f, "! "); |
583 |
instrument_expression(node->pn_kid, f); |
output_expression(node->pn_kid, f, false); |
584 |
break; |
break; |
585 |
case JSOP_BITNOT: |
case JSOP_BITNOT: |
586 |
Stream_write_char(f, '~'); |
Stream_write_string(f, "~ "); |
587 |
instrument_expression(node->pn_kid, f); |
output_expression(node->pn_kid, f, false); |
588 |
break; |
break; |
589 |
case JSOP_TYPEOF: |
case JSOP_TYPEOF: |
590 |
Stream_write_string(f, "typeof "); |
Stream_write_string(f, "typeof "); |
591 |
instrument_expression(node->pn_kid, f); |
output_expression(node->pn_kid, f, false); |
592 |
break; |
break; |
593 |
case JSOP_VOID: |
case JSOP_VOID: |
594 |
Stream_write_string(f, "void "); |
Stream_write_string(f, "void "); |
595 |
instrument_expression(node->pn_kid, f); |
output_expression(node->pn_kid, f, false); |
596 |
break; |
break; |
597 |
default: |
default: |
598 |
abort(); |
fatal_source(file_id, node->pn_pos.begin.lineno, "unknown operator (%d)", node->pn_op); |
599 |
break; |
break; |
600 |
} |
} |
601 |
break; |
break; |
609 |
case JSOP_INCPROP: |
case JSOP_INCPROP: |
610 |
case JSOP_INCELEM: |
case JSOP_INCELEM: |
611 |
Stream_write_string(f, "++"); |
Stream_write_string(f, "++"); |
612 |
instrument_expression(node->pn_kid, f); |
output_expression(node->pn_kid, f, false); |
613 |
break; |
break; |
614 |
case JSOP_DECNAME: |
case JSOP_DECNAME: |
615 |
case JSOP_DECPROP: |
case JSOP_DECPROP: |
616 |
case JSOP_DECELEM: |
case JSOP_DECELEM: |
617 |
Stream_write_string(f, "--"); |
Stream_write_string(f, "--"); |
618 |
instrument_expression(node->pn_kid, f); |
output_expression(node->pn_kid, f, false); |
619 |
break; |
break; |
620 |
case JSOP_NAMEINC: |
case JSOP_NAMEINC: |
621 |
case JSOP_PROPINC: |
case JSOP_PROPINC: |
622 |
case JSOP_ELEMINC: |
case JSOP_ELEMINC: |
623 |
instrument_expression(node->pn_kid, f); |
output_expression(node->pn_kid, f, parenthesize_object_literals); |
624 |
Stream_write_string(f, "++"); |
Stream_write_string(f, "++"); |
625 |
break; |
break; |
626 |
case JSOP_NAMEDEC: |
case JSOP_NAMEDEC: |
627 |
case JSOP_PROPDEC: |
case JSOP_PROPDEC: |
628 |
case JSOP_ELEMDEC: |
case JSOP_ELEMDEC: |
629 |
instrument_expression(node->pn_kid, f); |
output_expression(node->pn_kid, f, parenthesize_object_literals); |
630 |
Stream_write_string(f, "--"); |
Stream_write_string(f, "--"); |
631 |
break; |
break; |
632 |
default: |
default: |
640 |
break; |
break; |
641 |
case TOK_DELETE: |
case TOK_DELETE: |
642 |
Stream_write_string(f, "delete "); |
Stream_write_string(f, "delete "); |
643 |
instrument_expression(node->pn_kid, f); |
output_expression(node->pn_kid, f, false); |
644 |
break; |
break; |
645 |
case TOK_DOT: |
case TOK_DOT: |
646 |
|
/* numeric literals must be parenthesized */ |
647 |
|
switch (node->pn_expr->pn_type) { |
648 |
|
case TOK_NUMBER: |
649 |
|
Stream_write_char(f, '('); |
650 |
|
output_expression(node->pn_expr, f, false); |
651 |
|
Stream_write_char(f, ')'); |
652 |
|
break; |
653 |
|
default: |
654 |
|
output_expression(node->pn_expr, f, true); |
655 |
|
break; |
656 |
|
} |
657 |
/* |
/* |
658 |
This may have originally been x['foo-bar']. Because the string 'foo-bar' |
This may have originally been x['foo-bar']. Because the string 'foo-bar' |
659 |
contains illegal characters, we have to use the subscript syntax instead of |
contains illegal characters, we have to use the subscript syntax instead of |
660 |
the dot syntax. |
the dot syntax. |
661 |
*/ |
*/ |
|
instrument_expression(node->pn_expr, f); |
|
662 |
assert(ATOM_IS_STRING(node->pn_atom)); |
assert(ATOM_IS_STRING(node->pn_atom)); |
663 |
{ |
{ |
664 |
JSString * s = ATOM_TO_STRING(node->pn_atom); |
JSString * s = ATOM_TO_STRING(node->pn_atom); |
687 |
} |
} |
688 |
break; |
break; |
689 |
case TOK_LB: |
case TOK_LB: |
690 |
instrument_expression(node->pn_left, f); |
output_expression(node->pn_left, f, false); |
691 |
Stream_write_char(f, '['); |
Stream_write_char(f, '['); |
692 |
instrument_expression(node->pn_right, f); |
output_expression(node->pn_right, f, false); |
693 |
Stream_write_char(f, ']'); |
Stream_write_char(f, ']'); |
694 |
break; |
break; |
695 |
case TOK_LP: |
case TOK_LP: |
703 |
} |
} |
704 |
/* TOK_COMMA is a special case: a hole in the array */ |
/* TOK_COMMA is a special case: a hole in the array */ |
705 |
if (p->pn_type != TOK_COMMA) { |
if (p->pn_type != TOK_COMMA) { |
706 |
instrument_expression(p, f); |
output_expression(p, f, false); |
707 |
} |
} |
708 |
} |
} |
709 |
if (node->pn_extra == PNX_ENDCOMMA) { |
if (node->pn_extra == PNX_ENDCOMMA) { |
712 |
Stream_write_char(f, ']'); |
Stream_write_char(f, ']'); |
713 |
break; |
break; |
714 |
case TOK_RC: |
case TOK_RC: |
715 |
|
if (parenthesize_object_literals) { |
716 |
|
Stream_write_char(f, '('); |
717 |
|
} |
718 |
Stream_write_char(f, '{'); |
Stream_write_char(f, '{'); |
719 |
for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) { |
for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) { |
720 |
assert(p->pn_type == TOK_COLON); |
if (p->pn_type != TOK_COLON) { |
721 |
|
fatal_source(file_id, p->pn_pos.begin.lineno, "unsupported node type (%d)", p->pn_type); |
722 |
|
} |
723 |
if (p != node->pn_head) { |
if (p != node->pn_head) { |
724 |
Stream_write_string(f, ", "); |
Stream_write_string(f, ", "); |
725 |
} |
} |
734 |
else { |
else { |
735 |
Stream_write_string(f, "set "); |
Stream_write_string(f, "set "); |
736 |
} |
} |
737 |
instrument_expression(p->pn_left, f); |
output_expression(p->pn_left, f, false); |
738 |
|
Stream_write_char(f, ' '); |
739 |
if (p->pn_right->pn_type != TOK_FUNCTION) { |
if (p->pn_right->pn_type != TOK_FUNCTION) { |
740 |
fatal("parse error: expected function"); |
fatal_source(file_id, p->pn_pos.begin.lineno, "expected function"); |
741 |
} |
} |
742 |
instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER); |
instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER); |
743 |
break; |
break; |
744 |
default: |
default: |
745 |
instrument_expression(p->pn_left, f); |
output_expression(p->pn_left, f, false); |
746 |
Stream_write_string(f, ": "); |
Stream_write_string(f, ": "); |
747 |
instrument_expression(p->pn_right, f); |
output_expression(p->pn_right, f, false); |
748 |
break; |
break; |
749 |
} |
} |
750 |
} |
} |
751 |
Stream_write_char(f, '}'); |
Stream_write_char(f, '}'); |
752 |
|
if (parenthesize_object_literals) { |
753 |
|
Stream_write_char(f, ')'); |
754 |
|
} |
755 |
break; |
break; |
756 |
case TOK_RP: |
case TOK_RP: |
757 |
Stream_write_char(f, '('); |
Stream_write_char(f, '('); |
758 |
instrument_expression(node->pn_kid, f); |
output_expression(node->pn_kid, f, false); |
759 |
Stream_write_char(f, ')'); |
Stream_write_char(f, ')'); |
760 |
break; |
break; |
761 |
case TOK_NAME: |
case TOK_NAME: |
762 |
print_string_atom(node->pn_atom, f); |
print_string_atom(node->pn_atom, f); |
763 |
|
if (node->pn_expr != NULL) { |
764 |
|
Stream_write_string(f, " = "); |
765 |
|
output_expression(node->pn_expr, f, false); |
766 |
|
} |
767 |
break; |
break; |
768 |
case TOK_STRING: |
case TOK_STRING: |
769 |
print_quoted_string_atom(node->pn_atom, f); |
print_quoted_string_atom(node->pn_atom, f); |
785 |
To keep the output simple, special-case zero. |
To keep the output simple, special-case zero. |
786 |
*/ |
*/ |
787 |
if (node->pn_dval == 0.0) { |
if (node->pn_dval == 0.0) { |
788 |
Stream_write_string(f, "0"); |
if (signbit(node->pn_dval)) { |
789 |
|
Stream_write_string(f, "-0"); |
790 |
|
} |
791 |
|
else { |
792 |
|
Stream_write_string(f, "0"); |
793 |
|
} |
794 |
|
} |
795 |
|
else if (node->pn_dval == INFINITY) { |
796 |
|
Stream_write_string(f, "Number.POSITIVE_INFINITY"); |
797 |
|
} |
798 |
|
else if (node->pn_dval == -INFINITY) { |
799 |
|
Stream_write_string(f, "Number.NEGATIVE_INFINITY"); |
800 |
|
} |
801 |
|
else if (isnan(node->pn_dval)) { |
802 |
|
Stream_write_string(f, "Number.NaN"); |
803 |
} |
} |
804 |
else { |
else { |
805 |
Stream_printf(f, "%.15g", node->pn_dval); |
Stream_printf(f, "%.15g", node->pn_dval); |
825 |
} |
} |
826 |
break; |
break; |
827 |
case TOK_INSTANCEOF: |
case TOK_INSTANCEOF: |
828 |
instrument_expression(node->pn_left, f); |
output_expression(node->pn_left, f, parenthesize_object_literals); |
829 |
Stream_write_string(f, " instanceof "); |
Stream_write_string(f, " instanceof "); |
830 |
instrument_expression(node->pn_right, f); |
output_expression(node->pn_right, f, false); |
831 |
break; |
break; |
832 |
case TOK_IN: |
case TOK_IN: |
833 |
instrument_expression(node->pn_left, f); |
output_expression(node->pn_left, f, false); |
834 |
Stream_write_string(f, " in "); |
Stream_write_string(f, " in "); |
835 |
instrument_expression(node->pn_right, f); |
output_expression(node->pn_right, f, false); |
836 |
break; |
break; |
837 |
case TOK_LEXICALSCOPE: |
case TOK_LEXICALSCOPE: |
838 |
assert(node->pn_arity == PN_NAME); |
assert(node->pn_arity == PN_NAME); |
843 |
assert(node->pn_expr->pn_left->pn_arity == PN_LIST); |
assert(node->pn_expr->pn_left->pn_arity == PN_LIST); |
844 |
instrument_declarations(node->pn_expr->pn_left, f); |
instrument_declarations(node->pn_expr->pn_left, f); |
845 |
Stream_write_string(f, ") "); |
Stream_write_string(f, ") "); |
846 |
instrument_expression(node->pn_expr->pn_right, f); |
output_expression(node->pn_expr->pn_right, f, true); |
847 |
break; |
break; |
848 |
case TOK_YIELD: |
case TOK_YIELD: |
849 |
assert(node->pn_arity == PN_UNARY); |
assert(node->pn_arity == PN_UNARY); |
850 |
Stream_write_string(f, "yield "); |
Stream_write_string(f, "yield"); |
851 |
instrument_expression(node->pn_kid, f); |
if (node->pn_kid != NULL) { |
852 |
|
Stream_write_char(f, ' '); |
853 |
|
output_expression(node->pn_kid, f, true); |
854 |
|
} |
855 |
break; |
break; |
856 |
case TOK_ARRAYCOMP: |
case TOK_ARRAYCOMP: |
857 |
assert(node->pn_arity == PN_LIST); |
assert(node->pn_arity == PN_LIST); |
868 |
abort(); |
abort(); |
869 |
break; |
break; |
870 |
} |
} |
|
assert(block_node->pn_type == TOK_LEXICALSCOPE); |
|
|
assert(block_node->pn_arity == PN_NAME); |
|
|
JSParseNode * for_node = block_node->pn_expr; |
|
|
assert(for_node->pn_type == TOK_FOR); |
|
|
assert(for_node->pn_arity == PN_BINARY); |
|
|
JSParseNode * if_node = NULL; |
|
|
JSParseNode * push_node; |
|
|
switch (for_node->pn_right->pn_type) { |
|
|
case TOK_ARRAYPUSH: |
|
|
push_node = for_node->pn_right; |
|
|
assert(push_node->pn_arity == PN_UNARY); |
|
|
break; |
|
|
case TOK_IF: |
|
|
if_node = for_node->pn_right; |
|
|
assert(if_node->pn_arity == PN_TERNARY); |
|
|
push_node = if_node->pn_kid2; |
|
|
break; |
|
|
default: |
|
|
abort(); |
|
|
break; |
|
|
} |
|
871 |
Stream_write_char(f, '['); |
Stream_write_char(f, '['); |
872 |
instrument_expression(push_node->pn_kid, f); |
output_array_comprehension_or_generator_expression(block_node, f); |
|
Stream_write_char(f, ' '); |
|
|
output_for_in(for_node, f); |
|
|
if (if_node) { |
|
|
Stream_write_string(f, " if ("); |
|
|
instrument_expression(if_node->pn_kid1, f); |
|
|
Stream_write_char(f, ')'); |
|
|
} |
|
873 |
Stream_write_char(f, ']'); |
Stream_write_char(f, ']'); |
874 |
} |
} |
875 |
break; |
break; |
884 |
instrument_declarations(node, f); |
instrument_declarations(node, f); |
885 |
break; |
break; |
886 |
default: |
default: |
887 |
fatal("unsupported node type in file %s: %d", file_id, node->pn_type); |
fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%d)", node->pn_type); |
888 |
} |
} |
889 |
} |
} |
890 |
|
|
892 |
switch (node->pn_type) { |
switch (node->pn_type) { |
893 |
case TOK_FUNCTION: |
case TOK_FUNCTION: |
894 |
instrument_function(node, f, indent, FUNCTION_NORMAL); |
instrument_function(node, f, indent, FUNCTION_NORMAL); |
895 |
|
Stream_write_char(f, '\n'); |
896 |
break; |
break; |
897 |
case TOK_LC: |
case TOK_LC: |
898 |
assert(node->pn_arity == PN_LIST); |
assert(node->pn_arity == PN_LIST); |
914 |
uint16_t line = node->pn_pos.begin.lineno; |
uint16_t line = node->pn_pos.begin.lineno; |
915 |
if (! is_jscoverage_if) { |
if (! is_jscoverage_if) { |
916 |
if (line > num_lines) { |
if (line > num_lines) { |
917 |
fatal("%s: script contains more than 65,535 lines", file_id); |
fatal("file %s contains more than 65,535 lines", file_id); |
918 |
} |
} |
919 |
if (line >= 2 && exclusive_directives[line - 2]) { |
if (line >= 2 && exclusive_directives[line - 2]) { |
920 |
is_jscoverage_if = true; |
is_jscoverage_if = true; |
923 |
|
|
924 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
925 |
Stream_write_string(f, "if ("); |
Stream_write_string(f, "if ("); |
926 |
instrument_expression(node->pn_kid1, f); |
output_expression(node->pn_kid1, f, false); |
927 |
Stream_write_string(f, ") {\n"); |
Stream_write_string(f, ") {\n"); |
928 |
if (is_jscoverage_if && node->pn_kid3) { |
if (is_jscoverage_if && node->pn_kid3) { |
929 |
uint16_t else_start = node->pn_kid3->pn_pos.begin.lineno; |
uint16_t else_start = node->pn_kid3->pn_pos.begin.lineno; |
960 |
assert(node->pn_arity == PN_BINARY); |
assert(node->pn_arity == PN_BINARY); |
961 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
962 |
Stream_write_string(f, "switch ("); |
Stream_write_string(f, "switch ("); |
963 |
instrument_expression(node->pn_left, f); |
output_expression(node->pn_left, f, false); |
964 |
Stream_write_string(f, ") {\n"); |
Stream_write_string(f, ") {\n"); |
965 |
{ |
{ |
966 |
JSParseNode * list = node->pn_right; |
JSParseNode * list = node->pn_right; |
972 |
switch (p->pn_type) { |
switch (p->pn_type) { |
973 |
case TOK_CASE: |
case TOK_CASE: |
974 |
Stream_write_string(f, "case "); |
Stream_write_string(f, "case "); |
975 |
instrument_expression(p->pn_left, f); |
output_expression(p->pn_left, f, false); |
976 |
Stream_write_string(f, ":\n"); |
Stream_write_string(f, ":\n"); |
977 |
break; |
break; |
978 |
case TOK_DEFAULT: |
case TOK_DEFAULT: |
996 |
assert(node->pn_arity == PN_BINARY); |
assert(node->pn_arity == PN_BINARY); |
997 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
998 |
Stream_write_string(f, "while ("); |
Stream_write_string(f, "while ("); |
999 |
instrument_expression(node->pn_left, f); |
output_expression(node->pn_left, f, false); |
1000 |
Stream_write_string(f, ") {\n"); |
Stream_write_string(f, ") {\n"); |
1001 |
instrument_statement(node->pn_right, f, indent + 2, false); |
instrument_statement(node->pn_right, f, indent + 2, false); |
1002 |
Stream_write_string(f, "}\n"); |
Stream_write_string(f, "}\n"); |
1009 |
Stream_write_string(f, "}\n"); |
Stream_write_string(f, "}\n"); |
1010 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
1011 |
Stream_write_string(f, "while ("); |
Stream_write_string(f, "while ("); |
1012 |
instrument_expression(node->pn_right, f); |
output_expression(node->pn_right, f, false); |
1013 |
Stream_write_string(f, ");\n"); |
Stream_write_string(f, ");\n"); |
1014 |
break; |
break; |
1015 |
case TOK_FOR: |
case TOK_FOR: |
1021 |
assert(node->pn_left->pn_arity == PN_BINARY); |
assert(node->pn_left->pn_arity == PN_BINARY); |
1022 |
output_for_in(node, f); |
output_for_in(node, f); |
1023 |
break; |
break; |
1024 |
case TOK_RESERVED: |
case TOK_FORHEAD: |
1025 |
/* for (;;) */ |
/* for (;;) */ |
1026 |
assert(node->pn_left->pn_arity == PN_TERNARY); |
assert(node->pn_left->pn_arity == PN_TERNARY); |
1027 |
Stream_write_string(f, "for ("); |
Stream_write_string(f, "for ("); |
1028 |
if (node->pn_left->pn_kid1) { |
if (node->pn_left->pn_kid1) { |
1029 |
instrument_expression(node->pn_left->pn_kid1, f); |
output_expression(node->pn_left->pn_kid1, f, false); |
1030 |
} |
} |
1031 |
Stream_write_string(f, ";"); |
Stream_write_string(f, ";"); |
1032 |
if (node->pn_left->pn_kid2) { |
if (node->pn_left->pn_kid2) { |
1033 |
Stream_write_char(f, ' '); |
Stream_write_char(f, ' '); |
1034 |
instrument_expression(node->pn_left->pn_kid2, f); |
output_expression(node->pn_left->pn_kid2, f, false); |
1035 |
} |
} |
1036 |
Stream_write_string(f, ";"); |
Stream_write_string(f, ";"); |
1037 |
if (node->pn_left->pn_kid3) { |
if (node->pn_left->pn_kid3) { |
1038 |
Stream_write_char(f, ' '); |
Stream_write_char(f, ' '); |
1039 |
instrument_expression(node->pn_left->pn_kid3, f); |
output_expression(node->pn_left->pn_kid3, f, false); |
1040 |
} |
} |
1041 |
Stream_write_char(f, ')'); |
Stream_write_char(f, ')'); |
1042 |
break; |
break; |
1052 |
assert(node->pn_arity == PN_UNARY); |
assert(node->pn_arity == PN_UNARY); |
1053 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
1054 |
Stream_write_string(f, "throw "); |
Stream_write_string(f, "throw "); |
1055 |
instrument_expression(node->pn_u.unary.kid, f); |
output_expression(node->pn_u.unary.kid, f, false); |
1056 |
Stream_write_string(f, ";\n"); |
Stream_write_string(f, ";\n"); |
1057 |
break; |
break; |
1058 |
case TOK_TRY: |
case TOK_TRY: |
1065 |
assert(node->pn_kid2->pn_type == TOK_RESERVED); |
assert(node->pn_kid2->pn_type == TOK_RESERVED); |
1066 |
for (JSParseNode * scope = node->pn_kid2->pn_head; scope != NULL; scope = scope->pn_next) { |
for (JSParseNode * scope = node->pn_kid2->pn_head; scope != NULL; scope = scope->pn_next) { |
1067 |
assert(scope->pn_type == TOK_LEXICALSCOPE); |
assert(scope->pn_type == TOK_LEXICALSCOPE); |
1068 |
JSParseNode * catch = scope->pn_expr; |
JSParseNode * catch_node = scope->pn_expr; |
1069 |
assert(catch->pn_type == TOK_CATCH); |
assert(catch_node->pn_type == TOK_CATCH); |
1070 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
1071 |
Stream_write_string(f, "catch ("); |
Stream_write_string(f, "catch ("); |
1072 |
/* this may not be a name - destructuring assignment */ |
output_expression(catch_node->pn_kid1, f, false); |
1073 |
/* |
if (catch_node->pn_kid2) { |
|
assert(catch->pn_kid1->pn_arity == PN_NAME); |
|
|
print_string_atom(catch->pn_kid1->pn_atom, f); |
|
|
*/ |
|
|
instrument_expression(catch->pn_kid1, f); |
|
|
if (catch->pn_kid2) { |
|
1074 |
Stream_write_string(f, " if "); |
Stream_write_string(f, " if "); |
1075 |
instrument_expression(catch->pn_kid2, f); |
output_expression(catch_node->pn_kid2, f, false); |
1076 |
} |
} |
1077 |
Stream_write_string(f, ") {\n"); |
Stream_write_string(f, ") {\n"); |
1078 |
instrument_statement(catch->pn_kid3, f, indent + 2, false); |
instrument_statement(catch_node->pn_kid3, f, indent + 2, false); |
1079 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
1080 |
Stream_write_string(f, "}\n"); |
Stream_write_string(f, "}\n"); |
1081 |
} |
} |
1096 |
assert(node->pn_arity == PN_NAME || node->pn_arity == PN_NULLARY); |
assert(node->pn_arity == PN_NAME || node->pn_arity == PN_NULLARY); |
1097 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
1098 |
Stream_write_string(f, node->pn_type == TOK_BREAK? "break": "continue"); |
Stream_write_string(f, node->pn_type == TOK_BREAK? "break": "continue"); |
1099 |
JSAtom * atom = node->pn_u.name.atom; |
if (node->pn_atom != NULL) { |
|
if (atom != NULL) { |
|
1100 |
Stream_write_char(f, ' '); |
Stream_write_char(f, ' '); |
1101 |
print_string_atom(node->pn_atom, f); |
print_string_atom(node->pn_atom, f); |
1102 |
} |
} |
1106 |
assert(node->pn_arity == PN_BINARY); |
assert(node->pn_arity == PN_BINARY); |
1107 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
1108 |
Stream_write_string(f, "with ("); |
Stream_write_string(f, "with ("); |
1109 |
instrument_expression(node->pn_left, f); |
output_expression(node->pn_left, f, false); |
1110 |
Stream_write_string(f, ") {\n"); |
Stream_write_string(f, ") {\n"); |
1111 |
instrument_statement(node->pn_right, f, indent + 2, false); |
instrument_statement(node->pn_right, f, indent + 2, false); |
1112 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
1114 |
break; |
break; |
1115 |
case TOK_VAR: |
case TOK_VAR: |
1116 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
1117 |
instrument_expression(node, f); |
output_expression(node, f, false); |
1118 |
Stream_write_string(f, ";\n"); |
Stream_write_string(f, ";\n"); |
1119 |
break; |
break; |
1120 |
case TOK_RETURN: |
case TOK_RETURN: |
1123 |
Stream_write_string(f, "return"); |
Stream_write_string(f, "return"); |
1124 |
if (node->pn_kid != NULL) { |
if (node->pn_kid != NULL) { |
1125 |
Stream_write_char(f, ' '); |
Stream_write_char(f, ' '); |
1126 |
instrument_expression(node->pn_kid, f); |
output_expression(node->pn_kid, f, true); |
1127 |
} |
} |
1128 |
Stream_write_string(f, ";\n"); |
Stream_write_string(f, ";\n"); |
1129 |
break; |
break; |
1131 |
assert(node->pn_arity == PN_UNARY); |
assert(node->pn_arity == PN_UNARY); |
1132 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
1133 |
if (node->pn_kid != NULL) { |
if (node->pn_kid != NULL) { |
1134 |
instrument_expression(node->pn_kid, f); |
output_expression(node->pn_kid, f, true); |
1135 |
} |
} |
1136 |
Stream_write_string(f, ";\n"); |
Stream_write_string(f, ";\n"); |
1137 |
break; |
break; |
1138 |
case TOK_COLON: |
case TOK_COLON: |
1139 |
|
{ |
1140 |
assert(node->pn_arity == PN_NAME); |
assert(node->pn_arity == PN_NAME); |
|
/* |
|
|
This one is tricky: can't output instrumentation between the label and the |
|
|
statement it's supposed to label ... |
|
|
*/ |
|
1141 |
Stream_printf(f, "%*s", indent < 2? 0: indent - 2, ""); |
Stream_printf(f, "%*s", indent < 2? 0: indent - 2, ""); |
1142 |
print_string_atom(node->pn_atom, f); |
print_string_atom(node->pn_atom, f); |
1143 |
Stream_write_string(f, ":\n"); |
Stream_write_string(f, ":\n"); |
1144 |
/* |
JSParseNode * labelled = node->pn_expr; |
1145 |
... use output_statement instead of instrument_statement. |
if (labelled->pn_type == TOK_LEXICALSCOPE) { |
1146 |
*/ |
labelled = labelled->pn_expr; |
1147 |
output_statement(node->pn_expr, f, indent, false); |
} |
1148 |
|
if (labelled->pn_type == TOK_LC) { |
1149 |
|
/* labelled block */ |
1150 |
|
Stream_printf(f, "%*s", indent, ""); |
1151 |
|
Stream_write_string(f, "{\n"); |
1152 |
|
instrument_statement(labelled, f, indent + 2, false); |
1153 |
|
Stream_printf(f, "%*s", indent, ""); |
1154 |
|
Stream_write_string(f, "}\n"); |
1155 |
|
} |
1156 |
|
else { |
1157 |
|
/* |
1158 |
|
This one is tricky: can't output instrumentation between the label and the |
1159 |
|
statement it's supposed to label, so use output_statement instead of |
1160 |
|
instrument_statement. |
1161 |
|
*/ |
1162 |
|
output_statement(labelled, f, indent, false); |
1163 |
|
} |
1164 |
break; |
break; |
1165 |
|
} |
1166 |
case TOK_LEXICALSCOPE: |
case TOK_LEXICALSCOPE: |
1167 |
/* let statement */ |
/* let statement */ |
1168 |
assert(node->pn_arity == PN_NAME); |
assert(node->pn_arity == PN_NAME); |
1205 |
case PN_LIST: |
case PN_LIST: |
1206 |
/* let definition */ |
/* let definition */ |
1207 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
1208 |
instrument_expression(node, f); |
output_expression(node, f, false); |
1209 |
Stream_write_string(f, ";\n"); |
Stream_write_string(f, ";\n"); |
1210 |
break; |
break; |
1211 |
default: |
default: |
1217 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
1218 |
Stream_write_string(f, "debugger;\n"); |
Stream_write_string(f, "debugger;\n"); |
1219 |
break; |
break; |
1220 |
|
case TOK_SEQ: |
1221 |
|
/* |
1222 |
|
This occurs with the statement: |
1223 |
|
for (var a = b in c) {} |
1224 |
|
*/ |
1225 |
|
assert(node->pn_arity == PN_LIST); |
1226 |
|
for (JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) { |
1227 |
|
instrument_statement(p, f, indent, false); |
1228 |
|
} |
1229 |
|
break; |
1230 |
default: |
default: |
1231 |
fatal("unsupported node type in file %s: %d", file_id, node->pn_type); |
fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%d)", node->pn_type); |
1232 |
} |
} |
1233 |
} |
} |
1234 |
|
|
1241 |
if (node->pn_type != TOK_LC && node->pn_type != TOK_LEXICALSCOPE) { |
if (node->pn_type != TOK_LC && node->pn_type != TOK_LEXICALSCOPE) { |
1242 |
uint16_t line = node->pn_pos.begin.lineno; |
uint16_t line = node->pn_pos.begin.lineno; |
1243 |
if (line > num_lines) { |
if (line > num_lines) { |
1244 |
fatal("%s: script contains more than 65,535 lines", file_id); |
fatal("file %s contains more than 65,535 lines", file_id); |
1245 |
} |
} |
1246 |
|
|
1247 |
/* the root node has line number 0 */ |
/* the root node has line number 0 */ |
1289 |
} |
} |
1290 |
|
|
1291 |
static void error_reporter(JSContext * context, const char * message, JSErrorReport * report) { |
static void error_reporter(JSContext * context, const char * message, JSErrorReport * report) { |
1292 |
fprintf(stderr, "jscoverage: parse error: line %u: %s\n", report->lineno, message); |
warn_source(file_id, report->lineno, "%s", message); |
1293 |
} |
} |
1294 |
|
|
1295 |
void jscoverage_instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output) { |
void jscoverage_instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output) { |
1298 |
/* parse the javascript */ |
/* parse the javascript */ |
1299 |
JSParseContext parse_context; |
JSParseContext parse_context; |
1300 |
if (! js_InitParseContext(context, &parse_context, NULL, NULL, characters, num_characters, NULL, NULL, 1)) { |
if (! js_InitParseContext(context, &parse_context, NULL, NULL, characters, num_characters, NULL, NULL, 1)) { |
1301 |
fatal("cannot create token stream from file: %s", file_id); |
fatal("cannot create token stream from file %s", file_id); |
1302 |
} |
} |
1303 |
JSErrorReporter old_error_reporter = JS_SetErrorReporter(context, error_reporter); |
JSErrorReporter old_error_reporter = JS_SetErrorReporter(context, error_reporter); |
1304 |
JSParseNode * node = js_ParseScript(context, global, &parse_context); |
JSParseNode * node = js_ParseScript(context, global, &parse_context); |
1305 |
if (node == NULL) { |
if (node == NULL) { |
1306 |
js_ReportUncaughtException(context); |
js_ReportUncaughtException(context); |
1307 |
fatal("parse error in file: %s", file_id); |
fatal("parse error in file %s", file_id); |
1308 |
} |
} |
1309 |
JS_SetErrorReporter(context, old_error_reporter); |
JS_SetErrorReporter(context, old_error_reporter); |
1310 |
num_lines = node->pn_pos.end.lineno; |
num_lines = node->pn_pos.end.lineno; |
1311 |
lines = xmalloc(num_lines); |
lines = (char *) xmalloc(num_lines); |
1312 |
for (unsigned int i = 0; i < num_lines; i++) { |
for (unsigned int i = 0; i < num_lines; i++) { |
1313 |
lines[i] = 0; |
lines[i] = 0; |
1314 |
} |
} |
1325 |
size_t i = 0; |
size_t i = 0; |
1326 |
while (i < num_characters) { |
while (i < num_characters) { |
1327 |
if (line_number == UINT16_MAX) { |
if (line_number == UINT16_MAX) { |
1328 |
fatal("%s: script has more than 65,535 lines", file_id); |
fatal("file %s contains more than 65,535 lines", file_id); |
1329 |
} |
} |
1330 |
line_number++; |
line_number++; |
1331 |
size_t line_start = i; |
size_t line_start = i; |
1392 |
|
|
1393 |
/* write line number info to the output */ |
/* write line number info to the output */ |
1394 |
Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n"); |
Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n"); |
1395 |
Stream_write_string(output, "if (! top._$jscoverage) {\n top._$jscoverage = {};\n}\n"); |
switch (jscoverage_mode) { |
1396 |
Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n"); |
case JSCOVERAGE_MOZILLA: |
1397 |
|
Stream_write_string(output, "try {\n"); |
1398 |
|
Stream_write_string(output, " Components.utils.import('resource://gre/modules/jscoverage.jsm');\n"); |
1399 |
|
Stream_printf(output, " dump('%s: successfully imported jscoverage module\\n');\n", id); |
1400 |
|
Stream_write_string(output, "}\n"); |
1401 |
|
Stream_write_string(output, "catch (e) {\n"); |
1402 |
|
Stream_write_string(output, " _$jscoverage = {};\n"); |
1403 |
|
Stream_printf(output, " dump('%s: failed to import jscoverage module - coverage not available for this file\\n');\n", id); |
1404 |
|
Stream_write_string(output, "}\n"); |
1405 |
|
break; |
1406 |
|
case JSCOVERAGE_NORMAL: |
1407 |
|
Stream_write_string(output, "if (! top._$jscoverage) {\n top._$jscoverage = {};\n}\n"); |
1408 |
|
Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n"); |
1409 |
|
break; |
1410 |
|
case JSCOVERAGE_NO_BROWSER: |
1411 |
|
Stream_write_string(output, "if (typeof _$jscoverage === 'undefined') {\n var _$jscoverage = {};\n}\n"); |
1412 |
|
break; |
1413 |
|
} |
1414 |
Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id); |
Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id); |
1415 |
Stream_printf(output, " _$jscoverage['%s'] = [];\n", file_id); |
Stream_printf(output, " _$jscoverage['%s'] = [];\n", file_id); |
1416 |
for (int i = 0; i < num_lines; i++) { |
for (int i = 0; i < num_lines; i++) { |
1626 |
}; |
}; |
1627 |
|
|
1628 |
static int compare_strings(const void * p1, const void * p2) { |
static int compare_strings(const void * p1, const void * p2) { |
1629 |
return strcmp(p1, p2) == 0; |
return strcmp((const char *) p1, (const char *) p2) == 0; |
1630 |
} |
} |
1631 |
|
|
1632 |
Coverage * Coverage_new(void) { |
Coverage * Coverage_new(void) { |
1633 |
Coverage * result = xmalloc(sizeof(Coverage)); |
Coverage * result = (Coverage *) xmalloc(sizeof(Coverage)); |
1634 |
result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL); |
result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL); |
1635 |
if (result->coverage_table == NULL) { |
if (result->coverage_table == NULL) { |
1636 |
fatal("cannot create hash table"); |
fatal("cannot create hash table"); |
1665 |
}; |
}; |
1666 |
|
|
1667 |
static intN enumerator(JSHashEntry * entry, intN i, void * arg) { |
static intN enumerator(JSHashEntry * entry, intN i, void * arg) { |
1668 |
struct EnumeratorArg * enumerator_arg = arg; |
struct EnumeratorArg * enumerator_arg = (struct EnumeratorArg *) arg; |
1669 |
enumerator_arg->f(entry->value, i, enumerator_arg->p); |
enumerator_arg->f((FileCoverage *) entry->value, i, enumerator_arg->p); |
1670 |
return 0; |
return 0; |
1671 |
} |
} |
1672 |
|
|
1699 |
} |
} |
1700 |
JSParseNode * root = js_ParseScript(context, global, &parse_context); |
JSParseNode * root = js_ParseScript(context, global, &parse_context); |
1701 |
free(parenthesized_json); |
free(parenthesized_json); |
1702 |
|
|
1703 |
|
JSParseNode * semi = NULL; |
1704 |
|
JSParseNode * object = NULL; |
1705 |
|
|
1706 |
if (root == NULL) { |
if (root == NULL) { |
1707 |
result = -1; |
result = -1; |
1708 |
goto done; |
goto done; |
1713 |
result = -1; |
result = -1; |
1714 |
goto done; |
goto done; |
1715 |
} |
} |
1716 |
JSParseNode * semi = root->pn_u.list.head; |
semi = root->pn_u.list.head; |
1717 |
|
|
1718 |
/* the list must be TOK_SEMI and it must contain only one element */ |
/* the list must be TOK_SEMI and it must contain only one element */ |
1719 |
if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) { |
if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) { |
1720 |
result = -1; |
result = -1; |
1721 |
goto done; |
goto done; |
1722 |
} |
} |
1723 |
JSParseNode * parenthesized = semi->pn_kid; |
object = semi->pn_kid; |
|
|
|
|
/* this must be a parenthesized expression */ |
|
|
if (parenthesized->pn_type != TOK_RP) { |
|
|
result = -1; |
|
|
goto done; |
|
|
} |
|
|
JSParseNode * object = parenthesized->pn_kid; |
|
1724 |
|
|
1725 |
/* this must be an object literal */ |
/* this must be an object literal */ |
1726 |
if (object->pn_type != TOK_RC) { |
if (object->pn_type != TOK_RC) { |
1804 |
} |
} |
1805 |
|
|
1806 |
/* look up the file in the coverage table */ |
/* look up the file in the coverage table */ |
1807 |
FileCoverage * file_coverage = JS_HashTableLookup(coverage->coverage_table, id_bytes); |
FileCoverage * file_coverage = (FileCoverage *) JS_HashTableLookup(coverage->coverage_table, id_bytes); |
1808 |
if (file_coverage == NULL) { |
if (file_coverage == NULL) { |
1809 |
/* not there: create a new one */ |
/* not there: create a new one */ |
1810 |
char * id = xstrdup(id_bytes); |
char * id = xstrdup(id_bytes); |
1811 |
file_coverage = xmalloc(sizeof(FileCoverage)); |
file_coverage = (FileCoverage *) xmalloc(sizeof(FileCoverage)); |
1812 |
file_coverage->id = id; |
file_coverage->id = id; |
1813 |
file_coverage->num_coverage_lines = array->pn_count; |
file_coverage->num_coverage_lines = array->pn_count; |
1814 |
file_coverage->coverage_lines = xnew(int, array->pn_count); |
file_coverage->coverage_lines = xnew(int, array->pn_count); |
1832 |
|
|
1833 |
/* add to the hash table */ |
/* add to the hash table */ |
1834 |
JS_HashTableAdd(coverage->coverage_table, id, file_coverage); |
JS_HashTableAdd(coverage->coverage_table, id, file_coverage); |
1835 |
struct FileCoverageList * coverage_list = xmalloc(sizeof(struct FileCoverageList)); |
struct FileCoverageList * coverage_list = (FileCoverageList *) xmalloc(sizeof(struct FileCoverageList)); |
1836 |
coverage_list->file_coverage = file_coverage; |
coverage_list->file_coverage = file_coverage; |
1837 |
coverage_list->next = coverage->coverage_list; |
coverage_list->next = coverage->coverage_list; |
1838 |
coverage->coverage_list = coverage_list; |
coverage->coverage_list = coverage_list; |