/[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 156 by siliconforks, Fri Sep 12 19:44:03 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 894  Line 920 
920    
921    file_id = NULL;    file_id = NULL;
922  }  }
923    
924    void jscoverage_copy_resources(const char * destination_directory) {
925      copy_resource("jscoverage.html", destination_directory);
926      copy_resource("jscoverage.css", destination_directory);
927      copy_resource("jscoverage.js", destination_directory);
928      copy_resource("jscoverage-throbber.gif", destination_directory);
929      copy_resource("jscoverage-sh_main.js", destination_directory);
930      copy_resource("jscoverage-sh_javascript.js", destination_directory);
931      copy_resource("jscoverage-sh_nedit.css", destination_directory);
932    }
933    
934    /*
935    coverage reports
936    */
937    
938    struct FileCoverageList {
939      FileCoverage * file_coverage;
940      struct FileCoverageList * next;
941    };
942    
943    struct Coverage {
944      JSHashTable * coverage_table;
945      struct FileCoverageList * coverage_list;
946    };
947    
948    static int compare_strings(const void * p1, const void * p2) {
949      return strcmp(p1, p2) == 0;
950    }
951    
952    Coverage * Coverage_new(void) {
953      Coverage * result = xmalloc(sizeof(Coverage));
954      result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL);
955      if (result->coverage_table == NULL) {
956        fatal("cannot create hash table");
957      }
958      result->coverage_list = NULL;
959      return result;
960    }
961    
962    void Coverage_delete(Coverage * coverage) {
963      JS_HashTableDestroy(coverage->coverage_table);
964      struct FileCoverageList * p = coverage->coverage_list;
965      while (p != NULL) {
966        free(p->file_coverage->lines);
967        free(p->file_coverage->source);
968        free(p->file_coverage->id);
969        free(p->file_coverage);
970        struct FileCoverageList * q = p;
971        p = p->next;
972        free(q);
973      }
974      free(coverage);
975    }
976    
977    struct EnumeratorArg {
978      CoverageForeachFunction f;
979      void * p;
980    };
981    
982    static intN enumerator(JSHashEntry * entry, intN i, void * arg) {
983      struct EnumeratorArg * enumerator_arg = arg;
984      enumerator_arg->f(entry->value, i, enumerator_arg->p);
985      return 0;
986    }
987    
988    void Coverage_foreach_file(Coverage * coverage, CoverageForeachFunction f, void * p) {
989      struct EnumeratorArg enumerator_arg;
990      enumerator_arg.f = f;
991      enumerator_arg.p = p;
992      JS_HashTableEnumerateEntries(coverage->coverage_table, enumerator, &enumerator_arg);
993    }
994    
995    int jscoverage_parse_json(Coverage * coverage, const uint8_t * json, size_t length) {
996      jschar * base = js_InflateString(context, (char *) json, &length);
997      if (base == NULL) {
998        fatal("out of memory");
999      }
1000    
1001      jschar * parenthesized_json = xnew(jschar, addst(length, 2));
1002      parenthesized_json[0] = '(';
1003      memcpy(parenthesized_json + 1, base, mulst(length, sizeof(jschar)));
1004      parenthesized_json[length + 1] = ')';
1005    
1006      JS_free(context, base);
1007    
1008      JSTokenStream * token_stream = js_NewTokenStream(context, parenthesized_json, length + 2, NULL, 1, NULL);
1009      if (token_stream == NULL) {
1010        fatal("cannot create token stream");
1011      }
1012    
1013      JSParseNode * root = js_ParseTokenStream(context, global, token_stream);
1014      free(parenthesized_json);
1015      if (root == NULL) {
1016        return -1;
1017      }
1018    
1019      /* root node must be TOK_LC */
1020      if (root->pn_type != TOK_LC) {
1021        return -1;
1022      }
1023      JSParseNode * semi = root->pn_u.list.head;
1024    
1025      /* the list must be TOK_SEMI and it must contain only one element */
1026      if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {
1027        return -1;
1028      }
1029      JSParseNode * parenthesized = semi->pn_kid;
1030    
1031      /* this must be a parenthesized expression */
1032      if (parenthesized->pn_type != TOK_RP) {
1033        return -1;
1034      }
1035      JSParseNode * object = parenthesized->pn_kid;
1036    
1037      /* this must be an object literal */
1038      if (object->pn_type != TOK_RC) {
1039        return -1;
1040      }
1041    
1042      for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {
1043        /* every element of this list must be TOK_COLON */
1044        if (p->pn_type != TOK_COLON) {
1045          return -1;
1046        }
1047    
1048        /* the key must be a string representing the file */
1049        JSParseNode * key = p->pn_left;
1050        if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {
1051          return -1;
1052        }
1053        char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));
1054    
1055        /* the value must be an object literal OR an array */
1056        JSParseNode * value = p->pn_right;
1057        if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {
1058          return -1;
1059        }
1060    
1061        JSParseNode * array = NULL;
1062        JSParseNode * source = NULL;
1063        if (value->pn_type == TOK_RB) {
1064          /* an array */
1065          array = value;
1066        }
1067        else if (value->pn_type == TOK_RC) {
1068          /* an object literal */
1069          if (value->pn_count != 2) {
1070            return -1;
1071          }
1072          for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {
1073            if (element->pn_type != TOK_COLON) {
1074              return -1;
1075            }
1076            JSParseNode * left = element->pn_left;
1077            if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {
1078              return -1;
1079            }
1080            const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));
1081            if (strcmp(s, "coverage") == 0) {
1082              array = element->pn_right;
1083              if (array->pn_type != TOK_RB) {
1084                return -1;
1085              }
1086            }
1087            else if (strcmp(s, "source") == 0) {
1088              source = element->pn_right;
1089              if (source->pn_type != TOK_STRING || ! ATOM_IS_STRING(source->pn_atom)) {
1090                return -1;
1091              }
1092            }
1093            else {
1094              return -1;
1095            }
1096          }
1097        }
1098        else {
1099          return -1;
1100        }
1101    
1102        if (array == NULL) {
1103          return -1;
1104        }
1105    
1106        /* look up the file in the coverage table */
1107        FileCoverage * file_coverage = JS_HashTableLookup(coverage->coverage_table, id_bytes);
1108        if (file_coverage == NULL) {
1109          /* not there: create a new one */
1110          char * id = xstrdup(id_bytes);
1111          file_coverage = xmalloc(sizeof(FileCoverage));
1112          file_coverage->id = id;
1113          file_coverage->num_lines = array->pn_count - 1;
1114          file_coverage->lines = xnew(int, array->pn_count);
1115          if (source == NULL) {
1116            file_coverage->source = NULL;
1117          }
1118          else {
1119            file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom)));
1120          }
1121    
1122          /* set coverage for all lines */
1123          uint32 i = 0;
1124          for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1125            if (element->pn_type == TOK_NUMBER) {
1126              file_coverage->lines[i] = (int) element->pn_dval;
1127            }
1128            else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1129              file_coverage->lines[i] = -1;
1130            }
1131            else {
1132              return -1;
1133            }
1134          }
1135          assert(i == array->pn_count);
1136    
1137          /* add to the hash table */
1138          JS_HashTableAdd(coverage->coverage_table, id, file_coverage);
1139          struct FileCoverageList * coverage_list = xmalloc(sizeof(struct FileCoverageList));
1140          coverage_list->file_coverage = file_coverage;
1141          coverage_list->next = coverage->coverage_list;
1142          coverage->coverage_list = coverage_list;
1143        }
1144        else {
1145          /* sanity check */
1146          assert(strcmp(file_coverage->id, id_bytes) == 0);
1147          if (file_coverage->num_lines != array->pn_count - 1) {
1148            return -2;
1149          }
1150    
1151          /* merge the coverage */
1152          uint32 i = 0;
1153          for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
1154            if (element->pn_type == TOK_NUMBER) {
1155              if (file_coverage->lines[i] == -1) {
1156                return -2;
1157              }
1158              file_coverage->lines[i] += (int) element->pn_dval;
1159            }
1160            else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
1161              if (file_coverage->lines[i] != -1) {
1162                return -2;
1163              }
1164            }
1165            else {
1166              return -1;
1167            }
1168          }
1169          assert(i == array->pn_count);
1170    
1171          /* if this JSON file has source, use it */
1172          if (file_coverage->source == NULL && source != NULL) {
1173            file_coverage->source = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(source->pn_atom)));
1174          }
1175        }
1176      }
1177    
1178      return 0;
1179    }

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

  ViewVC Help
Powered by ViewVC 1.1.24