40 |
#include "resource-manager.h" |
#include "resource-manager.h" |
41 |
#include "util.h" |
#include "util.h" |
42 |
|
|
43 |
|
struct IfDirective { |
44 |
|
const jschar * condition_start; |
45 |
|
const jschar * condition_end; |
46 |
|
uint16_t start_line; |
47 |
|
uint16_t end_line; |
48 |
|
struct IfDirective * next; |
49 |
|
}; |
50 |
|
|
51 |
|
static bool * exclusive_directives = NULL; |
52 |
|
|
53 |
static JSRuntime * runtime = NULL; |
static JSRuntime * runtime = NULL; |
54 |
static JSContext * context = NULL; |
static JSContext * context = NULL; |
55 |
static JSObject * global = NULL; |
static JSObject * global = NULL; |
215 |
} |
} |
216 |
|
|
217 |
static void instrument_expression(JSParseNode * node, Stream * f); |
static void instrument_expression(JSParseNode * node, Stream * f); |
218 |
static void instrument_statement(JSParseNode * node, Stream * f, int indent); |
static void instrument_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if); |
219 |
|
|
220 |
enum FunctionType { |
enum FunctionType { |
221 |
FUNCTION_NORMAL, |
FUNCTION_NORMAL, |
271 |
free(params); |
free(params); |
272 |
|
|
273 |
/* function body */ |
/* function body */ |
274 |
instrument_statement(node->pn_body, f, indent + 2); |
instrument_statement(node->pn_body, f, indent + 2, false); |
275 |
|
|
276 |
Stream_write_string(f, "}\n"); |
Stream_write_string(f, "}\n"); |
277 |
} |
} |
630 |
} |
} |
631 |
} |
} |
632 |
|
|
633 |
static void output_statement(JSParseNode * node, Stream * f, int indent) { |
static void output_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) { |
634 |
switch (node->pn_type) { |
switch (node->pn_type) { |
635 |
case TOK_FUNCTION: |
case TOK_FUNCTION: |
636 |
instrument_function(node, f, indent, FUNCTION_NORMAL); |
instrument_function(node, f, indent, FUNCTION_NORMAL); |
641 |
Stream_write_string(f, "{\n"); |
Stream_write_string(f, "{\n"); |
642 |
*/ |
*/ |
643 |
for (struct JSParseNode * p = node->pn_u.list.head; p != NULL; p = p->pn_next) { |
for (struct JSParseNode * p = node->pn_u.list.head; p != NULL; p = p->pn_next) { |
644 |
instrument_statement(p, f, indent); |
instrument_statement(p, f, indent, false); |
645 |
} |
} |
646 |
/* |
/* |
647 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
649 |
*/ |
*/ |
650 |
break; |
break; |
651 |
case TOK_IF: |
case TOK_IF: |
652 |
|
{ |
653 |
assert(node->pn_arity == PN_TERNARY); |
assert(node->pn_arity == PN_TERNARY); |
654 |
|
|
655 |
|
uint16_t line = node->pn_pos.begin.lineno; |
656 |
|
if (! is_jscoverage_if) { |
657 |
|
if (line > num_lines) { |
658 |
|
fatal("%s: script contains more than 65,535 lines", file_id); |
659 |
|
} |
660 |
|
if (line >= 2 && exclusive_directives[line - 2]) { |
661 |
|
is_jscoverage_if = true; |
662 |
|
} |
663 |
|
} |
664 |
|
|
665 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
666 |
Stream_write_string(f, "if ("); |
Stream_write_string(f, "if ("); |
667 |
instrument_expression(node->pn_kid1, f); |
instrument_expression(node->pn_kid1, f); |
668 |
Stream_write_string(f, ") {\n"); |
Stream_write_string(f, ") {\n"); |
669 |
instrument_statement(node->pn_kid2, f, indent + 2); |
if (is_jscoverage_if && node->pn_kid3) { |
670 |
|
uint16_t else_start = node->pn_kid3->pn_pos.begin.lineno; |
671 |
|
uint16_t else_end = node->pn_kid3->pn_pos.end.lineno + 1; |
672 |
|
Stream_printf(f, "%*s", indent + 2, ""); |
673 |
|
Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, else_start, else_end); |
674 |
|
} |
675 |
|
instrument_statement(node->pn_kid2, f, indent + 2, false); |
676 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
677 |
Stream_write_string(f, "}\n"); |
Stream_write_string(f, "}\n"); |
678 |
if (node->pn_kid3) { |
|
679 |
|
if (node->pn_kid3 || is_jscoverage_if) { |
680 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
681 |
Stream_write_string(f, "else {\n"); |
Stream_write_string(f, "else {\n"); |
682 |
instrument_statement(node->pn_kid3, f, indent + 2); |
|
683 |
|
if (is_jscoverage_if) { |
684 |
|
uint16_t if_start = node->pn_kid2->pn_pos.begin.lineno + 1; |
685 |
|
uint16_t if_end = node->pn_kid2->pn_pos.end.lineno + 1; |
686 |
|
Stream_printf(f, "%*s", indent + 2, ""); |
687 |
|
Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_start, if_end); |
688 |
|
} |
689 |
|
|
690 |
|
if (node->pn_kid3) { |
691 |
|
instrument_statement(node->pn_kid3, f, indent + 2, is_jscoverage_if); |
692 |
|
} |
693 |
|
|
694 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
695 |
Stream_write_string(f, "}\n"); |
Stream_write_string(f, "}\n"); |
696 |
} |
} |
697 |
|
|
698 |
break; |
break; |
699 |
|
} |
700 |
case TOK_SWITCH: |
case TOK_SWITCH: |
701 |
assert(node->pn_arity == PN_BINARY); |
assert(node->pn_arity == PN_BINARY); |
702 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
718 |
abort(); |
abort(); |
719 |
break; |
break; |
720 |
} |
} |
721 |
instrument_statement(p->pn_right, f, indent + 2); |
instrument_statement(p->pn_right, f, indent + 2, false); |
722 |
} |
} |
723 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
724 |
Stream_write_string(f, "}\n"); |
Stream_write_string(f, "}\n"); |
733 |
Stream_write_string(f, "while ("); |
Stream_write_string(f, "while ("); |
734 |
instrument_expression(node->pn_left, f); |
instrument_expression(node->pn_left, f); |
735 |
Stream_write_string(f, ") {\n"); |
Stream_write_string(f, ") {\n"); |
736 |
instrument_statement(node->pn_right, f, indent + 2); |
instrument_statement(node->pn_right, f, indent + 2, false); |
737 |
Stream_write_string(f, "}\n"); |
Stream_write_string(f, "}\n"); |
738 |
break; |
break; |
739 |
case TOK_DO: |
case TOK_DO: |
740 |
assert(node->pn_arity == PN_BINARY); |
assert(node->pn_arity == PN_BINARY); |
741 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
742 |
Stream_write_string(f, "do {\n"); |
Stream_write_string(f, "do {\n"); |
743 |
instrument_statement(node->pn_left, f, indent + 2); |
instrument_statement(node->pn_left, f, indent + 2, false); |
744 |
Stream_write_string(f, "}\n"); |
Stream_write_string(f, "}\n"); |
745 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
746 |
Stream_write_string(f, "while ("); |
Stream_write_string(f, "while ("); |
803 |
break; |
break; |
804 |
} |
} |
805 |
Stream_write_string(f, ") {\n"); |
Stream_write_string(f, ") {\n"); |
806 |
instrument_statement(node->pn_right, f, indent + 2); |
instrument_statement(node->pn_right, f, indent + 2, false); |
807 |
Stream_write_string(f, "}\n"); |
Stream_write_string(f, "}\n"); |
808 |
break; |
break; |
809 |
case TOK_THROW: |
case TOK_THROW: |
816 |
case TOK_TRY: |
case TOK_TRY: |
817 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
818 |
Stream_write_string(f, "try {\n"); |
Stream_write_string(f, "try {\n"); |
819 |
instrument_statement(node->pn_kid1, f, indent + 2); |
instrument_statement(node->pn_kid1, f, indent + 2, false); |
820 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
821 |
Stream_write_string(f, "}\n"); |
Stream_write_string(f, "}\n"); |
822 |
{ |
{ |
831 |
instrument_expression(catch->pn_kid1->pn_expr, f); |
instrument_expression(catch->pn_kid1->pn_expr, f); |
832 |
} |
} |
833 |
Stream_write_string(f, ") {\n"); |
Stream_write_string(f, ") {\n"); |
834 |
instrument_statement(catch->pn_kid3, f, indent + 2); |
instrument_statement(catch->pn_kid3, f, indent + 2, false); |
835 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
836 |
Stream_write_string(f, "}\n"); |
Stream_write_string(f, "}\n"); |
837 |
} |
} |
839 |
if (node->pn_kid3) { |
if (node->pn_kid3) { |
840 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
841 |
Stream_write_string(f, "finally {\n"); |
Stream_write_string(f, "finally {\n"); |
842 |
instrument_statement(node->pn_kid3, f, indent + 2); |
instrument_statement(node->pn_kid3, f, indent + 2, false); |
843 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
844 |
Stream_write_string(f, "}\n"); |
Stream_write_string(f, "}\n"); |
845 |
} |
} |
865 |
Stream_write_string(f, "with ("); |
Stream_write_string(f, "with ("); |
866 |
instrument_expression(node->pn_left, f); |
instrument_expression(node->pn_left, f); |
867 |
Stream_write_string(f, ") {\n"); |
Stream_write_string(f, ") {\n"); |
868 |
instrument_statement(node->pn_right, f, indent + 2); |
instrument_statement(node->pn_right, f, indent + 2, false); |
869 |
Stream_printf(f, "%*s", indent, ""); |
Stream_printf(f, "%*s", indent, ""); |
870 |
Stream_write_string(f, "}\n"); |
Stream_write_string(f, "}\n"); |
871 |
break; |
break; |
903 |
/* |
/* |
904 |
... use output_statement instead of instrument_statement. |
... use output_statement instead of instrument_statement. |
905 |
*/ |
*/ |
906 |
output_statement(node->pn_expr, f, indent); |
output_statement(node->pn_expr, f, indent, false); |
907 |
break; |
break; |
908 |
default: |
default: |
909 |
fatal("unsupported node type in file %s: %d", file_id, node->pn_type); |
fatal("unsupported node type in file %s: %d", file_id, node->pn_type); |
915 |
TOK_FUNCTION is handled as a statement and as an expression. |
TOK_FUNCTION is handled as a statement and as an expression. |
916 |
TOK_EXPORT, TOK_IMPORT are not handled. |
TOK_EXPORT, TOK_IMPORT are not handled. |
917 |
*/ |
*/ |
918 |
static void instrument_statement(JSParseNode * node, Stream * f, int indent) { |
static void instrument_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) { |
919 |
if (node->pn_type != TOK_LC) { |
if (node->pn_type != TOK_LC) { |
920 |
uint16_t line = node->pn_pos.begin.lineno; |
uint16_t line = node->pn_pos.begin.lineno; |
921 |
if (line > num_lines) { |
if (line > num_lines) { |
929 |
lines[line - 1] = 1; |
lines[line - 1] = 1; |
930 |
} |
} |
931 |
} |
} |
932 |
output_statement(node, f, indent); |
output_statement(node, f, indent, is_jscoverage_if); |
933 |
} |
} |
934 |
|
|
935 |
static bool characters_start_with(const jschar * characters, size_t line_start, size_t line_end, const char * prefix) { |
static bool characters_start_with(const jschar * characters, size_t line_start, size_t line_end, const char * prefix) { |
951 |
} |
} |
952 |
} |
} |
953 |
|
|
954 |
|
static bool characters_are_white_space(const jschar * characters, size_t line_start, size_t line_end) { |
955 |
|
/* XXX - other Unicode space */ |
956 |
|
const jschar * end = characters + line_end; |
957 |
|
for (const jschar * p = characters + line_start; p < end; p++) { |
958 |
|
jschar c = *p; |
959 |
|
if (c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0) { |
960 |
|
continue; |
961 |
|
} |
962 |
|
else { |
963 |
|
return false; |
964 |
|
} |
965 |
|
} |
966 |
|
return true; |
967 |
|
} |
968 |
|
|
969 |
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) { |
970 |
file_id = id; |
file_id = id; |
971 |
|
|
982 |
} |
} |
983 |
num_lines = node->pn_pos.end.lineno; |
num_lines = node->pn_pos.end.lineno; |
984 |
lines = xmalloc(num_lines); |
lines = xmalloc(num_lines); |
985 |
for (int i = 0; i < num_lines; i++) { |
for (unsigned int i = 0; i < num_lines; i++) { |
986 |
lines[i] = 0; |
lines[i] = 0; |
987 |
} |
} |
988 |
|
|
989 |
/* |
/* search code for conditionals */ |
990 |
An instrumented JavaScript file has 3 sections: |
exclusive_directives = xnew(bool, num_lines); |
991 |
1. initialization |
for (unsigned int i = 0; i < num_lines; i++) { |
992 |
2. instrumented source code |
exclusive_directives[i] = false; |
|
3. original source code |
|
|
*/ |
|
|
|
|
|
Stream * instrumented = Stream_new(0); |
|
|
instrument_statement(node, instrumented, 0); |
|
|
|
|
|
/* write line number info to the output */ |
|
|
Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n"); |
|
|
Stream_write_string(output, "if (! top._$jscoverage) {\n top._$jscoverage = {};\n}\n"); |
|
|
Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n"); |
|
|
Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id); |
|
|
Stream_printf(output, " _$jscoverage['%s'] = [];\n", file_id); |
|
|
for (int i = 0; i < num_lines; i++) { |
|
|
if (lines[i]) { |
|
|
Stream_printf(output, " _$jscoverage['%s'][%d] = 0;\n", file_id, i + 1); |
|
|
} |
|
993 |
} |
} |
|
Stream_write_string(output, "}\n"); |
|
|
free(lines); |
|
|
lines = NULL; |
|
|
|
|
|
/* copy the instrumented source code to the output */ |
|
|
Stream_write(output, instrumented->data, instrumented->length); |
|
994 |
|
|
|
/* conditionals */ |
|
995 |
bool has_conditionals = false; |
bool has_conditionals = false; |
996 |
|
struct IfDirective * if_directives = NULL; |
997 |
size_t line_number = 0; |
size_t line_number = 0; |
998 |
size_t i = 0; |
size_t i = 0; |
999 |
while (i < num_characters) { |
while (i < num_characters) { |
1000 |
|
if (line_number == UINT16_MAX) { |
1001 |
|
fatal("%s: script has more than 65,535 lines", file_id); |
1002 |
|
} |
1003 |
line_number++; |
line_number++; |
1004 |
size_t line_start = i; |
size_t line_start = i; |
1005 |
jschar c; |
jschar c; |
1015 |
break; |
break; |
1016 |
default: |
default: |
1017 |
i++; |
i++; |
|
break; |
|
1018 |
} |
} |
1019 |
} |
} |
1020 |
size_t line_end = i; |
size_t line_end = i; |
1024 |
i++; |
i++; |
1025 |
} |
} |
1026 |
} |
} |
1027 |
|
|
1028 |
if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_IF")) { |
if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_IF")) { |
1029 |
if (! has_conditionals) { |
has_conditionals = true; |
1030 |
has_conditionals = true; |
|
1031 |
Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id); |
if (characters_are_white_space(characters, line_start + 16, line_end)) { |
1032 |
} |
exclusive_directives[line_number - 1] = true; |
1033 |
Stream_write_string(output, "if (!("); |
} |
1034 |
for (size_t j = line_start + 16; j < line_end; j++) { |
else { |
1035 |
jschar c = characters[j]; |
struct IfDirective * if_directive = xnew(struct IfDirective, 1); |
1036 |
if (c == '\t' || (32 <= c && c <= 126)) { |
if_directive->condition_start = characters + line_start + 16; |
1037 |
Stream_write_char(output, c); |
if_directive->condition_end = characters + line_end; |
1038 |
} |
if_directive->start_line = line_number; |
1039 |
else { |
if_directive->end_line = 0; |
1040 |
Stream_printf(output, "\\u%04x", c); |
if_directive->next = if_directives; |
1041 |
} |
if_directives = if_directive; |
1042 |
} |
} |
|
Stream_write_string(output, ")) {\n"); |
|
|
Stream_printf(output, " _$jscoverage['%s'].conditionals[%d] = ", file_id, line_number); |
|
1043 |
} |
} |
1044 |
else if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_ENDIF")) { |
else if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_ENDIF")) { |
1045 |
Stream_printf(output, "%d;\n", line_number); |
for (struct IfDirective * p = if_directives; p != NULL; p = p->next) { |
1046 |
Stream_printf(output, "}\n"); |
if (p->end_line == 0) { |
1047 |
|
p->end_line = line_number; |
1048 |
|
break; |
1049 |
|
} |
1050 |
|
} |
1051 |
|
} |
1052 |
|
} |
1053 |
|
|
1054 |
|
/* |
1055 |
|
An instrumented JavaScript file has 4 sections: |
1056 |
|
1. initialization |
1057 |
|
2. instrumented source code |
1058 |
|
3. conditionals |
1059 |
|
4. original source code |
1060 |
|
*/ |
1061 |
|
|
1062 |
|
Stream * instrumented = Stream_new(0); |
1063 |
|
instrument_statement(node, instrumented, 0, false); |
1064 |
|
|
1065 |
|
/* write line number info to the output */ |
1066 |
|
Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n"); |
1067 |
|
Stream_write_string(output, "if (! top._$jscoverage) {\n top._$jscoverage = {};\n}\n"); |
1068 |
|
Stream_write_string(output, "var _$jscoverage = top._$jscoverage;\n"); |
1069 |
|
Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id); |
1070 |
|
Stream_printf(output, " _$jscoverage['%s'] = [];\n", file_id); |
1071 |
|
for (int i = 0; i < num_lines; i++) { |
1072 |
|
if (lines[i]) { |
1073 |
|
Stream_printf(output, " _$jscoverage['%s'][%d] = 0;\n", file_id, i + 1); |
1074 |
} |
} |
1075 |
} |
} |
1076 |
|
Stream_write_string(output, "}\n"); |
1077 |
|
free(lines); |
1078 |
|
lines = NULL; |
1079 |
|
free(exclusive_directives); |
1080 |
|
exclusive_directives = NULL; |
1081 |
|
|
1082 |
|
/* conditionals */ |
1083 |
|
if (has_conditionals) { |
1084 |
|
Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id); |
1085 |
|
} |
1086 |
|
|
1087 |
|
/* copy the instrumented source code to the output */ |
1088 |
|
Stream_write(output, instrumented->data, instrumented->length); |
1089 |
|
|
1090 |
|
/* conditionals */ |
1091 |
|
for (struct IfDirective * if_directive = if_directives; if_directive != NULL; if_directive = if_directive->next) { |
1092 |
|
Stream_write_string(output, "if (!("); |
1093 |
|
for (const jschar * p = if_directive->condition_start; p < if_directive->condition_end; p++) { |
1094 |
|
jschar c = *p; |
1095 |
|
if (c == '\t' || (32 <= c && c <= 126)) { |
1096 |
|
Stream_write_char(output, c); |
1097 |
|
} |
1098 |
|
else { |
1099 |
|
Stream_printf(output, "\\u%04x", c); |
1100 |
|
} |
1101 |
|
} |
1102 |
|
Stream_write_string(output, ")) {\n"); |
1103 |
|
Stream_printf(output, " _$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_directive->start_line, if_directive->end_line); |
1104 |
|
Stream_write_string(output, "}\n"); |
1105 |
|
} |
1106 |
|
|
1107 |
|
/* free */ |
1108 |
|
while (if_directives != NULL) { |
1109 |
|
struct IfDirective * if_directive = if_directives; |
1110 |
|
if_directives = if_directives->next; |
1111 |
|
free(if_directive); |
1112 |
|
} |
1113 |
|
|
1114 |
/* copy the original source to the output */ |
/* copy the original source to the output */ |
1115 |
Stream_printf(output, "_$jscoverage['%s'].source = ", file_id); |
Stream_printf(output, "_$jscoverage['%s'].source = ", file_id); |