17 |
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
18 |
*/ |
*/ |
19 |
|
|
20 |
|
#include <config.h> |
21 |
|
|
22 |
#include "instrument-js.h" |
#include "instrument-js.h" |
23 |
|
|
24 |
#include <assert.h> |
#include <assert.h> |
34 |
#include <jsscope.h> |
#include <jsscope.h> |
35 |
#include <jsstr.h> |
#include <jsstr.h> |
36 |
|
|
37 |
|
#include "resource-manager.h" |
38 |
#include "util.h" |
#include "util.h" |
39 |
|
|
40 |
static JSRuntime * runtime = NULL; |
static JSRuntime * runtime = NULL; |
167 |
|
|
168 |
/* function parameters */ |
/* function parameters */ |
169 |
Stream_write_string(f, "("); |
Stream_write_string(f, "("); |
170 |
JSAtom ** params = xmalloc(function->nargs * sizeof(JSAtom *)); |
JSAtom ** params = xnew(JSAtom *, function->nargs); |
171 |
for (int i = 0; i < function->nargs; i++) { |
for (int i = 0; i < function->nargs; i++) { |
172 |
/* initialize to NULL for sanity check */ |
/* initialize to NULL for sanity check */ |
173 |
params[i] = NULL; |
params[i] = NULL; |
809 |
|
|
810 |
/* scan the javascript */ |
/* scan the javascript */ |
811 |
size_t input_length = input->length; |
size_t input_length = input->length; |
812 |
jschar * base = js_InflateString(context, input->data, &input_length); |
jschar * base = js_InflateString(context, (char *) input->data, &input_length); |
813 |
if (base == NULL) { |
if (base == NULL) { |
814 |
fatal("out of memory"); |
fatal("out of memory"); |
815 |
} |
} |
833 |
An instrumented JavaScript file has 3 sections: |
An instrumented JavaScript file has 3 sections: |
834 |
1. initialization |
1. initialization |
835 |
2. instrumented source code |
2. instrumented source code |
836 |
3. original source code (TODO) |
3. original source code |
837 |
*/ |
*/ |
838 |
|
|
839 |
Stream * instrumented = Stream_new(0); |
Stream * instrumented = Stream_new(0); |
897 |
|
|
898 |
file_id = NULL; |
file_id = NULL; |
899 |
} |
} |
900 |
|
|
901 |
|
void jscoverage_copy_resources(const char * destination_directory) { |
902 |
|
copy_resource("jscoverage.html", destination_directory); |
903 |
|
copy_resource("jscoverage.css", destination_directory); |
904 |
|
copy_resource("jscoverage.js", destination_directory); |
905 |
|
copy_resource("jscoverage-throbber.gif", destination_directory); |
906 |
|
copy_resource("jscoverage-sh_main.js", destination_directory); |
907 |
|
copy_resource("jscoverage-sh_javascript.js", destination_directory); |
908 |
|
copy_resource("jscoverage-sh_nedit.css", destination_directory); |
909 |
|
} |
910 |
|
|
911 |
|
/* |
912 |
|
coverage reports |
913 |
|
*/ |
914 |
|
|
915 |
|
struct FileCoverageList { |
916 |
|
FileCoverage * file_coverage; |
917 |
|
struct FileCoverageList * next; |
918 |
|
}; |
919 |
|
|
920 |
|
struct Coverage { |
921 |
|
JSHashTable * coverage_table; |
922 |
|
struct FileCoverageList * coverage_list; |
923 |
|
}; |
924 |
|
|
925 |
|
static int compare_strings(const void * p1, const void * p2) { |
926 |
|
return strcmp(p1, p2) == 0; |
927 |
|
} |
928 |
|
|
929 |
|
Coverage * Coverage_new(void) { |
930 |
|
Coverage * result = xmalloc(sizeof(Coverage)); |
931 |
|
result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL); |
932 |
|
if (result->coverage_table == NULL) { |
933 |
|
fatal("cannot create hash table"); |
934 |
|
} |
935 |
|
result->coverage_list = NULL; |
936 |
|
return result; |
937 |
|
} |
938 |
|
|
939 |
|
void Coverage_delete(Coverage * coverage) { |
940 |
|
JS_HashTableDestroy(coverage->coverage_table); |
941 |
|
struct FileCoverageList * p = coverage->coverage_list; |
942 |
|
while (p != NULL) { |
943 |
|
free(p->file_coverage->lines); |
944 |
|
free(p->file_coverage->source); |
945 |
|
free(p->file_coverage->id); |
946 |
|
free(p->file_coverage); |
947 |
|
struct FileCoverageList * q = p; |
948 |
|
p = p->next; |
949 |
|
free(q); |
950 |
|
} |
951 |
|
free(coverage); |
952 |
|
} |
953 |
|
|
954 |
|
struct EnumeratorArg { |
955 |
|
CoverageForeachFunction f; |
956 |
|
void * p; |
957 |
|
}; |
958 |
|
|
959 |
|
static intN enumerator(JSHashEntry * entry, intN i, void * arg) { |
960 |
|
struct EnumeratorArg * enumerator_arg = arg; |
961 |
|
enumerator_arg->f(entry->value, i, enumerator_arg->p); |
962 |
|
return 0; |
963 |
|
} |
964 |
|
|
965 |
|
void Coverage_foreach_file(Coverage * coverage, CoverageForeachFunction f, void * p) { |
966 |
|
struct EnumeratorArg enumerator_arg; |
967 |
|
enumerator_arg.f = f; |
968 |
|
enumerator_arg.p = p; |
969 |
|
JS_HashTableEnumerateEntries(coverage->coverage_table, enumerator, &enumerator_arg); |
970 |
|
} |
971 |
|
|
972 |
|
int jscoverage_parse_json(Coverage * coverage, const uint8_t * json, size_t length) { |
973 |
|
jschar * base = js_InflateString(context, (char *) json, &length); |
974 |
|
if (base == NULL) { |
975 |
|
fatal("out of memory"); |
976 |
|
} |
977 |
|
|
978 |
|
jschar * parenthesized_json = xnew(jschar, addst(length, 2)); |
979 |
|
parenthesized_json[0] = '('; |
980 |
|
memcpy(parenthesized_json + 1, base, mulst(length, sizeof(jschar))); |
981 |
|
parenthesized_json[length + 1] = ')'; |
982 |
|
|
983 |
|
JS_free(context, base); |
984 |
|
|
985 |
|
JSTokenStream * token_stream = js_NewTokenStream(context, parenthesized_json, length + 2, NULL, 1, NULL); |
986 |
|
if (token_stream == NULL) { |
987 |
|
fatal("cannot create token stream"); |
988 |
|
} |
989 |
|
|
990 |
|
JSParseNode * root = js_ParseTokenStream(context, global, token_stream); |
991 |
|
free(parenthesized_json); |
992 |
|
if (root == NULL) { |
993 |
|
return -1; |
994 |
|
} |
995 |
|
|
996 |
|
/* root node must be TOK_LC */ |
997 |
|
if (root->pn_type != TOK_LC) { |
998 |
|
return -1; |
999 |
|
} |
1000 |
|
JSParseNode * semi = root->pn_u.list.head; |
1001 |
|
|
1002 |
|
/* the list must be TOK_SEMI and it must contain only one element */ |
1003 |
|
if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) { |
1004 |
|
return -1; |
1005 |
|
} |
1006 |
|
JSParseNode * parenthesized = semi->pn_kid; |
1007 |
|
|
1008 |
|
/* this must be a parenthesized expression */ |
1009 |
|
if (parenthesized->pn_type != TOK_RP) { |
1010 |
|
return -1; |
1011 |
|
} |
1012 |
|
JSParseNode * object = parenthesized->pn_kid; |
1013 |
|
|
1014 |
|
/* this must be an object literal */ |
1015 |
|
if (object->pn_type != TOK_RC) { |
1016 |
|
return -1; |
1017 |
|
} |
1018 |
|
|
1019 |
|
for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) { |
1020 |
|
/* every element of this list must be TOK_COLON */ |
1021 |
|
if (p->pn_type != TOK_COLON) { |
1022 |
|
return -1; |
1023 |
|
} |
1024 |
|
|
1025 |
|
/* the key must be a string representing the file */ |
1026 |
|
JSParseNode * key = p->pn_left; |
1027 |
|
if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) { |
1028 |
|
return -1; |
1029 |
|
} |
1030 |
|
char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom)); |
1031 |
|
|
1032 |
|
/* the value must be an object literal OR an array */ |
1033 |
|
JSParseNode * value = p->pn_right; |
1034 |
|
if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) { |
1035 |
|
return -1; |
1036 |
|
} |
1037 |
|
|
1038 |
|
JSParseNode * array = NULL; |
1039 |
|
JSParseNode * source = NULL; |
1040 |
|
if (value->pn_type == TOK_RB) { |
1041 |
|
/* an array */ |
1042 |
|
array = value; |
1043 |
|
} |
1044 |
|
else if (value->pn_type == TOK_RC) { |
1045 |
|
/* an object literal */ |
1046 |
|
if (value->pn_count != 2) { |
1047 |
|
return -1; |
1048 |
|
} |
1049 |
|
for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) { |
1050 |
|
if (element->pn_type != TOK_COLON) { |
1051 |
|
return -1; |
1052 |
|
} |
1053 |
|
JSParseNode * left = element->pn_left; |
1054 |
|
if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) { |
1055 |
|
return -1; |
1056 |
|
} |
1057 |
|
const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom)); |
1058 |
|
if (strcmp(s, "coverage") == 0) { |
1059 |
|
array = element->pn_right; |
1060 |
|
if (array->pn_type != TOK_RB) { |
1061 |
|
return -1; |
1062 |
|
} |
1063 |
|
} |
1064 |
|
else if (strcmp(s, "source") == 0) { |
1065 |
|
source = element->pn_right; |
1066 |
|
if (source->pn_type != TOK_STRING || ! ATOM_IS_STRING(source->pn_atom)) { |
1067 |
|
return -1; |
1068 |
|
} |
1069 |
|
} |
1070 |
|
else { |
1071 |
|
return -1; |
1072 |
|
} |
1073 |
|
} |
1074 |
|
} |
1075 |
|
else { |
1076 |
|
return -1; |
1077 |
|
} |
1078 |
|
|
1079 |
|
if (array == NULL) { |
1080 |
|
return -1; |
1081 |
|
} |
1082 |
|
|
1083 |
|
/* look up the file in the coverage table */ |
1084 |
|
FileCoverage * file_coverage = JS_HashTableLookup(coverage->coverage_table, id_bytes); |
1085 |
|
if (file_coverage == NULL) { |
1086 |
|
/* not there: create a new one */ |
1087 |
|
char * id = xstrdup(id_bytes); |
1088 |
|
file_coverage = xmalloc(sizeof(FileCoverage)); |
1089 |
|
file_coverage->id = id; |
1090 |
|
file_coverage->num_lines = array->pn_count - 1; |
1091 |
|
file_coverage->lines = xnew(int, array->pn_count); |
1092 |
|
if (source == NULL) { |
1093 |
|
file_coverage->source = NULL; |
1094 |
|
} |
1095 |
|
else { |
1096 |
|
file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom))); |
1097 |
|
} |
1098 |
|
|
1099 |
|
/* set coverage for all lines */ |
1100 |
|
uint32 i = 0; |
1101 |
|
for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) { |
1102 |
|
if (element->pn_type == TOK_NUMBER) { |
1103 |
|
file_coverage->lines[i] = (int) element->pn_dval; |
1104 |
|
} |
1105 |
|
else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) { |
1106 |
|
file_coverage->lines[i] = -1; |
1107 |
|
} |
1108 |
|
else { |
1109 |
|
return -1; |
1110 |
|
} |
1111 |
|
} |
1112 |
|
assert(i == array->pn_count); |
1113 |
|
|
1114 |
|
/* add to the hash table */ |
1115 |
|
JS_HashTableAdd(coverage->coverage_table, id, file_coverage); |
1116 |
|
struct FileCoverageList * coverage_list = xmalloc(sizeof(struct FileCoverageList)); |
1117 |
|
coverage_list->file_coverage = file_coverage; |
1118 |
|
coverage_list->next = coverage->coverage_list; |
1119 |
|
coverage->coverage_list = coverage_list; |
1120 |
|
} |
1121 |
|
else { |
1122 |
|
/* sanity check */ |
1123 |
|
assert(strcmp(file_coverage->id, id_bytes) == 0); |
1124 |
|
if (file_coverage->num_lines != array->pn_count - 1) { |
1125 |
|
return -2; |
1126 |
|
} |
1127 |
|
|
1128 |
|
/* merge the coverage */ |
1129 |
|
uint32 i = 0; |
1130 |
|
for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) { |
1131 |
|
if (element->pn_type == TOK_NUMBER) { |
1132 |
|
if (file_coverage->lines[i] == -1) { |
1133 |
|
return -2; |
1134 |
|
} |
1135 |
|
file_coverage->lines[i] += (int) element->pn_dval; |
1136 |
|
} |
1137 |
|
else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) { |
1138 |
|
if (file_coverage->lines[i] != -1) { |
1139 |
|
return -2; |
1140 |
|
} |
1141 |
|
} |
1142 |
|
else { |
1143 |
|
return -1; |
1144 |
|
} |
1145 |
|
} |
1146 |
|
assert(i == array->pn_count); |
1147 |
|
|
1148 |
|
/* if this JSON file has source, use it */ |
1149 |
|
if (file_coverage->source == NULL && source != NULL) { |
1150 |
|
file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom))); |
1151 |
|
} |
1152 |
|
} |
1153 |
|
} |
1154 |
|
|
1155 |
|
return 0; |
1156 |
|
} |