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

Diff of /trunk/instrument-js.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 101 by siliconforks, Sat May 24 18:11:30 2008 UTC revision 169 by siliconforks, Mon Sep 15 16:22:41 2008 UTC
# Line 17  Line 17 
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>
# Line 32  Line 34 
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;
# Line 145  Line 148 
148  static void instrument_expression(JSParseNode * node, Stream * f);  static void instrument_expression(JSParseNode * node, Stream * f);
149  static void instrument_statement(JSParseNode * node, Stream * f, int indent);  static void instrument_statement(JSParseNode * node, Stream * f, int indent);
150    
151  static void instrument_function(JSParseNode * node, Stream * f, int indent) {  enum FunctionType {
152      assert(node->pn_arity == PN_FUNC);    FUNCTION_NORMAL,
153      assert(ATOM_IS_OBJECT(node->pn_funAtom));    FUNCTION_GETTER_OR_SETTER
154      JSObject * object = ATOM_TO_OBJECT(node->pn_funAtom);  };
155      assert(JS_ObjectIsFunction(context, object));  
156      JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);  static void instrument_function(JSParseNode * node, Stream * f, int indent, enum FunctionType type) {
157      assert(function);    assert(node->pn_arity == PN_FUNC);
158      assert(object == function->object);    assert(ATOM_IS_OBJECT(node->pn_funAtom));
159      Stream_printf(f, "%*s", indent, "");    JSObject * object = ATOM_TO_OBJECT(node->pn_funAtom);
160      assert(JS_ObjectIsFunction(context, object));
161      JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);
162      assert(function);
163      assert(object == function->object);
164      Stream_printf(f, "%*s", indent, "");
165      if (type == FUNCTION_NORMAL) {
166      Stream_write_string(f, "function");      Stream_write_string(f, "function");
167      }
168    
169      /* function name */    /* function name */
170      if (function->atom) {    if (function->atom) {
171        Stream_write_char(f, ' ');      Stream_write_char(f, ' ');
172        print_string_atom(function->atom, f);      print_string_atom(function->atom, f);
173      }    }
174    
175      /* function parameters */    /* function parameters */
176      Stream_write_string(f, "(");    Stream_write_string(f, "(");
177      JSAtom ** params = xmalloc(function->nargs * sizeof(JSAtom *));    JSAtom ** params = xnew(JSAtom *, function->nargs);
178      for (int i = 0; i < function->nargs; i++) {    for (int i = 0; i < function->nargs; i++) {
179        /* initialize to NULL for sanity check */      /* initialize to NULL for sanity check */
180        params[i] = NULL;      params[i] = NULL;
181      }    }
182      JSScope * scope = OBJ_SCOPE(object);    JSScope * scope = OBJ_SCOPE(object);
183      for (JSScopeProperty * scope_property = SCOPE_LAST_PROP(scope); scope_property != NULL; scope_property = scope_property->parent) {    for (JSScopeProperty * scope_property = SCOPE_LAST_PROP(scope); scope_property != NULL; scope_property = scope_property->parent) {
184        if (scope_property->getter != js_GetArgument) {      if (scope_property->getter != js_GetArgument) {
185          continue;        continue;
186        }      }
187        assert(scope_property->flags & SPROP_HAS_SHORTID);      assert(scope_property->flags & SPROP_HAS_SHORTID);
188        assert((uint16) scope_property->shortid < function->nargs);      assert((uint16) scope_property->shortid < function->nargs);
189        assert(JSID_IS_ATOM(scope_property->id));      assert(JSID_IS_ATOM(scope_property->id));
190        params[(uint16) scope_property->shortid] = JSID_TO_ATOM(scope_property->id);      params[(uint16) scope_property->shortid] = JSID_TO_ATOM(scope_property->id);
191      }    }
192      for (int i = 0; i < function->nargs; i++) {    for (int i = 0; i < function->nargs; i++) {
193        assert(params[i] != NULL);      assert(params[i] != NULL);
194        if (i > 0) {      if (i > 0) {
195          Stream_write_string(f, ", ");        Stream_write_string(f, ", ");
       }  
       if (ATOM_IS_STRING(params[i])) {  
         print_string_atom(params[i], f);  
       }  
196      }      }
197      Stream_write_string(f, ") {\n");      if (ATOM_IS_STRING(params[i])) {
198      free(params);        print_string_atom(params[i], f);
199        }
200      }
201      Stream_write_string(f, ") {\n");
202      free(params);
203    
204      /* function body */    /* function body */
205      instrument_statement(node->pn_body, f, indent + 2);    instrument_statement(node->pn_body, f, indent + 2);
206    
207      Stream_write_string(f, "}\n");    Stream_write_string(f, "}\n");
208  }  }
209    
210  static void instrument_function_call(JSParseNode * node, Stream * f) {  static void instrument_function_call(JSParseNode * node, Stream * f) {
# Line 225  Line 235 
235  static void instrument_expression(JSParseNode * node, Stream * f) {  static void instrument_expression(JSParseNode * node, Stream * f) {
236    switch (node->pn_type) {    switch (node->pn_type) {
237    case TOK_FUNCTION:    case TOK_FUNCTION:
238      instrument_function(node, f, 0);      instrument_function(node, f, 0, FUNCTION_NORMAL);
239      break;      break;
240    case TOK_COMMA:    case TOK_COMMA:
241      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) {
# Line 432  Line 442 
442        if (p != node->pn_head) {        if (p != node->pn_head) {
443          Stream_write_string(f, ", ");          Stream_write_string(f, ", ");
444        }        }
445        instrument_expression(p->pn_left, f);  
446        Stream_write_string(f, ": ");        /* check whether this is a getter or setter */
447        instrument_expression(p->pn_right, f);        switch (p->pn_op) {
448          case JSOP_GETTER:
449            Stream_write_string(f, "get ");
450            instrument_expression(p->pn_left, f);
451            instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER);
452            break;
453          case JSOP_SETTER:
454            Stream_write_string(f, "set ");
455            instrument_expression(p->pn_left, f);
456            instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER);
457            break;
458          default:
459            instrument_expression(p->pn_left, f);
460            Stream_write_string(f, ": ");
461            instrument_expression(p->pn_right, f);
462            break;
463          }
464      }      }
465      Stream_write_char(f, '}');      Stream_write_char(f, '}');
466      break;      break;
# Line 538  Line 564 
564  static void output_statement(JSParseNode * node, Stream * f, int indent) {  static void output_statement(JSParseNode * node, Stream * f, int indent) {
565    switch (node->pn_type) {    switch (node->pn_type) {
566    case TOK_FUNCTION:    case TOK_FUNCTION:
567      instrument_function(node, f, indent);      instrument_function(node, f, indent, FUNCTION_NORMAL);
568      break;      break;
569    case TOK_LC:    case TOK_LC:
570      assert(node->pn_arity == PN_LIST);      assert(node->pn_arity == PN_LIST);
# Line 806  Line 832 
832    
833    /* scan the javascript */    /* scan the javascript */
834    size_t input_length = input->length;    size_t input_length = input->length;
835    jschar * base = js_InflateString(context, input->data, &input_length);    jschar * base = js_InflateString(context, (char *) input->data, &input_length);
836    if (base == NULL) {    if (base == NULL) {
837      fatal("out of memory");      fatal("out of memory");
838    }    }
# Line 830  Line 856 
856    An instrumented JavaScript file has 3 sections:    An instrumented JavaScript file has 3 sections:
857    1. initialization    1. initialization
858    2. instrumented source code    2. instrumented source code
859    3. original source code (TODO)    3. original source code
860    */    */
861    
862    Stream * instrumented = Stream_new(0);    Stream * instrumented = Stream_new(0);
# Line 855  Line 881 
881    Stream_write(output, instrumented->data, instrumented->length);    Stream_write(output, instrumented->data, instrumented->length);
882    Stream_write_char(output, '\n');    Stream_write_char(output, '\n');
883    
884    /* copy the original source to the output */    /* conditionals */
885      bool has_conditionals = false;
886      size_t line_number = 0;
887    size_t i = 0;    size_t i = 0;
888    while (i < input_length) {    while (i < input_length) {
889        line_number++;
890        size_t line_start = i;
891        while (i < input_length && base[i] != '\r' && base[i] != '\n') {
892          i++;
893        }
894        size_t line_end = i;
895        if (i < input_length) {
896          if (base[i] == '\r') {
897            line_end = i;
898            i++;
899            if (i < input_length && base[i] == '\n') {
900              i++;
901            }
902          }
903          else if (base[i] == '\n') {
904            line_end = i;
905            i++;
906          }
907          else {
908            abort();
909          }
910        }
911        char * line = js_DeflateString(context, base + line_start, line_end - line_start);
912        if (str_starts_with(line, "//#JSCOVERAGE_IF")) {
913          if (! has_conditionals) {
914            has_conditionals = true;
915            Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);
916          }
917          Stream_printf(output, "if (!%s) {\n", line + 16);
918          Stream_printf(output, "  _$jscoverage['%s'].conditionals[%d] = ", file_id, line_number);
919        }
920        else if (str_starts_with(line, "//#JSCOVERAGE_ENDIF")) {
921          Stream_printf(output, "%d;\n", line_number);
922          Stream_printf(output, "}\n");
923        }
924        JS_free(context, line);
925      }
926    
927      /* copy the original source to the output */
928      i = 0;
929      while (i < input_length) {
930      Stream_write_string(output, "// ");      Stream_write_string(output, "// ");
931      size_t line_start = i;      size_t line_start = i;
932      while (i < input_length && base[i] != '\r' && base[i] != '\n') {      while (i < input_length && base[i] != '\r' && base[i] != '\n') {
# Line 894  Line 963 
963    
964    file_id = NULL;    file_id = NULL;
965  }  }
966    
967    void jscoverage_copy_resources(const char * destination_directory) {
968      copy_resource("jscoverage.html", destination_directory);
969      copy_resource("jscoverage.css", destination_directory);
970      copy_resource("jscoverage.js", destination_directory);
971      copy_resource("jscoverage-ie.css", destination_directory);
972      copy_resource("jscoverage-throbber.gif", destination_directory);
973      copy_resource("jscoverage-sh_main.js", destination_directory);
974      copy_resource("jscoverage-sh_javascript.js", destination_directory);
975      copy_resource("jscoverage-sh_nedit.css", destination_directory);
976    }
977    
978    /*
979    coverage reports
980    */
981    
982    struct FileCoverageList {
983      FileCoverage * file_coverage;
984      struct FileCoverageList * next;
985    };
986    
987    struct Coverage {
988      JSHashTable * coverage_table;
989      struct FileCoverageList * coverage_list;
990    };
991    
992    static int compare_strings(const void * p1, const void * p2) {
993      return strcmp(p1, p2) == 0;
994    }
995    
996    Coverage * Coverage_new(void) {
997      Coverage * result = xmalloc(sizeof(Coverage));
998      result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL);
999      if (result->coverage_table == NULL) {
1000        fatal("cannot create hash table");
1001      }
1002      result->coverage_list = NULL;
1003      return result;
1004    }
1005    
1006    void Coverage_delete(Coverage * coverage) {
1007      JS_HashTableDestroy(coverage->coverage_table);
1008      struct FileCoverageList * p = coverage->coverage_list;
1009      while (p != NULL) {
1010        free(p->file_coverage->lines);
1011        free(p->file_coverage->source);
1012        free(p->file_coverage->id);
1013        free(p->file_coverage);
1014        struct FileCoverageList * q = p;
1015        p = p->next;
1016        free(q);
1017      }
1018      free(coverage);
1019    }
1020    
1021    struct EnumeratorArg {
1022      CoverageForeachFunction f;
1023      void * p;
1024    };
1025    
1026    static intN enumerator(JSHashEntry * entry, intN i, void * arg) {
1027      struct EnumeratorArg * enumerator_arg = arg;
1028      enumerator_arg->f(entry->value, i, enumerator_arg->p);
1029      return 0;
1030    }
1031    
1032    void Coverage_foreach_file(Coverage * coverage, CoverageForeachFunction f, void * p) {
1033      struct EnumeratorArg enumerator_arg;
1034      enumerator_arg.f = f;
1035      enumerator_arg.p = p;
1036      JS_HashTableEnumerateEntries(coverage->coverage_table, enumerator, &enumerator_arg);
1037    }
1038    
1039    int jscoverage_parse_json(Coverage * coverage, const uint8_t * json, size_t length) {
1040      jschar * base = js_InflateString(context, (char *) json, &length);
1041      if (base == NULL) {
1042        fatal("out of memory");
1043      }
1044    
1045      jschar * parenthesized_json = xnew(jschar, addst(length, 2));
1046      parenthesized_json[0] = '(';
1047      memcpy(parenthesized_json + 1, base, mulst(length, sizeof(jschar)));
1048      parenthesized_json[length + 1] = ')';
1049    
1050      JS_free(context, base);
1051    
1052      JSTokenStream * token_stream = js_NewTokenStream(context, parenthesized_json, length + 2, NULL, 1, NULL);
1053      if (token_stream == NULL) {
1054        fatal("cannot create token stream");
1055      }
1056    
1057      JSParseNode * root = js_ParseTokenStream(context, global, token_stream);
1058      free(parenthesized_json);
1059      if (root == NULL) {
1060        return -1;
1061      }
1062    
1063      /* root node must be TOK_LC */
1064      if (root->pn_type != TOK_LC) {
1065        return -1;
1066      }
1067      JSParseNode * semi = root->pn_u.list.head;
1068    
1069      /* the list must be TOK_SEMI and it must contain only one element */
1070      if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {
1071        return -1;
1072      }
1073      JSParseNode * parenthesized = semi->pn_kid;
1074    
1075      /* this must be a parenthesized expression */
1076      if (parenthesized->pn_type != TOK_RP) {
1077        return -1;
1078      }
1079      JSParseNode * object = parenthesized->pn_kid;
1080    
1081      /* this must be an object literal */
1082      if (object->pn_type != TOK_RC) {
1083        return -1;
1084      }
1085    
1086      for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {
1087        /* every element of this list must be TOK_COLON */
1088        if (p->pn_type != TOK_COLON) {
1089          return -1;
1090        }
1091    
1092        /* the key must be a string representing the file */
1093        JSParseNode * key = p->pn_left;
1094        if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {
1095          return -1;
1096        }
1097        char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));
1098    
1099        /* the value must be an object literal OR an array */
1100        JSParseNode * value = p->pn_right;
1101        if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {
1102          return -1;
1103        }
1104    
1105        JSParseNode * array = NULL;
1106        JSParseNode * source = NULL;
1107        if (value->pn_type == TOK_RB) {
1108          /* an array */
1109          array = value;
1110        }
1111        else if (value->pn_type == TOK_RC) {
1112          /* an object literal */
1113          if (value->pn_count != 2) {
1114            return -1;
1115          }
1116          for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {
1117            if (element->pn_type != TOK_COLON) {
1118              return -1;
1119            }
1120            JSParseNode * left = element->pn_left;
1121            if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {
1122              return -1;
1123            }
1124            const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));
1125            if (strcmp(s, "coverage") == 0) {
1126              array = element->pn_right;
1127              if (array->pn_type != TOK_RB) {
1128                return -1;
1129              }
1130            }
1131            else if (strcmp(s, "source") == 0) {
1132              source = element->pn_right;
1133              if (source->pn_type != TOK_STRING || ! ATOM_IS_STRING(source->pn_atom)) {
1134                return -1;
1135              }
1136            }
1137            else {
1138              return -1;
1139            }
1140          }
1141        }
1142        else {
1143          return -1;
1144        }
1145    
1146        if (array == NULL) {
1147          return -1;
1148        }
1149    
1150        /* look up the file in the coverage table */
1151        FileCoverage * file_coverage = JS_HashTableLookup(coverage->coverage_table, id_bytes);
1152        if (file_coverage == NULL) {
1153          /* not there: create a new one */
1154          char * id = xstrdup(id_bytes);
1155          file_coverage = xmalloc(sizeof(FileCoverage));
1156          file_coverage->id = id;
1157          file_coverage->num_lines = array->pn_count - 1;
1158          file_coverage->lines = xnew(int, array->pn_count);
1159          if (source == NULL) {
1160            file_coverage->source = NULL;
1161          }
1162          else {
1163            file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom)));
1164          }
1165    
1166          /* set coverage for all lines */
1167          uint32 i = 0;
1168          for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1169            if (element->pn_type == TOK_NUMBER) {
1170              file_coverage->lines[i] = (int) element->pn_dval;
1171            }
1172            else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1173              file_coverage->lines[i] = -1;
1174            }
1175            else {
1176              return -1;
1177            }
1178          }
1179          assert(i == array->pn_count);
1180    
1181          /* add to the hash table */
1182          JS_HashTableAdd(coverage->coverage_table, id, file_coverage);
1183          struct FileCoverageList * coverage_list = xmalloc(sizeof(struct FileCoverageList));
1184          coverage_list->file_coverage = file_coverage;
1185          coverage_list->next = coverage->coverage_list;
1186          coverage->coverage_list = coverage_list;
1187        }
1188        else {
1189          /* sanity check */
1190          assert(strcmp(file_coverage->id, id_bytes) == 0);
1191          if (file_coverage->num_lines != array->pn_count - 1) {
1192            return -2;
1193          }
1194    
1195          /* merge the coverage */
1196          uint32 i = 0;
1197          for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1198            if (element->pn_type == TOK_NUMBER) {
1199              if (file_coverage->lines[i] == -1) {
1200                return -2;
1201              }
1202              file_coverage->lines[i] += (int) element->pn_dval;
1203            }
1204            else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1205              if (file_coverage->lines[i] != -1) {
1206                return -2;
1207              }
1208            }
1209            else {
1210              return -1;
1211            }
1212          }
1213          assert(i == array->pn_count);
1214    
1215          /* if this JSON file has source, use it */
1216          if (file_coverage->source == NULL && source != NULL) {
1217            file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom)));
1218          }
1219        }
1220      }
1221    
1222      return 0;
1223    }

Legend:
Removed from v.101  
changed lines
  Added in v.169

  ViewVC Help
Powered by ViewVC 1.1.24