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

Diff of /trunk/instrument-js.cpp

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

revision 101 by siliconforks, Sat May 24 18:11:30 2008 UTC revision 157 by siliconforks, Sat Sep 13 03:56:45 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 851  Line 877 
877    free(lines);    free(lines);
878    lines = NULL;    lines = NULL;
879    
880      /* conditionals */
881      bool has_conditionals = false;
882      size_t line_number = 0;
883      size_t i = 0;
884      while (i < input_length) {
885        line_number++;
886        size_t line_start = i;
887        while (i < input_length && base[i] != '\r' && base[i] != '\n') {
888          i++;
889        }
890        size_t line_end = i;
891        if (i < input_length) {
892          if (base[i] == '\r') {
893            line_end = i;
894            i++;
895            if (i < input_length && base[i] == '\n') {
896              i++;
897            }
898          }
899          else if (base[i] == '\n') {
900            line_end = i;
901            i++;
902          }
903          else {
904            abort();
905          }
906        }
907        char * line = js_DeflateString(context, base + line_start, line_end - line_start);
908        if (str_starts_with(line, "//#JSCOVERAGE_IF")) {
909          if (! has_conditionals) {
910            has_conditionals = true;
911            Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);
912          }
913          Stream_printf(output, "_$jscoverage['%s'].conditionals[%d] = {condition: function () {return %s;}, ", file_id, line_number, line + 16);
914        }
915        else if (str_starts_with(line, "//#JSCOVERAGE_ENDIF")) {
916          Stream_printf(output, "end: %d};\n", line_number);
917        }
918        JS_free(context, line);
919      }
920    
921    /* copy the instrumented source code to the output */    /* copy the instrumented source code to the output */
922    Stream_write(output, instrumented->data, instrumented->length);    Stream_write(output, instrumented->data, instrumented->length);
923    Stream_write_char(output, '\n');    Stream_write_char(output, '\n');
924    
925    /* copy the original source to the output */    /* copy the original source to the output */
926    size_t i = 0;    i = 0;
927    while (i < input_length) {    while (i < input_length) {
928      Stream_write_string(output, "// ");      Stream_write_string(output, "// ");
929      size_t line_start = i;      size_t line_start = i;
# Line 894  Line 961 
961    
962    file_id = NULL;    file_id = NULL;
963  }  }
964    
965    void jscoverage_copy_resources(const char * destination_directory) {
966      copy_resource("jscoverage.html", destination_directory);
967      copy_resource("jscoverage.css", destination_directory);
968      copy_resource("jscoverage.js", destination_directory);
969      copy_resource("jscoverage-throbber.gif", destination_directory);
970      copy_resource("jscoverage-sh_main.js", destination_directory);
971      copy_resource("jscoverage-sh_javascript.js", destination_directory);
972      copy_resource("jscoverage-sh_nedit.css", destination_directory);
973    }
974    
975    /*
976    coverage reports
977    */
978    
979    struct FileCoverageList {
980      FileCoverage * file_coverage;
981      struct FileCoverageList * next;
982    };
983    
984    struct Coverage {
985      JSHashTable * coverage_table;
986      struct FileCoverageList * coverage_list;
987    };
988    
989    static int compare_strings(const void * p1, const void * p2) {
990      return strcmp(p1, p2) == 0;
991    }
992    
993    Coverage * Coverage_new(void) {
994      Coverage * result = xmalloc(sizeof(Coverage));
995      result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL);
996      if (result->coverage_table == NULL) {
997        fatal("cannot create hash table");
998      }
999      result->coverage_list = NULL;
1000      return result;
1001    }
1002    
1003    void Coverage_delete(Coverage * coverage) {
1004      JS_HashTableDestroy(coverage->coverage_table);
1005      struct FileCoverageList * p = coverage->coverage_list;
1006      while (p != NULL) {
1007        free(p->file_coverage->lines);
1008        free(p->file_coverage->source);
1009        free(p->file_coverage->id);
1010        free(p->file_coverage);
1011        struct FileCoverageList * q = p;
1012        p = p->next;
1013        free(q);
1014      }
1015      free(coverage);
1016    }
1017    
1018    struct EnumeratorArg {
1019      CoverageForeachFunction f;
1020      void * p;
1021    };
1022    
1023    static intN enumerator(JSHashEntry * entry, intN i, void * arg) {
1024      struct EnumeratorArg * enumerator_arg = arg;
1025      enumerator_arg->f(entry->value, i, enumerator_arg->p);
1026      return 0;
1027    }
1028    
1029    void Coverage_foreach_file(Coverage * coverage, CoverageForeachFunction f, void * p) {
1030      struct EnumeratorArg enumerator_arg;
1031      enumerator_arg.f = f;
1032      enumerator_arg.p = p;
1033      JS_HashTableEnumerateEntries(coverage->coverage_table, enumerator, &enumerator_arg);
1034    }
1035    
1036    int jscoverage_parse_json(Coverage * coverage, const uint8_t * json, size_t length) {
1037      jschar * base = js_InflateString(context, (char *) json, &length);
1038      if (base == NULL) {
1039        fatal("out of memory");
1040      }
1041    
1042      jschar * parenthesized_json = xnew(jschar, addst(length, 2));
1043      parenthesized_json[0] = '(';
1044      memcpy(parenthesized_json + 1, base, mulst(length, sizeof(jschar)));
1045      parenthesized_json[length + 1] = ')';
1046    
1047      JS_free(context, base);
1048    
1049      JSTokenStream * token_stream = js_NewTokenStream(context, parenthesized_json, length + 2, NULL, 1, NULL);
1050      if (token_stream == NULL) {
1051        fatal("cannot create token stream");
1052      }
1053    
1054      JSParseNode * root = js_ParseTokenStream(context, global, token_stream);
1055      free(parenthesized_json);
1056      if (root == NULL) {
1057        return -1;
1058      }
1059    
1060      /* root node must be TOK_LC */
1061      if (root->pn_type != TOK_LC) {
1062        return -1;
1063      }
1064      JSParseNode * semi = root->pn_u.list.head;
1065    
1066      /* the list must be TOK_SEMI and it must contain only one element */
1067      if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {
1068        return -1;
1069      }
1070      JSParseNode * parenthesized = semi->pn_kid;
1071    
1072      /* this must be a parenthesized expression */
1073      if (parenthesized->pn_type != TOK_RP) {
1074        return -1;
1075      }
1076      JSParseNode * object = parenthesized->pn_kid;
1077    
1078      /* this must be an object literal */
1079      if (object->pn_type != TOK_RC) {
1080        return -1;
1081      }
1082    
1083      for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {
1084        /* every element of this list must be TOK_COLON */
1085        if (p->pn_type != TOK_COLON) {
1086          return -1;
1087        }
1088    
1089        /* the key must be a string representing the file */
1090        JSParseNode * key = p->pn_left;
1091        if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {
1092          return -1;
1093        }
1094        char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));
1095    
1096        /* the value must be an object literal OR an array */
1097        JSParseNode * value = p->pn_right;
1098        if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {
1099          return -1;
1100        }
1101    
1102        JSParseNode * array = NULL;
1103        JSParseNode * source = NULL;
1104        if (value->pn_type == TOK_RB) {
1105          /* an array */
1106          array = value;
1107        }
1108        else if (value->pn_type == TOK_RC) {
1109          /* an object literal */
1110          if (value->pn_count != 2) {
1111            return -1;
1112          }
1113          for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {
1114            if (element->pn_type != TOK_COLON) {
1115              return -1;
1116            }
1117            JSParseNode * left = element->pn_left;
1118            if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {
1119              return -1;
1120            }
1121            const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));
1122            if (strcmp(s, "coverage") == 0) {
1123              array = element->pn_right;
1124              if (array->pn_type != TOK_RB) {
1125                return -1;
1126              }
1127            }
1128            else if (strcmp(s, "source") == 0) {
1129              source = element->pn_right;
1130              if (source->pn_type != TOK_STRING || ! ATOM_IS_STRING(source->pn_atom)) {
1131                return -1;
1132              }
1133            }
1134            else {
1135              return -1;
1136            }
1137          }
1138        }
1139        else {
1140          return -1;
1141        }
1142    
1143        if (array == NULL) {
1144          return -1;
1145        }
1146    
1147        /* look up the file in the coverage table */
1148        FileCoverage * file_coverage = JS_HashTableLookup(coverage->coverage_table, id_bytes);
1149        if (file_coverage == NULL) {
1150          /* not there: create a new one */
1151          char * id = xstrdup(id_bytes);
1152          file_coverage = xmalloc(sizeof(FileCoverage));
1153          file_coverage->id = id;
1154          file_coverage->num_lines = array->pn_count - 1;
1155          file_coverage->lines = xnew(int, array->pn_count);
1156          if (source == NULL) {
1157            file_coverage->source = NULL;
1158          }
1159          else {
1160            file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom)));
1161          }
1162    
1163          /* set coverage for all lines */
1164          uint32 i = 0;
1165          for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1166            if (element->pn_type == TOK_NUMBER) {
1167              file_coverage->lines[i] = (int) element->pn_dval;
1168            }
1169            else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1170              file_coverage->lines[i] = -1;
1171            }
1172            else {
1173              return -1;
1174            }
1175          }
1176          assert(i == array->pn_count);
1177    
1178          /* add to the hash table */
1179          JS_HashTableAdd(coverage->coverage_table, id, file_coverage);
1180          struct FileCoverageList * coverage_list = xmalloc(sizeof(struct FileCoverageList));
1181          coverage_list->file_coverage = file_coverage;
1182          coverage_list->next = coverage->coverage_list;
1183          coverage->coverage_list = coverage_list;
1184        }
1185        else {
1186          /* sanity check */
1187          assert(strcmp(file_coverage->id, id_bytes) == 0);
1188          if (file_coverage->num_lines != array->pn_count - 1) {
1189            return -2;
1190          }
1191    
1192          /* merge the coverage */
1193          uint32 i = 0;
1194          for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1195            if (element->pn_type == TOK_NUMBER) {
1196              if (file_coverage->lines[i] == -1) {
1197                return -2;
1198              }
1199              file_coverage->lines[i] += (int) element->pn_dval;
1200            }
1201            else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1202              if (file_coverage->lines[i] != -1) {
1203                return -2;
1204              }
1205            }
1206            else {
1207              return -1;
1208            }
1209          }
1210          assert(i == array->pn_count);
1211    
1212          /* if this JSON file has source, use it */
1213          if (file_coverage->source == NULL && source != NULL) {
1214            file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom)));
1215          }
1216        }
1217      }
1218    
1219      return 0;
1220    }

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

  ViewVC Help
Powered by ViewVC 1.1.24