35 |
#include <jsstr.h> |
#include <jsstr.h> |
36 |
|
|
37 |
#include "encoding.h" |
#include "encoding.h" |
38 |
|
#include "global.h" |
39 |
|
#include "highlight.h" |
40 |
#include "resource-manager.h" |
#include "resource-manager.h" |
41 |
#include "util.h" |
#include "util.h" |
42 |
|
|
885 |
output_statement(node, f, indent); |
output_statement(node, f, indent); |
886 |
} |
} |
887 |
|
|
888 |
void jscoverage_instrument_js(const char * id, const char * encoding, Stream * input, Stream * output) { |
void jscoverage_instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output) { |
889 |
file_id = id; |
file_id = id; |
890 |
|
|
891 |
/* scan the javascript */ |
/* scan the javascript */ |
892 |
size_t num_characters = input->length; |
JSTokenStream * token_stream = js_NewTokenStream(context, characters, num_characters, NULL, 1, NULL); |
|
jschar * base = NULL; |
|
|
int result = jscoverage_bytes_to_characters(encoding, input->data, input->length, &base, &num_characters); |
|
|
if (result == JSCOVERAGE_ERROR_ENCODING_NOT_SUPPORTED) { |
|
|
fatal("encoding %s not supported in file %s", encoding, id); |
|
|
} |
|
|
else if (result == JSCOVERAGE_ERROR_INVALID_BYTE_SEQUENCE) { |
|
|
fatal("error decoding %s in file %s", encoding, id); |
|
|
} |
|
|
JSTokenStream * token_stream = js_NewTokenStream(context, base, num_characters, NULL, 1, NULL); |
|
893 |
if (token_stream == NULL) { |
if (token_stream == NULL) { |
894 |
fatal("cannot create token stream from file: %s", file_id); |
fatal("cannot create token stream from file: %s", file_id); |
895 |
} |
} |
932 |
|
|
933 |
/* copy the instrumented source code to the output */ |
/* copy the instrumented source code to the output */ |
934 |
Stream_write(output, instrumented->data, instrumented->length); |
Stream_write(output, instrumented->data, instrumented->length); |
|
Stream_write_char(output, '\n'); |
|
935 |
|
|
936 |
/* conditionals */ |
/* conditionals */ |
937 |
bool has_conditionals = false; |
bool has_conditionals = false; |
940 |
while (i < num_characters) { |
while (i < num_characters) { |
941 |
line_number++; |
line_number++; |
942 |
size_t line_start = i; |
size_t line_start = i; |
943 |
while (i < num_characters && base[i] != '\r' && base[i] != '\n') { |
/* FIXME */ |
944 |
|
while (i < num_characters && characters[i] != '\r' && characters[i] != '\n') { |
945 |
i++; |
i++; |
946 |
} |
} |
947 |
size_t line_end = i; |
size_t line_end = i; |
948 |
if (i < num_characters) { |
if (i < num_characters) { |
949 |
if (base[i] == '\r') { |
if (characters[i] == '\r') { |
950 |
line_end = i; |
line_end = i; |
951 |
i++; |
i++; |
952 |
if (i < num_characters && base[i] == '\n') { |
if (i < num_characters && characters[i] == '\n') { |
953 |
i++; |
i++; |
954 |
} |
} |
955 |
} |
} |
956 |
else if (base[i] == '\n') { |
else if (characters[i] == '\n') { |
957 |
line_end = i; |
line_end = i; |
958 |
i++; |
i++; |
959 |
} |
} |
961 |
abort(); |
abort(); |
962 |
} |
} |
963 |
} |
} |
964 |
char * line = js_DeflateString(context, base + line_start, line_end - line_start); |
char * line = js_DeflateString(context, characters + line_start, line_end - line_start); |
965 |
if (str_starts_with(line, "//#JSCOVERAGE_IF")) { |
if (str_starts_with(line, "//#JSCOVERAGE_IF")) { |
966 |
if (! has_conditionals) { |
if (! has_conditionals) { |
967 |
has_conditionals = true; |
has_conditionals = true; |
978 |
} |
} |
979 |
|
|
980 |
/* copy the original source to the output */ |
/* copy the original source to the output */ |
981 |
i = 0; |
Stream_printf(output, "_$jscoverage['%s'].source = ", file_id); |
982 |
while (i < num_characters) { |
jscoverage_write_source(id, characters, num_characters, output); |
983 |
Stream_write_string(output, "// "); |
Stream_printf(output, ";\n"); |
|
size_t line_start = i; |
|
|
while (i < num_characters && base[i] != '\r' && base[i] != '\n') { |
|
|
i++; |
|
|
} |
|
984 |
|
|
985 |
size_t line_end = i; |
Stream_delete(instrumented); |
986 |
if (i < num_characters) { |
|
987 |
if (base[i] == '\r') { |
file_id = NULL; |
988 |
line_end = i; |
} |
989 |
|
|
990 |
|
void jscoverage_write_source(const char * id, const jschar * characters, size_t num_characters, Stream * output) { |
991 |
|
Stream_write_string(output, "["); |
992 |
|
if (jscoverage_highlight) { |
993 |
|
Stream * highlighted_stream = Stream_new(num_characters); |
994 |
|
jscoverage_highlight_js(context, id, characters, num_characters, highlighted_stream); |
995 |
|
size_t i = 0; |
996 |
|
while (i < highlighted_stream->length) { |
997 |
|
if (i > 0) { |
998 |
|
Stream_write_char(output, ','); |
999 |
|
} |
1000 |
|
|
1001 |
|
Stream_write_char(output, '"'); |
1002 |
|
bool done = false; |
1003 |
|
while (! done) { |
1004 |
|
char c = highlighted_stream->data[i]; |
1005 |
|
switch (c) { |
1006 |
|
case 0x8: |
1007 |
|
/* backspace */ |
1008 |
|
Stream_write_string(output, "\\b"); |
1009 |
|
break; |
1010 |
|
case 0x9: |
1011 |
|
/* horizontal tab */ |
1012 |
|
Stream_write_string(output, "\\t"); |
1013 |
|
break; |
1014 |
|
case 0xa: |
1015 |
|
/* line feed (new line) */ |
1016 |
|
done = true; |
1017 |
|
break; |
1018 |
|
case 0xb: |
1019 |
|
/* vertical tab */ |
1020 |
|
Stream_write_string(output, "\\v"); |
1021 |
|
break; |
1022 |
|
case 0xc: |
1023 |
|
/* form feed */ |
1024 |
|
Stream_write_string(output, "\\f"); |
1025 |
|
break; |
1026 |
|
case 0xd: |
1027 |
|
/* carriage return */ |
1028 |
|
done = true; |
1029 |
|
if (i + 1 < highlighted_stream->length && highlighted_stream->data[i + 1] == '\n') { |
1030 |
|
i++; |
1031 |
|
} |
1032 |
|
break; |
1033 |
|
case '"': |
1034 |
|
Stream_write_string(output, "\\\""); |
1035 |
|
break; |
1036 |
|
case '\\': |
1037 |
|
Stream_write_string(output, "\\\\"); |
1038 |
|
break; |
1039 |
|
default: |
1040 |
|
Stream_write_char(output, c); |
1041 |
|
break; |
1042 |
|
} |
1043 |
i++; |
i++; |
1044 |
if (i < num_characters && base[i] == '\n') { |
if (i >= highlighted_stream->length) { |
1045 |
i++; |
done = true; |
1046 |
} |
} |
1047 |
} |
} |
1048 |
else if (base[i] == '\n') { |
Stream_write_char(output, '"'); |
1049 |
line_end = i; |
} |
1050 |
|
Stream_delete(highlighted_stream); |
1051 |
|
} |
1052 |
|
else { |
1053 |
|
size_t i = 0; |
1054 |
|
while (i < num_characters) { |
1055 |
|
if (i > 0) { |
1056 |
|
Stream_write_char(output, ','); |
1057 |
|
} |
1058 |
|
|
1059 |
|
Stream_write_char(output, '"'); |
1060 |
|
bool done = false; |
1061 |
|
while (! done) { |
1062 |
|
jschar c = characters[i]; |
1063 |
|
switch (c) { |
1064 |
|
case 0x8: |
1065 |
|
/* backspace */ |
1066 |
|
Stream_write_string(output, "\\b"); |
1067 |
|
break; |
1068 |
|
case 0x9: |
1069 |
|
/* horizontal tab */ |
1070 |
|
Stream_write_string(output, "\\t"); |
1071 |
|
break; |
1072 |
|
case 0xa: |
1073 |
|
/* line feed (new line) */ |
1074 |
|
done = true; |
1075 |
|
break; |
1076 |
|
case 0xb: |
1077 |
|
/* vertical tab */ |
1078 |
|
Stream_write_string(output, "\\v"); |
1079 |
|
break; |
1080 |
|
case 0xc: |
1081 |
|
/* form feed */ |
1082 |
|
Stream_write_string(output, "\\f"); |
1083 |
|
break; |
1084 |
|
case 0xd: |
1085 |
|
/* carriage return */ |
1086 |
|
done = true; |
1087 |
|
if (i + 1 < num_characters && characters[i + 1] == '\n') { |
1088 |
|
i++; |
1089 |
|
} |
1090 |
|
break; |
1091 |
|
case '"': |
1092 |
|
Stream_write_string(output, "\\\""); |
1093 |
|
break; |
1094 |
|
case '\\': |
1095 |
|
Stream_write_string(output, "\\\\"); |
1096 |
|
break; |
1097 |
|
case '&': |
1098 |
|
Stream_write_string(output, "&"); |
1099 |
|
break; |
1100 |
|
case '<': |
1101 |
|
Stream_write_string(output, "<"); |
1102 |
|
break; |
1103 |
|
case '>': |
1104 |
|
Stream_write_string(output, ">"); |
1105 |
|
break; |
1106 |
|
case 0x2028: |
1107 |
|
case 0x2029: |
1108 |
|
done = true; |
1109 |
|
break; |
1110 |
|
default: |
1111 |
|
if (32 <= c && c <= 126) { |
1112 |
|
Stream_write_char(output, c); |
1113 |
|
} |
1114 |
|
else { |
1115 |
|
Stream_printf(output, "&#%d;", c); |
1116 |
|
} |
1117 |
|
break; |
1118 |
|
} |
1119 |
i++; |
i++; |
1120 |
|
if (i >= num_characters) { |
1121 |
|
done = true; |
1122 |
|
} |
1123 |
} |
} |
1124 |
else { |
Stream_write_char(output, '"'); |
|
abort(); |
|
|
} |
|
1125 |
} |
} |
|
|
|
|
char * line = js_DeflateString(context, base + line_start, line_end - line_start); |
|
|
Stream_write_string(output, line); |
|
|
Stream_write_char(output, '\n'); |
|
|
JS_free(context, line); |
|
1126 |
} |
} |
1127 |
|
Stream_write_string(output, "]"); |
|
Stream_delete(instrumented); |
|
|
|
|
|
JS_free(context, base); |
|
|
|
|
|
file_id = NULL; |
|
1128 |
} |
} |
1129 |
|
|
1130 |
void jscoverage_copy_resources(const char * destination_directory) { |
void jscoverage_copy_resources(const char * destination_directory) { |
1133 |
copy_resource("jscoverage.js", destination_directory); |
copy_resource("jscoverage.js", destination_directory); |
1134 |
copy_resource("jscoverage-ie.css", destination_directory); |
copy_resource("jscoverage-ie.css", destination_directory); |
1135 |
copy_resource("jscoverage-throbber.gif", destination_directory); |
copy_resource("jscoverage-throbber.gif", destination_directory); |
1136 |
copy_resource("jscoverage-sh_main.js", destination_directory); |
copy_resource("jscoverage-highlight.css", destination_directory); |
|
copy_resource("jscoverage-sh_javascript.js", destination_directory); |
|
|
copy_resource("jscoverage-sh_nedit.css", destination_directory); |
|
1137 |
} |
} |
1138 |
|
|
1139 |
/* |
/* |
1168 |
JS_HashTableDestroy(coverage->coverage_table); |
JS_HashTableDestroy(coverage->coverage_table); |
1169 |
struct FileCoverageList * p = coverage->coverage_list; |
struct FileCoverageList * p = coverage->coverage_list; |
1170 |
while (p != NULL) { |
while (p != NULL) { |
1171 |
free(p->file_coverage->lines); |
free(p->file_coverage->coverage_lines); |
1172 |
free(p->file_coverage->source); |
if (p->file_coverage->source_lines != NULL) { |
1173 |
|
for (uint32 i = 0; i < p->file_coverage->num_source_lines; i++) { |
1174 |
|
free(p->file_coverage->source_lines[i]); |
1175 |
|
} |
1176 |
|
free(p->file_coverage->source_lines); |
1177 |
|
} |
1178 |
free(p->file_coverage->id); |
free(p->file_coverage->id); |
1179 |
free(p->file_coverage); |
free(p->file_coverage); |
1180 |
struct FileCoverageList * q = p; |
struct FileCoverageList * q = p; |
1296 |
} |
} |
1297 |
else if (strcmp(s, "source") == 0) { |
else if (strcmp(s, "source") == 0) { |
1298 |
source = element->pn_right; |
source = element->pn_right; |
1299 |
if (source->pn_type != TOK_STRING || ! ATOM_IS_STRING(source->pn_atom)) { |
if (source->pn_type != TOK_RB) { |
1300 |
return -1; |
return -1; |
1301 |
} |
} |
1302 |
} |
} |
1320 |
char * id = xstrdup(id_bytes); |
char * id = xstrdup(id_bytes); |
1321 |
file_coverage = xmalloc(sizeof(FileCoverage)); |
file_coverage = xmalloc(sizeof(FileCoverage)); |
1322 |
file_coverage->id = id; |
file_coverage->id = id; |
1323 |
file_coverage->num_lines = array->pn_count - 1; |
file_coverage->num_coverage_lines = array->pn_count; |
1324 |
file_coverage->lines = xnew(int, array->pn_count); |
file_coverage->coverage_lines = xnew(int, array->pn_count); |
1325 |
if (source == NULL) { |
if (source == NULL) { |
1326 |
file_coverage->source = NULL; |
file_coverage->source_lines = NULL; |
1327 |
} |
} |
1328 |
else { |
else { |
1329 |
file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom))); |
file_coverage->num_source_lines = source->pn_count; |
1330 |
|
file_coverage->source_lines = xnew(char *, source->pn_count); |
1331 |
|
uint32 i = 0; |
1332 |
|
for (JSParseNode * element = source->pn_head; element != NULL; element = element->pn_next, i++) { |
1333 |
|
if (element->pn_type != TOK_STRING) { |
1334 |
|
return -1; |
1335 |
|
} |
1336 |
|
file_coverage->source_lines[i] = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(element->pn_atom))); |
1337 |
|
} |
1338 |
|
assert(i == source->pn_count); |
1339 |
} |
} |
1340 |
|
|
1341 |
/* set coverage for all lines */ |
/* set coverage for all lines */ |
1342 |
uint32 i = 0; |
uint32 i = 0; |
1343 |
for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) { |
for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) { |
1344 |
if (element->pn_type == TOK_NUMBER) { |
if (element->pn_type == TOK_NUMBER) { |
1345 |
file_coverage->lines[i] = (int) element->pn_dval; |
file_coverage->coverage_lines[i] = (int) element->pn_dval; |
1346 |
} |
} |
1347 |
else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) { |
else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) { |
1348 |
file_coverage->lines[i] = -1; |
file_coverage->coverage_lines[i] = -1; |
1349 |
} |
} |
1350 |
else { |
else { |
1351 |
return -1; |
return -1; |
1363 |
else { |
else { |
1364 |
/* sanity check */ |
/* sanity check */ |
1365 |
assert(strcmp(file_coverage->id, id_bytes) == 0); |
assert(strcmp(file_coverage->id, id_bytes) == 0); |
1366 |
if (file_coverage->num_lines != array->pn_count - 1) { |
if (file_coverage->num_coverage_lines != array->pn_count) { |
1367 |
return -2; |
return -2; |
1368 |
} |
} |
1369 |
|
|
1371 |
uint32 i = 0; |
uint32 i = 0; |
1372 |
for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) { |
for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) { |
1373 |
if (element->pn_type == TOK_NUMBER) { |
if (element->pn_type == TOK_NUMBER) { |
1374 |
if (file_coverage->lines[i] == -1) { |
if (file_coverage->coverage_lines[i] == -1) { |
1375 |
return -2; |
return -2; |
1376 |
} |
} |
1377 |
file_coverage->lines[i] += (int) element->pn_dval; |
file_coverage->coverage_lines[i] += (int) element->pn_dval; |
1378 |
} |
} |
1379 |
else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) { |
else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) { |
1380 |
if (file_coverage->lines[i] != -1) { |
if (file_coverage->coverage_lines[i] != -1) { |
1381 |
return -2; |
return -2; |
1382 |
} |
} |
1383 |
} |
} |
1388 |
assert(i == array->pn_count); |
assert(i == array->pn_count); |
1389 |
|
|
1390 |
/* if this JSON file has source, use it */ |
/* if this JSON file has source, use it */ |
1391 |
if (file_coverage->source == NULL && source != NULL) { |
if (file_coverage->source_lines == NULL && source != NULL) { |
1392 |
file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom))); |
file_coverage->num_source_lines = source->pn_count; |
1393 |
|
file_coverage->source_lines = xnew(char *, source->pn_count); |
1394 |
|
uint32 i = 0; |
1395 |
|
for (JSParseNode * element = source->pn_head; element != NULL; element = element->pn_next, i++) { |
1396 |
|
if (element->pn_type != TOK_STRING) { |
1397 |
|
return -1; |
1398 |
|
} |
1399 |
|
file_coverage->source_lines[i] = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(element->pn_atom))); |
1400 |
|
} |
1401 |
|
assert(i == source->pn_count); |
1402 |
} |
} |
1403 |
} |
} |
1404 |
} |
} |