PageRenderTime 30ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/js/lib/Socket.IO-node/support/expresso/deps/jscoverage/instrument-js.cpp

http://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs
C++ | 1873 lines | 1629 code | 102 blank | 142 comment | 410 complexity | a85c79efb6b3ccffe4aa784a8140d131 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
  1. /*
  2. instrument-js.cpp - JavaScript instrumentation routines
  3. Copyright (C) 2007, 2008 siliconforks.com
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License along
  13. with this program; if not, write to the Free Software Foundation, Inc.,
  14. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  15. */
  16. #include <config.h>
  17. #include "instrument-js.h"
  18. #include <assert.h>
  19. #include <math.h>
  20. #include <stdlib.h>
  21. #include <string.h>
  22. #include <jsapi.h>
  23. #include <jsarena.h>
  24. #include <jsatom.h>
  25. #include <jsemit.h>
  26. #include <jsexn.h>
  27. #include <jsfun.h>
  28. #include <jsinterp.h>
  29. #include <jsiter.h>
  30. #include <jsparse.h>
  31. #include <jsregexp.h>
  32. #include <jsscope.h>
  33. #include <jsstr.h>
  34. #include "encoding.h"
  35. #include "global.h"
  36. #include "highlight.h"
  37. #include "resource-manager.h"
  38. #include "util.h"
  39. struct IfDirective {
  40. const jschar * condition_start;
  41. const jschar * condition_end;
  42. uint16_t start_line;
  43. uint16_t end_line;
  44. struct IfDirective * next;
  45. };
  46. bool jscoverage_mozilla = false;
  47. static bool * exclusive_directives = NULL;
  48. static JSRuntime * runtime = NULL;
  49. static JSContext * context = NULL;
  50. static JSObject * global = NULL;
  51. static JSVersion js_version = JSVERSION_ECMA_3;
  52. /*
  53. JSParseNode objects store line numbers starting from 1.
  54. The lines array stores line numbers starting from 0.
  55. */
  56. static const char * file_id = NULL;
  57. static char * lines = NULL;
  58. static uint16_t num_lines = 0;
  59. void jscoverage_set_js_version(const char * version) {
  60. js_version = JS_StringToVersion(version);
  61. if (js_version != JSVERSION_UNKNOWN) {
  62. return;
  63. }
  64. char * end;
  65. js_version = (JSVersion) strtol(version, &end, 10);
  66. if ((size_t) (end - version) != strlen(version)) {
  67. fatal("invalid version: %s", version);
  68. }
  69. }
  70. void jscoverage_init(void) {
  71. runtime = JS_NewRuntime(8L * 1024L * 1024L);
  72. if (runtime == NULL) {
  73. fatal("cannot create runtime");
  74. }
  75. context = JS_NewContext(runtime, 8192);
  76. if (context == NULL) {
  77. fatal("cannot create context");
  78. }
  79. JS_SetVersion(context, js_version);
  80. global = JS_NewObject(context, NULL, NULL, NULL);
  81. if (global == NULL) {
  82. fatal("cannot create global object");
  83. }
  84. if (! JS_InitStandardClasses(context, global)) {
  85. fatal("cannot initialize standard classes");
  86. }
  87. }
  88. void jscoverage_cleanup(void) {
  89. JS_DestroyContext(context);
  90. JS_DestroyRuntime(runtime);
  91. }
  92. static void print_javascript(const jschar * characters, size_t num_characters, Stream * f) {
  93. for (size_t i = 0; i < num_characters; i++) {
  94. jschar c = characters[i];
  95. /*
  96. XXX does not handle no-break space, other unicode "space separator"
  97. */
  98. switch (c) {
  99. case 0x9:
  100. case 0xB:
  101. case 0xC:
  102. Stream_write_char(f, c);
  103. break;
  104. default:
  105. if (32 <= c && c <= 126) {
  106. Stream_write_char(f, c);
  107. }
  108. else {
  109. Stream_printf(f, "\\u%04x", c);
  110. }
  111. break;
  112. }
  113. }
  114. }
  115. static void print_string(JSString * s, Stream * f) {
  116. size_t length = JSSTRING_LENGTH(s);
  117. jschar * characters = JSSTRING_CHARS(s);
  118. for (size_t i = 0; i < length; i++) {
  119. jschar c = characters[i];
  120. if (32 <= c && c <= 126) {
  121. switch (c) {
  122. case '"':
  123. Stream_write_string(f, "\\\"");
  124. break;
  125. /*
  126. case '\'':
  127. Stream_write_string(f, "\\'");
  128. break;
  129. */
  130. case '\\':
  131. Stream_write_string(f, "\\\\");
  132. break;
  133. default:
  134. Stream_write_char(f, c);
  135. break;
  136. }
  137. }
  138. else {
  139. switch (c) {
  140. case 0x8:
  141. Stream_write_string(f, "\\b");
  142. break;
  143. case 0x9:
  144. Stream_write_string(f, "\\t");
  145. break;
  146. case 0xa:
  147. Stream_write_string(f, "\\n");
  148. break;
  149. /* IE doesn't support this */
  150. /*
  151. case 0xb:
  152. Stream_write_string(f, "\\v");
  153. break;
  154. */
  155. case 0xc:
  156. Stream_write_string(f, "\\f");
  157. break;
  158. case 0xd:
  159. Stream_write_string(f, "\\r");
  160. break;
  161. default:
  162. Stream_printf(f, "\\u%04x", c);
  163. break;
  164. }
  165. }
  166. }
  167. }
  168. static void print_string_atom(JSAtom * atom, Stream * f) {
  169. assert(ATOM_IS_STRING(atom));
  170. JSString * s = ATOM_TO_STRING(atom);
  171. print_string(s, f);
  172. }
  173. static void print_regex(jsval value, Stream * f) {
  174. assert(JSVAL_IS_STRING(value));
  175. JSString * s = JSVAL_TO_STRING(value);
  176. size_t length = JSSTRING_LENGTH(s);
  177. jschar * characters = JSSTRING_CHARS(s);
  178. for (size_t i = 0; i < length; i++) {
  179. jschar c = characters[i];
  180. if (32 <= c && c <= 126) {
  181. Stream_write_char(f, c);
  182. }
  183. else {
  184. Stream_printf(f, "\\u%04x", c);
  185. }
  186. }
  187. }
  188. static void print_quoted_string_atom(JSAtom * atom, Stream * f) {
  189. assert(ATOM_IS_STRING(atom));
  190. JSString * s = ATOM_TO_STRING(atom);
  191. Stream_write_char(f, '"');
  192. print_string(s, f);
  193. Stream_write_char(f, '"');
  194. }
  195. static const char * get_op(uint8 op) {
  196. switch(op) {
  197. case JSOP_OR:
  198. return "||";
  199. case JSOP_AND:
  200. return "&&";
  201. case JSOP_BITOR:
  202. return "|";
  203. case JSOP_BITXOR:
  204. return "^";
  205. case JSOP_BITAND:
  206. return "&";
  207. case JSOP_EQ:
  208. return "==";
  209. case JSOP_NE:
  210. return "!=";
  211. case JSOP_STRICTEQ:
  212. return "===";
  213. case JSOP_STRICTNE:
  214. return "!==";
  215. case JSOP_LT:
  216. return "<";
  217. case JSOP_LE:
  218. return "<=";
  219. case JSOP_GT:
  220. return ">";
  221. case JSOP_GE:
  222. return ">=";
  223. case JSOP_LSH:
  224. return "<<";
  225. case JSOP_RSH:
  226. return ">>";
  227. case JSOP_URSH:
  228. return ">>>";
  229. case JSOP_ADD:
  230. return "+";
  231. case JSOP_SUB:
  232. return "-";
  233. case JSOP_MUL:
  234. return "*";
  235. case JSOP_DIV:
  236. return "/";
  237. case JSOP_MOD:
  238. return "%";
  239. default:
  240. abort();
  241. }
  242. }
  243. static void output_expression(JSParseNode * node, Stream * f, bool parenthesize_object_literals);
  244. static void instrument_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if);
  245. static void output_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if);
  246. enum FunctionType {
  247. FUNCTION_NORMAL,
  248. FUNCTION_GETTER_OR_SETTER
  249. };
  250. static void output_for_in(JSParseNode * node, Stream * f) {
  251. assert(node->pn_type == TOK_FOR);
  252. assert(node->pn_arity == PN_BINARY);
  253. Stream_write_string(f, "for ");
  254. if (node->pn_iflags & JSITER_FOREACH) {
  255. Stream_write_string(f, "each ");
  256. }
  257. Stream_write_char(f, '(');
  258. output_expression(node->pn_left, f, false);
  259. Stream_write_char(f, ')');
  260. }
  261. static void output_array_comprehension_or_generator_expression(JSParseNode * node, Stream * f) {
  262. assert(node->pn_type == TOK_LEXICALSCOPE);
  263. assert(node->pn_arity == PN_NAME);
  264. JSParseNode * for_node = node->pn_expr;
  265. assert(for_node->pn_type == TOK_FOR);
  266. assert(for_node->pn_arity == PN_BINARY);
  267. JSParseNode * p = for_node;
  268. while (p->pn_type == TOK_FOR) {
  269. p = p->pn_right;
  270. }
  271. JSParseNode * if_node = NULL;
  272. if (p->pn_type == TOK_IF) {
  273. if_node = p;
  274. assert(if_node->pn_arity == PN_TERNARY);
  275. p = if_node->pn_kid2;
  276. }
  277. switch (p->pn_arity) {
  278. case PN_UNARY:
  279. p = p->pn_kid;
  280. if (p->pn_type == TOK_YIELD) {
  281. /* for generator expressions */
  282. p = p->pn_kid;
  283. }
  284. output_expression(p, f, false);
  285. break;
  286. case PN_LIST:
  287. /*
  288. When the array comprehension contains "if (0)", it will be optimized away and
  289. the result will be an empty TOK_LC list.
  290. */
  291. assert(p->pn_type == TOK_LC);
  292. assert(p->pn_head == NULL);
  293. /* the "1" is arbitrary (since the list is empty) */
  294. Stream_write_char(f, '1');
  295. break;
  296. default:
  297. abort();
  298. break;
  299. }
  300. p = for_node;
  301. while (p->pn_type == TOK_FOR) {
  302. Stream_write_char(f, ' ');
  303. output_for_in(p, f);
  304. p = p->pn_right;
  305. }
  306. if (p->pn_type == TOK_LC) {
  307. /* this is the optimized-away "if (0)" */
  308. Stream_write_string(f, " if (0)");
  309. }
  310. else if (if_node) {
  311. Stream_write_string(f, " if (");
  312. output_expression(if_node->pn_kid1, f, false);
  313. Stream_write_char(f, ')');
  314. }
  315. }
  316. static void instrument_function(JSParseNode * node, Stream * f, int indent, enum FunctionType type) {
  317. assert(node->pn_type == TOK_FUNCTION);
  318. assert(node->pn_arity == PN_FUNC);
  319. JSObject * object = node->pn_funpob->object;
  320. assert(JS_ObjectIsFunction(context, object));
  321. JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);
  322. assert(function);
  323. assert(object == &function->object);
  324. Stream_printf(f, "%*s", indent, "");
  325. if (type == FUNCTION_NORMAL) {
  326. Stream_write_string(f, "function ");
  327. }
  328. /* function name */
  329. if (function->atom) {
  330. print_string_atom(function->atom, f);
  331. }
  332. /*
  333. function parameters - see JS_DecompileFunction in jsapi.cpp, which calls
  334. js_DecompileFunction in jsopcode.cpp
  335. */
  336. Stream_write_char(f, '(');
  337. JSArenaPool pool;
  338. JS_INIT_ARENA_POOL(&pool, "instrument_function", 256, 1, &context->scriptStackQuota);
  339. jsuword * local_names = NULL;
  340. if (JS_GET_LOCAL_NAME_COUNT(function)) {
  341. local_names = js_GetLocalNameArray(context, function, &pool);
  342. if (local_names == NULL) {
  343. fatal("out of memory");
  344. }
  345. }
  346. bool destructuring = false;
  347. for (int i = 0; i < function->nargs; i++) {
  348. if (i > 0) {
  349. Stream_write_string(f, ", ");
  350. }
  351. JSAtom * param = JS_LOCAL_NAME_TO_ATOM(local_names[i]);
  352. if (param == NULL) {
  353. destructuring = true;
  354. JSParseNode * expression = NULL;
  355. assert(node->pn_body->pn_type == TOK_LC || node->pn_body->pn_type == TOK_SEQ);
  356. JSParseNode * semi = node->pn_body->pn_head;
  357. assert(semi->pn_type == TOK_SEMI);
  358. JSParseNode * comma = semi->pn_kid;
  359. assert(comma->pn_type == TOK_COMMA);
  360. for (JSParseNode * p = comma->pn_head; p != NULL; p = p->pn_next) {
  361. assert(p->pn_type == TOK_ASSIGN);
  362. JSParseNode * rhs = p->pn_right;
  363. assert(JSSTRING_LENGTH(ATOM_TO_STRING(rhs->pn_atom)) == 0);
  364. if (rhs->pn_slot == i) {
  365. expression = p->pn_left;
  366. break;
  367. }
  368. }
  369. assert(expression != NULL);
  370. output_expression(expression, f, false);
  371. }
  372. else {
  373. print_string_atom(param, f);
  374. }
  375. }
  376. JS_FinishArenaPool(&pool);
  377. Stream_write_string(f, ") {\n");
  378. /* function body */
  379. if (function->flags & JSFUN_EXPR_CLOSURE) {
  380. /* expression closure - use output_statement instead of instrument_statement */
  381. if (node->pn_body->pn_type == TOK_SEQ) {
  382. assert(node->pn_body->pn_arity == PN_LIST);
  383. assert(node->pn_body->pn_count == 2);
  384. output_statement(node->pn_body->pn_head->pn_next, f, indent + 2, false);
  385. }
  386. else {
  387. output_statement(node->pn_body, f, indent + 2, false);
  388. }
  389. }
  390. else {
  391. assert(node->pn_body->pn_type == TOK_LC);
  392. assert(node->pn_body->pn_arity == PN_LIST);
  393. JSParseNode * p = node->pn_body->pn_head;
  394. if (destructuring) {
  395. p = p->pn_next;
  396. }
  397. for (; p != NULL; p = p->pn_next) {
  398. instrument_statement(p, f, indent + 2, false);
  399. }
  400. }
  401. Stream_write_char(f, '}');
  402. }
  403. static void instrument_function_call(JSParseNode * node, Stream * f) {
  404. JSParseNode * function_node = node->pn_head;
  405. if (function_node->pn_type == TOK_FUNCTION) {
  406. JSObject * object = function_node->pn_funpob->object;
  407. assert(JS_ObjectIsFunction(context, object));
  408. JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);
  409. assert(function);
  410. assert(object == &function->object);
  411. if (function_node->pn_flags & TCF_GENEXP_LAMBDA) {
  412. /* it's a generator expression */
  413. Stream_write_char(f, '(');
  414. output_array_comprehension_or_generator_expression(function_node->pn_body, f);
  415. Stream_write_char(f, ')');
  416. return;
  417. }
  418. }
  419. output_expression(function_node, f, false);
  420. Stream_write_char(f, '(');
  421. for (struct JSParseNode * p = function_node->pn_next; p != NULL; p = p->pn_next) {
  422. if (p != node->pn_head->pn_next) {
  423. Stream_write_string(f, ", ");
  424. }
  425. output_expression(p, f, false);
  426. }
  427. Stream_write_char(f, ')');
  428. }
  429. static void instrument_declarations(JSParseNode * list, Stream * f) {
  430. assert(list->pn_arity == PN_LIST);
  431. for (JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) {
  432. if (p != list->pn_head) {
  433. Stream_write_string(f, ", ");
  434. }
  435. output_expression(p, f, false);
  436. }
  437. }
  438. /*
  439. See <Expressions> in jsparse.h.
  440. TOK_FUNCTION is handled as a statement and as an expression.
  441. TOK_DBLDOT is not handled (XML op).
  442. TOK_DEFSHARP and TOK_USESHARP are not handled.
  443. TOK_ANYNAME is not handled (XML op).
  444. TOK_AT is not handled (XML op).
  445. TOK_DBLCOLON is not handled.
  446. TOK_XML* are not handled.
  447. There seem to be some undocumented expressions:
  448. TOK_INSTANCEOF binary
  449. TOK_IN binary
  450. */
  451. static void output_expression(JSParseNode * node, Stream * f, bool parenthesize_object_literals) {
  452. switch (node->pn_type) {
  453. case TOK_FUNCTION:
  454. Stream_write_char(f, '(');
  455. instrument_function(node, f, 0, FUNCTION_NORMAL);
  456. Stream_write_char(f, ')');
  457. break;
  458. case TOK_COMMA:
  459. for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
  460. if (p != node->pn_head) {
  461. Stream_write_string(f, ", ");
  462. }
  463. output_expression(p, f, parenthesize_object_literals);
  464. }
  465. break;
  466. case TOK_ASSIGN:
  467. output_expression(node->pn_left, f, parenthesize_object_literals);
  468. Stream_write_char(f, ' ');
  469. switch (node->pn_op) {
  470. case JSOP_ADD:
  471. case JSOP_SUB:
  472. case JSOP_MUL:
  473. case JSOP_MOD:
  474. case JSOP_LSH:
  475. case JSOP_RSH:
  476. case JSOP_URSH:
  477. case JSOP_BITAND:
  478. case JSOP_BITOR:
  479. case JSOP_BITXOR:
  480. case JSOP_DIV:
  481. Stream_printf(f, "%s", get_op(node->pn_op));
  482. break;
  483. default:
  484. /* do nothing - it must be a simple assignment */
  485. break;
  486. }
  487. Stream_write_string(f, "= ");
  488. output_expression(node->pn_right, f, false);
  489. break;
  490. case TOK_HOOK:
  491. output_expression(node->pn_kid1, f, parenthesize_object_literals);
  492. Stream_write_string(f, "? ");
  493. output_expression(node->pn_kid2, f, false);
  494. Stream_write_string(f, ": ");
  495. output_expression(node->pn_kid3, f, false);
  496. break;
  497. case TOK_OR:
  498. case TOK_AND:
  499. case TOK_BITOR:
  500. case TOK_BITXOR:
  501. case TOK_BITAND:
  502. case TOK_EQOP:
  503. case TOK_RELOP:
  504. case TOK_SHOP:
  505. case TOK_PLUS:
  506. case TOK_MINUS:
  507. case TOK_STAR:
  508. case TOK_DIVOP:
  509. switch (node->pn_arity) {
  510. case PN_BINARY:
  511. output_expression(node->pn_left, f, parenthesize_object_literals);
  512. Stream_printf(f, " %s ", get_op(node->pn_op));
  513. output_expression(node->pn_right, f, false);
  514. break;
  515. case PN_LIST:
  516. for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
  517. if (p == node->pn_head) {
  518. output_expression(p, f, parenthesize_object_literals);
  519. }
  520. else {
  521. Stream_printf(f, " %s ", get_op(node->pn_op));
  522. output_expression(p, f, false);
  523. }
  524. }
  525. break;
  526. default:
  527. abort();
  528. }
  529. break;
  530. case TOK_UNARYOP:
  531. switch (node->pn_op) {
  532. case JSOP_NEG:
  533. Stream_write_string(f, "- ");
  534. output_expression(node->pn_kid, f, false);
  535. break;
  536. case JSOP_POS:
  537. Stream_write_string(f, "+ ");
  538. output_expression(node->pn_kid, f, false);
  539. break;
  540. case JSOP_NOT:
  541. Stream_write_string(f, "! ");
  542. output_expression(node->pn_kid, f, false);
  543. break;
  544. case JSOP_BITNOT:
  545. Stream_write_string(f, "~ ");
  546. output_expression(node->pn_kid, f, false);
  547. break;
  548. case JSOP_TYPEOF:
  549. Stream_write_string(f, "typeof ");
  550. output_expression(node->pn_kid, f, false);
  551. break;
  552. case JSOP_VOID:
  553. Stream_write_string(f, "void ");
  554. output_expression(node->pn_kid, f, false);
  555. break;
  556. default:
  557. fatal_source(file_id, node->pn_pos.begin.lineno, "unknown operator (%d)", node->pn_op);
  558. break;
  559. }
  560. break;
  561. case TOK_INC:
  562. case TOK_DEC:
  563. /*
  564. This is not documented, but node->pn_op tells whether it is pre- or post-increment.
  565. */
  566. switch (node->pn_op) {
  567. case JSOP_INCNAME:
  568. case JSOP_INCPROP:
  569. case JSOP_INCELEM:
  570. Stream_write_string(f, "++");
  571. output_expression(node->pn_kid, f, false);
  572. break;
  573. case JSOP_DECNAME:
  574. case JSOP_DECPROP:
  575. case JSOP_DECELEM:
  576. Stream_write_string(f, "--");
  577. output_expression(node->pn_kid, f, false);
  578. break;
  579. case JSOP_NAMEINC:
  580. case JSOP_PROPINC:
  581. case JSOP_ELEMINC:
  582. output_expression(node->pn_kid, f, parenthesize_object_literals);
  583. Stream_write_string(f, "++");
  584. break;
  585. case JSOP_NAMEDEC:
  586. case JSOP_PROPDEC:
  587. case JSOP_ELEMDEC:
  588. output_expression(node->pn_kid, f, parenthesize_object_literals);
  589. Stream_write_string(f, "--");
  590. break;
  591. default:
  592. abort();
  593. break;
  594. }
  595. break;
  596. case TOK_NEW:
  597. Stream_write_string(f, "new ");
  598. instrument_function_call(node, f);
  599. break;
  600. case TOK_DELETE:
  601. Stream_write_string(f, "delete ");
  602. output_expression(node->pn_kid, f, false);
  603. break;
  604. case TOK_DOT:
  605. /* numeric literals must be parenthesized */
  606. switch (node->pn_expr->pn_type) {
  607. case TOK_NUMBER:
  608. Stream_write_char(f, '(');
  609. output_expression(node->pn_expr, f, false);
  610. Stream_write_char(f, ')');
  611. break;
  612. default:
  613. output_expression(node->pn_expr, f, true);
  614. break;
  615. }
  616. /*
  617. This may have originally been x['foo-bar']. Because the string 'foo-bar'
  618. contains illegal characters, we have to use the subscript syntax instead of
  619. the dot syntax.
  620. */
  621. assert(ATOM_IS_STRING(node->pn_atom));
  622. {
  623. JSString * s = ATOM_TO_STRING(node->pn_atom);
  624. bool must_quote;
  625. if (JSSTRING_LENGTH(s) == 0) {
  626. must_quote = true;
  627. }
  628. else if (js_CheckKeyword(JSSTRING_CHARS(s), JSSTRING_LENGTH(s)) != TOK_EOF) {
  629. must_quote = true;
  630. }
  631. else if (! js_IsIdentifier(s)) {
  632. must_quote = true;
  633. }
  634. else {
  635. must_quote = false;
  636. }
  637. if (must_quote) {
  638. Stream_write_char(f, '[');
  639. print_quoted_string_atom(node->pn_atom, f);
  640. Stream_write_char(f, ']');
  641. }
  642. else {
  643. Stream_write_char(f, '.');
  644. print_string_atom(node->pn_atom, f);
  645. }
  646. }
  647. break;
  648. case TOK_LB:
  649. output_expression(node->pn_left, f, false);
  650. Stream_write_char(f, '[');
  651. output_expression(node->pn_right, f, false);
  652. Stream_write_char(f, ']');
  653. break;
  654. case TOK_LP:
  655. instrument_function_call(node, f);
  656. break;
  657. case TOK_RB:
  658. Stream_write_char(f, '[');
  659. for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
  660. if (p != node->pn_head) {
  661. Stream_write_string(f, ", ");
  662. }
  663. /* TOK_COMMA is a special case: a hole in the array */
  664. if (p->pn_type != TOK_COMMA) {
  665. output_expression(p, f, false);
  666. }
  667. }
  668. if (node->pn_extra == PNX_ENDCOMMA) {
  669. Stream_write_char(f, ',');
  670. }
  671. Stream_write_char(f, ']');
  672. break;
  673. case TOK_RC:
  674. if (parenthesize_object_literals) {
  675. Stream_write_char(f, '(');
  676. }
  677. Stream_write_char(f, '{');
  678. for (struct JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
  679. if (p->pn_type != TOK_COLON) {
  680. fatal_source(file_id, p->pn_pos.begin.lineno, "unsupported node type (%d)", p->pn_type);
  681. }
  682. if (p != node->pn_head) {
  683. Stream_write_string(f, ", ");
  684. }
  685. /* check whether this is a getter or setter */
  686. switch (p->pn_op) {
  687. case JSOP_GETTER:
  688. case JSOP_SETTER:
  689. if (p->pn_op == JSOP_GETTER) {
  690. Stream_write_string(f, "get ");
  691. }
  692. else {
  693. Stream_write_string(f, "set ");
  694. }
  695. output_expression(p->pn_left, f, false);
  696. Stream_write_char(f, ' ');
  697. if (p->pn_right->pn_type != TOK_FUNCTION) {
  698. fatal_source(file_id, p->pn_pos.begin.lineno, "expected function");
  699. }
  700. instrument_function(p->pn_right, f, 0, FUNCTION_GETTER_OR_SETTER);
  701. break;
  702. default:
  703. output_expression(p->pn_left, f, false);
  704. Stream_write_string(f, ": ");
  705. output_expression(p->pn_right, f, false);
  706. break;
  707. }
  708. }
  709. Stream_write_char(f, '}');
  710. if (parenthesize_object_literals) {
  711. Stream_write_char(f, ')');
  712. }
  713. break;
  714. case TOK_RP:
  715. Stream_write_char(f, '(');
  716. output_expression(node->pn_kid, f, false);
  717. Stream_write_char(f, ')');
  718. break;
  719. case TOK_NAME:
  720. print_string_atom(node->pn_atom, f);
  721. if (node->pn_expr != NULL) {
  722. Stream_write_string(f, " = ");
  723. output_expression(node->pn_expr, f, false);
  724. }
  725. break;
  726. case TOK_STRING:
  727. print_quoted_string_atom(node->pn_atom, f);
  728. break;
  729. case TOK_REGEXP:
  730. assert(node->pn_op == JSOP_REGEXP);
  731. {
  732. JSObject * object = node->pn_pob->object;
  733. jsval result;
  734. js_regexp_toString(context, object, &result);
  735. print_regex(result, f);
  736. }
  737. break;
  738. case TOK_NUMBER:
  739. /*
  740. A 64-bit IEEE 754 floating point number has a 52-bit fraction.
  741. 2^(-52) = 2.22 x 10^(-16)
  742. Thus there are 16 significant digits.
  743. To keep the output simple, special-case zero.
  744. */
  745. if (node->pn_dval == 0.0) {
  746. if (signbit(node->pn_dval)) {
  747. Stream_write_string(f, "-0");
  748. }
  749. else {
  750. Stream_write_string(f, "0");
  751. }
  752. }
  753. else if (node->pn_dval == INFINITY) {
  754. Stream_write_string(f, "Number.POSITIVE_INFINITY");
  755. }
  756. else if (node->pn_dval == -INFINITY) {
  757. Stream_write_string(f, "Number.NEGATIVE_INFINITY");
  758. }
  759. else if (isnan(node->pn_dval)) {
  760. Stream_write_string(f, "Number.NaN");
  761. }
  762. else {
  763. Stream_printf(f, "%.15g", node->pn_dval);
  764. }
  765. break;
  766. case TOK_PRIMARY:
  767. switch (node->pn_op) {
  768. case JSOP_TRUE:
  769. Stream_write_string(f, "true");
  770. break;
  771. case JSOP_FALSE:
  772. Stream_write_string(f, "false");
  773. break;
  774. case JSOP_NULL:
  775. Stream_write_string(f, "null");
  776. break;
  777. case JSOP_THIS:
  778. Stream_write_string(f, "this");
  779. break;
  780. /* jsscan.h mentions `super' ??? */
  781. default:
  782. abort();
  783. }
  784. break;
  785. case TOK_INSTANCEOF:
  786. output_expression(node->pn_left, f, parenthesize_object_literals);
  787. Stream_write_string(f, " instanceof ");
  788. output_expression(node->pn_right, f, false);
  789. break;
  790. case TOK_IN:
  791. output_expression(node->pn_left, f, false);
  792. Stream_write_string(f, " in ");
  793. output_expression(node->pn_right, f, false);
  794. break;
  795. case TOK_LEXICALSCOPE:
  796. assert(node->pn_arity == PN_NAME);
  797. assert(node->pn_expr->pn_type == TOK_LET);
  798. assert(node->pn_expr->pn_arity == PN_BINARY);
  799. Stream_write_string(f, "let(");
  800. assert(node->pn_expr->pn_left->pn_type == TOK_LP);
  801. assert(node->pn_expr->pn_left->pn_arity == PN_LIST);
  802. instrument_declarations(node->pn_expr->pn_left, f);
  803. Stream_write_string(f, ") ");
  804. output_expression(node->pn_expr->pn_right, f, true);
  805. break;
  806. case TOK_YIELD:
  807. assert(node->pn_arity == PN_UNARY);
  808. Stream_write_string(f, "yield");
  809. if (node->pn_kid != NULL) {
  810. Stream_write_char(f, ' ');
  811. output_expression(node->pn_kid, f, true);
  812. }
  813. break;
  814. case TOK_ARRAYCOMP:
  815. assert(node->pn_arity == PN_LIST);
  816. {
  817. JSParseNode * block_node;
  818. switch (node->pn_count) {
  819. case 1:
  820. block_node = node->pn_head;
  821. break;
  822. case 2:
  823. block_node = node->pn_head->pn_next;
  824. break;
  825. default:
  826. abort();
  827. break;
  828. }
  829. Stream_write_char(f, '[');
  830. output_array_comprehension_or_generator_expression(block_node, f);
  831. Stream_write_char(f, ']');
  832. }
  833. break;
  834. case TOK_VAR:
  835. assert(node->pn_arity == PN_LIST);
  836. Stream_write_string(f, "var ");
  837. instrument_declarations(node, f);
  838. break;
  839. case TOK_LET:
  840. assert(node->pn_arity == PN_LIST);
  841. Stream_write_string(f, "let ");
  842. instrument_declarations(node, f);
  843. break;
  844. default:
  845. fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%d)", node->pn_type);
  846. }
  847. }
  848. static void output_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
  849. switch (node->pn_type) {
  850. case TOK_FUNCTION:
  851. instrument_function(node, f, indent, FUNCTION_NORMAL);
  852. Stream_write_char(f, '\n');
  853. break;
  854. case TOK_LC:
  855. assert(node->pn_arity == PN_LIST);
  856. /*
  857. Stream_write_string(f, "{\n");
  858. */
  859. for (struct JSParseNode * p = node->pn_u.list.head; p != NULL; p = p->pn_next) {
  860. instrument_statement(p, f, indent, false);
  861. }
  862. /*
  863. Stream_printf(f, "%*s", indent, "");
  864. Stream_write_string(f, "}\n");
  865. */
  866. break;
  867. case TOK_IF:
  868. {
  869. assert(node->pn_arity == PN_TERNARY);
  870. uint16_t line = node->pn_pos.begin.lineno;
  871. if (! is_jscoverage_if) {
  872. if (line > num_lines) {
  873. fatal("file %s contains more than 65,535 lines", file_id);
  874. }
  875. if (line >= 2 && exclusive_directives[line - 2]) {
  876. is_jscoverage_if = true;
  877. }
  878. }
  879. Stream_printf(f, "%*s", indent, "");
  880. Stream_write_string(f, "if (");
  881. output_expression(node->pn_kid1, f, false);
  882. Stream_write_string(f, ") {\n");
  883. if (is_jscoverage_if && node->pn_kid3) {
  884. uint16_t else_start = node->pn_kid3->pn_pos.begin.lineno;
  885. uint16_t else_end = node->pn_kid3->pn_pos.end.lineno + 1;
  886. Stream_printf(f, "%*s", indent + 2, "");
  887. Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, else_start, else_end);
  888. }
  889. instrument_statement(node->pn_kid2, f, indent + 2, false);
  890. Stream_printf(f, "%*s", indent, "");
  891. Stream_write_string(f, "}\n");
  892. if (node->pn_kid3 || is_jscoverage_if) {
  893. Stream_printf(f, "%*s", indent, "");
  894. Stream_write_string(f, "else {\n");
  895. if (is_jscoverage_if) {
  896. uint16_t if_start = node->pn_kid2->pn_pos.begin.lineno + 1;
  897. uint16_t if_end = node->pn_kid2->pn_pos.end.lineno + 1;
  898. Stream_printf(f, "%*s", indent + 2, "");
  899. Stream_printf(f, "_$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_start, if_end);
  900. }
  901. if (node->pn_kid3) {
  902. instrument_statement(node->pn_kid3, f, indent + 2, is_jscoverage_if);
  903. }
  904. Stream_printf(f, "%*s", indent, "");
  905. Stream_write_string(f, "}\n");
  906. }
  907. break;
  908. }
  909. case TOK_SWITCH:
  910. assert(node->pn_arity == PN_BINARY);
  911. Stream_printf(f, "%*s", indent, "");
  912. Stream_write_string(f, "switch (");
  913. output_expression(node->pn_left, f, false);
  914. Stream_write_string(f, ") {\n");
  915. {
  916. JSParseNode * list = node->pn_right;
  917. if (list->pn_type == TOK_LEXICALSCOPE) {
  918. list = list->pn_expr;
  919. }
  920. for (struct JSParseNode * p = list->pn_head; p != NULL; p = p->pn_next) {
  921. Stream_printf(f, "%*s", indent, "");
  922. switch (p->pn_type) {
  923. case TOK_CASE:
  924. Stream_write_string(f, "case ");
  925. output_expression(p->pn_left, f, false);
  926. Stream_write_string(f, ":\n");
  927. break;
  928. case TOK_DEFAULT:
  929. Stream_write_string(f, "default:\n");
  930. break;
  931. default:
  932. abort();
  933. break;
  934. }
  935. instrument_statement(p->pn_right, f, indent + 2, false);
  936. }
  937. }
  938. Stream_printf(f, "%*s", indent, "");
  939. Stream_write_string(f, "}\n");
  940. break;
  941. case TOK_CASE:
  942. case TOK_DEFAULT:
  943. abort();
  944. break;
  945. case TOK_WHILE:
  946. assert(node->pn_arity == PN_BINARY);
  947. Stream_printf(f, "%*s", indent, "");
  948. Stream_write_string(f, "while (");
  949. output_expression(node->pn_left, f, false);
  950. Stream_write_string(f, ") {\n");
  951. instrument_statement(node->pn_right, f, indent + 2, false);
  952. Stream_write_string(f, "}\n");
  953. break;
  954. case TOK_DO:
  955. assert(node->pn_arity == PN_BINARY);
  956. Stream_printf(f, "%*s", indent, "");
  957. Stream_write_string(f, "do {\n");
  958. instrument_statement(node->pn_left, f, indent + 2, false);
  959. Stream_write_string(f, "}\n");
  960. Stream_printf(f, "%*s", indent, "");
  961. Stream_write_string(f, "while (");
  962. output_expression(node->pn_right, f, false);
  963. Stream_write_string(f, ");\n");
  964. break;
  965. case TOK_FOR:
  966. assert(node->pn_arity == PN_BINARY);
  967. Stream_printf(f, "%*s", indent, "");
  968. switch (node->pn_left->pn_type) {
  969. case TOK_IN:
  970. /* for/in */
  971. assert(node->pn_left->pn_arity == PN_BINARY);
  972. output_for_in(node, f);
  973. break;
  974. case TOK_FORHEAD:
  975. /* for (;;) */
  976. assert(node->pn_left->pn_arity == PN_TERNARY);
  977. Stream_write_string(f, "for (");
  978. if (node->pn_left->pn_kid1) {
  979. output_expression(node->pn_left->pn_kid1, f, false);
  980. }
  981. Stream_write_string(f, ";");
  982. if (node->pn_left->pn_kid2) {
  983. Stream_write_char(f, ' ');
  984. output_expression(node->pn_left->pn_kid2, f, false);
  985. }
  986. Stream_write_string(f, ";");
  987. if (node->pn_left->pn_kid3) {
  988. Stream_write_char(f, ' ');
  989. output_expression(node->pn_left->pn_kid3, f, false);
  990. }
  991. Stream_write_char(f, ')');
  992. break;
  993. default:
  994. abort();
  995. break;
  996. }
  997. Stream_write_string(f, " {\n");
  998. instrument_statement(node->pn_right, f, indent + 2, false);
  999. Stream_write_string(f, "}\n");
  1000. break;
  1001. case TOK_THROW:
  1002. assert(node->pn_arity == PN_UNARY);
  1003. Stream_printf(f, "%*s", indent, "");
  1004. Stream_write_string(f, "throw ");
  1005. output_expression(node->pn_u.unary.kid, f, false);
  1006. Stream_write_string(f, ";\n");
  1007. break;
  1008. case TOK_TRY:
  1009. Stream_printf(f, "%*s", indent, "");
  1010. Stream_write_string(f, "try {\n");
  1011. instrument_statement(node->pn_kid1, f, indent + 2, false);
  1012. Stream_printf(f, "%*s", indent, "");
  1013. Stream_write_string(f, "}\n");
  1014. if (node->pn_kid2) {
  1015. assert(node->pn_kid2->pn_type == TOK_RESERVED);
  1016. for (JSParseNode * scope = node->pn_kid2->pn_head; scope != NULL; scope = scope->pn_next) {
  1017. assert(scope->pn_type == TOK_LEXICALSCOPE);
  1018. JSParseNode * catch_node = scope->pn_expr;
  1019. assert(catch_node->pn_type == TOK_CATCH);
  1020. Stream_printf(f, "%*s", indent, "");
  1021. Stream_write_string(f, "catch (");
  1022. output_expression(catch_node->pn_kid1, f, false);
  1023. if (catch_node->pn_kid2) {
  1024. Stream_write_string(f, " if ");
  1025. output_expression(catch_node->pn_kid2, f, false);
  1026. }
  1027. Stream_write_string(f, ") {\n");
  1028. instrument_statement(catch_node->pn_kid3, f, indent + 2, false);
  1029. Stream_printf(f, "%*s", indent, "");
  1030. Stream_write_string(f, "}\n");
  1031. }
  1032. }
  1033. if (node->pn_kid3) {
  1034. Stream_printf(f, "%*s", indent, "");
  1035. Stream_write_string(f, "finally {\n");
  1036. instrument_statement(node->pn_kid3, f, indent + 2, false);
  1037. Stream_printf(f, "%*s", indent, "");
  1038. Stream_write_string(f, "}\n");
  1039. }
  1040. break;
  1041. case TOK_CATCH:
  1042. abort();
  1043. break;
  1044. case TOK_BREAK:
  1045. case TOK_CONTINUE:
  1046. assert(node->pn_arity == PN_NAME || node->pn_arity == PN_NULLARY);
  1047. Stream_printf(f, "%*s", indent, "");
  1048. Stream_write_string(f, node->pn_type == TOK_BREAK? "break": "continue");
  1049. if (node->pn_atom != NULL) {
  1050. Stream_write_char(f, ' ');
  1051. print_string_atom(node->pn_atom, f);
  1052. }
  1053. Stream_write_string(f, ";\n");
  1054. break;
  1055. case TOK_WITH:
  1056. assert(node->pn_arity == PN_BINARY);
  1057. Stream_printf(f, "%*s", indent, "");
  1058. Stream_write_string(f, "with (");
  1059. output_expression(node->pn_left, f, false);
  1060. Stream_write_string(f, ") {\n");
  1061. instrument_statement(node->pn_right, f, indent + 2, false);
  1062. Stream_printf(f, "%*s", indent, "");
  1063. Stream_write_string(f, "}\n");
  1064. break;
  1065. case TOK_VAR:
  1066. Stream_printf(f, "%*s", indent, "");
  1067. output_expression(node, f, false);
  1068. Stream_write_string(f, ";\n");
  1069. break;
  1070. case TOK_RETURN:
  1071. assert(node->pn_arity == PN_UNARY);
  1072. Stream_printf(f, "%*s", indent, "");
  1073. Stream_write_string(f, "return");
  1074. if (node->pn_kid != NULL) {
  1075. Stream_write_char(f, ' ');
  1076. output_expression(node->pn_kid, f, true);
  1077. }
  1078. Stream_write_string(f, ";\n");
  1079. break;
  1080. case TOK_SEMI:
  1081. assert(node->pn_arity == PN_UNARY);
  1082. Stream_printf(f, "%*s", indent, "");
  1083. if (node->pn_kid != NULL) {
  1084. output_expression(node->pn_kid, f, true);
  1085. }
  1086. Stream_write_string(f, ";\n");
  1087. break;
  1088. case TOK_COLON:
  1089. {
  1090. assert(node->pn_arity == PN_NAME);
  1091. Stream_printf(f, "%*s", indent < 2? 0: indent - 2, "");
  1092. print_string_atom(node->pn_atom, f);
  1093. Stream_write_string(f, ":\n");
  1094. JSParseNode * labelled = node->pn_expr;
  1095. if (labelled->pn_type == TOK_LEXICALSCOPE) {
  1096. labelled = labelled->pn_expr;
  1097. }
  1098. if (labelled->pn_type == TOK_LC) {
  1099. /* labelled block */
  1100. Stream_printf(f, "%*s", indent, "");
  1101. Stream_write_string(f, "{\n");
  1102. instrument_statement(labelled, f, indent + 2, false);
  1103. Stream_printf(f, "%*s", indent, "");
  1104. Stream_write_string(f, "}\n");
  1105. }
  1106. else {
  1107. /*
  1108. This one is tricky: can't output instrumentation between the label and the
  1109. statement it's supposed to label, so use output_statement instead of
  1110. instrument_statement.
  1111. */
  1112. output_statement(labelled, f, indent, false);
  1113. }
  1114. break;
  1115. }
  1116. case TOK_LEXICALSCOPE:
  1117. /* let statement */
  1118. assert(node->pn_arity == PN_NAME);
  1119. switch (node->pn_expr->pn_type) {
  1120. case TOK_LET:
  1121. /* let statement */
  1122. assert(node->pn_expr->pn_arity == PN_BINARY);
  1123. instrument_statement(node->pn_expr, f, indent, false);
  1124. break;
  1125. case TOK_LC:
  1126. /* block */
  1127. Stream_printf(f, "%*s", indent, "");
  1128. Stream_write_string(f, "{\n");
  1129. instrument_statement(node->pn_expr, f, indent + 2, false);
  1130. Stream_printf(f, "%*s", indent, "");
  1131. Stream_write_string(f, "}\n");
  1132. break;
  1133. case TOK_FOR:
  1134. instrument_statement(node->pn_expr, f, indent, false);
  1135. break;
  1136. default:
  1137. abort();
  1138. break;
  1139. }
  1140. break;
  1141. case TOK_LET:
  1142. switch (node->pn_arity) {
  1143. case PN_BINARY:
  1144. /* let statement */
  1145. Stream_printf(f, "%*s", indent, "");
  1146. Stream_write_string(f, "let (");
  1147. assert(node->pn_left->pn_type == TOK_LP);
  1148. assert(node->pn_left->pn_arity == PN_LIST);
  1149. instrument_declarations(node->pn_left, f);
  1150. Stream_write_string(f, ") {\n");
  1151. instrument_statement(node->pn_right, f, indent + 2, false);
  1152. Stream_printf(f, "%*s", indent, "");
  1153. Stream_write_string(f, "}\n");
  1154. break;
  1155. case PN_LIST:
  1156. /* let definition */
  1157. Stream_printf(f, "%*s", indent, "");
  1158. output_expression(node, f, false);
  1159. Stream_write_string(f, ";\n");
  1160. break;
  1161. default:
  1162. abort();
  1163. break;
  1164. }
  1165. break;
  1166. case TOK_DEBUGGER:
  1167. Stream_printf(f, "%*s", indent, "");
  1168. Stream_write_string(f, "debugger;\n");
  1169. break;
  1170. case TOK_SEQ:
  1171. /*
  1172. This occurs with the statement:
  1173. for (var a = b in c) {}
  1174. */
  1175. assert(node->pn_arity == PN_LIST);
  1176. for (JSParseNode * p = node->pn_head; p != NULL; p = p->pn_next) {
  1177. instrument_statement(p, f, indent, false);
  1178. }
  1179. break;
  1180. default:
  1181. fatal_source(file_id, node->pn_pos.begin.lineno, "unsupported node type (%d)", node->pn_type);
  1182. }
  1183. }
  1184. /*
  1185. See <Statements> in jsparse.h.
  1186. TOK_FUNCTION is handled as a statement and as an expression.
  1187. TOK_EXPORT, TOK_IMPORT are not handled.
  1188. */
  1189. static void instrument_statement(JSParseNode * node, Stream * f, int indent, bool is_jscoverage_if) {
  1190. if (node->pn_type != TOK_LC && node->pn_type != TOK_LEXICALSCOPE) {
  1191. uint16_t line = node->pn_pos.begin.lineno;
  1192. if (line > num_lines) {
  1193. fatal("file %s contains more than 65,535 lines", file_id);
  1194. }
  1195. /* the root node has line number 0 */
  1196. if (line != 0) {
  1197. Stream_printf(f, "%*s", indent, "");
  1198. Stream_printf(f, "_$jscoverage['%s'][%d]++;\n", file_id, line);
  1199. lines[line - 1] = 1;
  1200. }
  1201. }
  1202. output_statement(node, f, indent, is_jscoverage_if);
  1203. }
  1204. static bool characters_start_with(const jschar * characters, size_t line_start, size_t line_end, const char * prefix) {
  1205. const jschar * characters_end = characters + line_end;
  1206. const jschar * cp = characters + line_start;
  1207. const char * bp = prefix;
  1208. for (;;) {
  1209. if (*bp == '\0') {
  1210. return true;
  1211. }
  1212. else if (cp == characters_end) {
  1213. return false;
  1214. }
  1215. else if (*cp != *bp) {
  1216. return false;
  1217. }
  1218. bp++;
  1219. cp++;
  1220. }
  1221. }
  1222. static bool characters_are_white_space(const jschar * characters, size_t line_start, size_t line_end) {
  1223. /* XXX - other Unicode space */
  1224. const jschar * end = characters + line_end;
  1225. for (const jschar * p = characters + line_start; p < end; p++) {
  1226. jschar c = *p;
  1227. if (c == 0x9 || c == 0xB || c == 0xC || c == 0x20 || c == 0xA0) {
  1228. continue;
  1229. }
  1230. else {
  1231. return false;
  1232. }
  1233. }
  1234. return true;
  1235. }
  1236. static void error_reporter(JSContext * context, const char * message, JSErrorReport * report) {
  1237. warn_source(file_id, report->lineno, "%s", message);
  1238. }
  1239. void jscoverage_instrument_js(const char * id, const uint16_t * characters, size_t num_characters, Stream * output) {
  1240. file_id = id;
  1241. /* parse the javascript */
  1242. JSParseContext parse_context;
  1243. if (! js_InitParseContext(context, &parse_context, NULL, NULL, characters, num_characters, NULL, NULL, 1)) {
  1244. fatal("cannot create token stream from file %s", file_id);
  1245. }
  1246. JSErrorReporter old_error_reporter = JS_SetErrorReporter(context, error_reporter);
  1247. JSParseNode * node = js_ParseScript(context, global, &parse_context);
  1248. if (node == NULL) {
  1249. js_ReportUncaughtException(context);
  1250. fatal("parse error in file %s", file_id);
  1251. }
  1252. JS_SetErrorReporter(context, old_error_reporter);
  1253. num_lines = node->pn_pos.end.lineno;
  1254. lines = (char *) xmalloc(num_lines);
  1255. for (unsigned int i = 0; i < num_lines; i++) {
  1256. lines[i] = 0;
  1257. }
  1258. /* search code for conditionals */
  1259. exclusive_directives = xnew(bool, num_lines);
  1260. for (unsigned int i = 0; i < num_lines; i++) {
  1261. exclusive_directives[i] = false;
  1262. }
  1263. bool has_conditionals = false;
  1264. struct IfDirective * if_directives = NULL;
  1265. size_t line_number = 0;
  1266. size_t i = 0;
  1267. while (i < num_characters) {
  1268. if (line_number == UINT16_MAX) {
  1269. fatal("file %s contains more than 65,535 lines", file_id);
  1270. }
  1271. line_number++;
  1272. size_t line_start = i;
  1273. jschar c;
  1274. bool done = false;
  1275. while (! done && i < num_characters) {
  1276. c = characters[i];
  1277. switch (c) {
  1278. case '\r':
  1279. case '\n':
  1280. case 0x2028:
  1281. case 0x2029:
  1282. done = true;
  1283. break;
  1284. default:
  1285. i++;
  1286. }
  1287. }
  1288. size_t line_end = i;
  1289. if (i < num_characters) {
  1290. i++;
  1291. if (c == '\r' && i < num_characters && characters[i] == '\n') {
  1292. i++;
  1293. }
  1294. }
  1295. if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_IF")) {
  1296. has_conditionals = true;
  1297. if (characters_are_white_space(characters, line_start + 16, line_end)) {
  1298. exclusive_directives[line_number - 1] = true;
  1299. }
  1300. else {
  1301. struct IfDirective * if_directive = xnew(struct IfDirective, 1);
  1302. if_directive->condition_start = characters + line_start + 16;
  1303. if_directive->condition_end = characters + line_end;
  1304. if_directive->start_line = line_number;
  1305. if_directive->end_line = 0;
  1306. if_directive->next = if_directives;
  1307. if_directives = if_directive;
  1308. }
  1309. }
  1310. else if (characters_start_with(characters, line_start, line_end, "//#JSCOVERAGE_ENDIF")) {
  1311. for (struct IfDirective * p = if_directives; p != NULL; p = p->next) {
  1312. if (p->end_line == 0) {
  1313. p->end_line = line_number;
  1314. break;
  1315. }
  1316. }
  1317. }
  1318. }
  1319. /*
  1320. An instrumented JavaScript file has 4 sections:
  1321. 1. initialization
  1322. 2. instrumented source code
  1323. 3. conditionals
  1324. 4. original source code
  1325. */
  1326. Stream * instrumented = Stream_new(0);
  1327. instrument_statement(node, instrumented, 0, false);
  1328. js_FinishParseContext(context, &parse_context);
  1329. /* write line number info to the output */
  1330. Stream_write_string(output, "/* automatically generated by JSCoverage - do not edit */\n");
  1331. Stream_write_string(output, "if (typeof _$jscoverage === 'undefined') _$jscoverage = {};\n");
  1332. Stream_printf(output, "if (! _$jscoverage['%s']) {\n", file_id);
  1333. Stream_printf(output, " _$jscoverage['%s'] = [];\n", file_id);
  1334. for (int i = 0; i < num_lines; i++) {
  1335. if (lines[i]) {
  1336. Stream_printf(output, " _$jscoverage['%s'][%d] = 0;\n", file_id, i + 1);
  1337. }
  1338. }
  1339. Stream_write_string(output, "}\n");
  1340. free(lines);
  1341. lines = NULL;
  1342. free(exclusive_directives);
  1343. exclusive_directives = NULL;
  1344. /* conditionals */
  1345. if (has_conditionals) {
  1346. Stream_printf(output, "_$jscoverage['%s'].conditionals = [];\n", file_id);
  1347. }
  1348. /* copy the instrumented source code to the output */
  1349. Stream_write(output, instrumented->data, instrumented->length);
  1350. /* conditionals */
  1351. for (struct IfDirective * if_directive = if_directives; if_directive != NULL; if_directive = if_directive->next) {
  1352. Stream_write_string(output, "if (!(");
  1353. print_javascript(if_directive->condition_start, if_directive->condition_end - if_directive->condition_start, output);
  1354. Stream_write_string(output, ")) {\n");
  1355. Stream_printf(output, " _$jscoverage['%s'].conditionals[%d] = %d;\n", file_id, if_directive->start_line, if_directive->end_line);
  1356. Stream_write_string(output, "}\n");
  1357. }
  1358. /* free */
  1359. while (if_directives != NULL) {
  1360. struct IfDirective * if_directive = if_directives;
  1361. if_directives = if_directives->next;
  1362. free(if_directive);
  1363. }
  1364. /* copy the original source to the output */
  1365. Stream_printf(output, "_$jscoverage['%s'].source = ", file_id);
  1366. jscoverage_write_source(id, characters, num_characters, output);
  1367. Stream_printf(output, ";\n");
  1368. Stream_delete(instrumented);
  1369. file_id = NULL;
  1370. }
  1371. void jscoverage_write_source(const char * id, const jschar * characters, size_t num_characters, Stream * output) {
  1372. Stream_write_string(output, "[");
  1373. if (jscoverage_highlight) {
  1374. Stream * highlighted_stream = Stream_new(num_characters);
  1375. jscoverage_highlight_js(context, id, characters, num_characters, highlighted_stream);
  1376. size_t i = 0;
  1377. while (i < highlighted_stream->length) {
  1378. if (i > 0) {
  1379. Stream_write_char(output, ',');
  1380. }
  1381. Stream_write_char(output, '"');
  1382. bool done = false;
  1383. while (! done) {
  1384. char c = highlighted_stream->data[i];
  1385. switch (c) {
  1386. case 0x8:
  1387. /* backspace */
  1388. Stream_write_string(output, "\\b");
  1389. break;
  1390. case 0x9:
  1391. /* horizontal tab */
  1392. Stream_write_string(output, "\\t");
  1393. break;
  1394. case 0xa:
  1395. /* line feed (new line) */
  1396. done = true;
  1397. break;
  1398. /* IE doesn't support this */
  1399. /*
  1400. case 0xb:
  1401. Stream_write_string(output, "\\v");
  1402. break;
  1403. */
  1404. case 0xc:
  1405. /* form feed */
  1406. Stream_write_string(output, "\\f");
  1407. break;
  1408. case 0xd:
  1409. /* carriage return */
  1410. done = true;
  1411. if (i + 1 < highlighted_stream->length && highlighted_stream->data[i + 1] == '\n') {
  1412. i++;
  1413. }
  1414. break;
  1415. case '"':
  1416. Stream_write_string(output, "\\\"");
  1417. break;
  1418. case '\\':
  1419. Stream_write_string(output, "\\\\");
  1420. break;
  1421. default:
  1422. Stream_write_char(output, c);
  1423. break;
  1424. }
  1425. i++;
  1426. if (i >= highlighted_stream->length) {
  1427. done = true;
  1428. }
  1429. }
  1430. Stream_write_char(output, '"');
  1431. }
  1432. Stream_delete(highlighted_stream);
  1433. }
  1434. else {
  1435. size_t i = 0;
  1436. while (i < num_characters) {
  1437. if (i > 0) {
  1438. Stream_write_char(output, ',');
  1439. }
  1440. Stream_write_char(output, '"');
  1441. bool done = false;
  1442. while (! done) {
  1443. jschar c = characters[i];
  1444. switch (c) {
  1445. case 0x8:
  1446. /* backspace */
  1447. Stream_write_string(output, "\\b");
  1448. break;
  1449. case 0x9:
  1450. /* horizontal tab */
  1451. Stream_write_string(output, "\\t");
  1452. break;
  1453. case 0xa:
  1454. /* line feed (new line) */
  1455. done = true;
  1456. break;
  1457. /* IE doesn't support this */
  1458. /*
  1459. case 0xb:
  1460. Stream_write_string(output, "\\v");
  1461. break;
  1462. */
  1463. case 0xc:
  1464. /* form feed */
  1465. Stream_write_string(output, "\\f");
  1466. break;
  1467. case 0xd:
  1468. /* carriage return */
  1469. done = true;
  1470. if (i + 1 < num_characters && characters[i + 1] == '\n') {
  1471. i++;
  1472. }
  1473. break;
  1474. case '"':
  1475. Stream_write_string(output, "\\\"");
  1476. break;
  1477. case '\\':
  1478. Stream_write_string(output, "\\\\");
  1479. break;
  1480. case '&':
  1481. Stream_write_string(output, "&amp;");
  1482. break;
  1483. case '<':
  1484. Stream_write_string(output, "&lt;");
  1485. break;
  1486. case '>':
  1487. Stream_write_string(output, "&gt;");
  1488. break;
  1489. case 0x2028:
  1490. case 0x2029:
  1491. done = true;
  1492. break;
  1493. default:
  1494. if (32 <= c && c <= 126) {
  1495. Stream_write_char(output, c);
  1496. }
  1497. else {
  1498. Stream_printf(output, "&#%d;", c);
  1499. }
  1500. break;
  1501. }
  1502. i++;
  1503. if (i >= num_characters) {
  1504. done = true;
  1505. }
  1506. }
  1507. Stream_write_char(output, '"');
  1508. }
  1509. }
  1510. Stream_write_string(output, "]");
  1511. }
  1512. void jscoverage_copy_resources(const char * destination_directory) {
  1513. copy_resource("jscoverage.html", destination_directory);
  1514. copy_resource("jscoverage.css", destination_directory);
  1515. copy_resource("jscoverage.js", destination_directory);
  1516. copy_resource("jscoverage-ie.css", destination_directory);
  1517. copy_resource("jscoverage-throbber.gif", destination_directory);
  1518. copy_resource("jscoverage-highlight.css", destination_directory);
  1519. }
  1520. /*
  1521. coverage reports
  1522. */
  1523. struct FileCoverageList {
  1524. FileCoverage * file_coverage;
  1525. struct FileCoverageList * next;
  1526. };
  1527. struct Coverage {
  1528. JSHashTable * coverage_table;
  1529. struct FileCoverageList * coverage_list;
  1530. };
  1531. static int compare_strings(const void * p1, const void * p2) {
  1532. return strcmp((const char *) p1, (const char *) p2) == 0;
  1533. }
  1534. Coverage * Coverage_new(void) {
  1535. Coverage * result = (Coverage *) xmalloc(sizeof(Coverage));
  1536. result->coverage_table = JS_NewHashTable(1024, JS_HashString, compare_strings, NULL, NULL, NULL);
  1537. if (result->coverage_table == NULL) {
  1538. fatal("cannot create hash table");
  1539. }
  1540. result->coverage_list = NULL;
  1541. return result;
  1542. }
  1543. void Coverage_delete(Coverage * coverage) {
  1544. JS_HashTableDestroy(coverage->coverage_table);
  1545. struct FileCoverageList * p = coverage->coverage_list;
  1546. while (p != NULL) {
  1547. free(p->file_coverage->coverage_lines);
  1548. if (p->file_coverage->source_lines != NULL) {
  1549. for (uint32 i = 0; i < p->file_coverage->num_source_lines; i++) {
  1550. free(p->file_coverage->source_lines[i]);
  1551. }
  1552. free(p->file_coverage->source_lines);
  1553. }
  1554. free(p->file_coverage->id);
  1555. free(p->file_coverage);
  1556. struct FileCoverageList * q = p;
  1557. p = p->next;
  1558. free(q);
  1559. }
  1560. free(coverage);
  1561. }
  1562. struct EnumeratorArg {
  1563. CoverageForeachFunction f;
  1564. void * p;
  1565. };
  1566. static intN enumerator(JSHashEntry * entry, intN i, void * arg) {
  1567. struct EnumeratorArg * enumerator_arg = (struct EnumeratorArg *) arg;
  1568. enumerator_arg->f((FileCoverage *) entry->value, i, enumerator_arg->p);
  1569. return 0;
  1570. }
  1571. void Coverage_foreach_file(Coverage * coverage, CoverageForeachFunction f, void * p) {
  1572. struct EnumeratorArg enumerator_arg;
  1573. enumerator_arg.f = f;
  1574. enumerator_arg.p = p;
  1575. JS_HashTableEnumerateEntries(coverage->coverage_table, enumerator, &enumerator_arg);
  1576. }
  1577. int jscoverage_parse_json(Coverage * coverage, const uint8_t * json, size_t length) {
  1578. int result = 0;
  1579. jschar * base = js_InflateString(context, (char *) json, &length);
  1580. if (base == NULL) {
  1581. fatal("out of memory");
  1582. }
  1583. jschar * parenthesized_json = xnew(jschar, addst(length, 2));
  1584. parenthesized_json[0] = '(';
  1585. memcpy(parenthesized_json + 1, base, mulst(length, sizeof(jschar)));
  1586. parenthesized_json[length + 1] = ')';
  1587. JS_free(context, base);
  1588. JSParseContext parse_context;
  1589. if (! js_InitParseContext(context, &parse_context, NULL, NULL, parenthesized_json, length + 2, NULL, NULL, 1)) {
  1590. free(parenthesized_json);
  1591. return -1;
  1592. }
  1593. JSParseNode * root = js_ParseScript(context, global, &parse_context);
  1594. free(parenthesized_json);
  1595. JSParseNode * semi = NULL;
  1596. JSParseNode * object = NULL;
  1597. if (root == NULL) {
  1598. result = -1;
  1599. goto done;
  1600. }
  1601. /* root node must be TOK_LC */
  1602. if (root->pn_type != TOK_LC) {
  1603. result = -1;
  1604. goto done;
  1605. }
  1606. semi = root->pn_u.list.head;
  1607. /* the list must be TOK_SEMI and it must contain only one element */
  1608. if (semi->pn_type != TOK_SEMI || semi->pn_next != NULL) {
  1609. result = -1;
  1610. goto done;
  1611. }
  1612. object = semi->pn_kid;
  1613. /* this must be an object literal */
  1614. if (object->pn_type != TOK_RC) {
  1615. result = -1;
  1616. goto done;
  1617. }
  1618. for (JSParseNode * p = object->pn_head; p != NULL; p = p->pn_next) {
  1619. /* every element of this list must be TOK_COLON */
  1620. if (p->pn_type != TOK_COLON) {
  1621. result = -1;
  1622. goto done;
  1623. }
  1624. /* the key must be a string representing the file */
  1625. JSParseNode * key = p->pn_left;
  1626. if (key->pn_type != TOK_STRING || ! ATOM_IS_STRING(key->pn_atom)) {
  1627. result = -1;
  1628. goto done;
  1629. }
  1630. char * id_bytes = JS_GetStringBytes(ATOM_TO_STRING(key->pn_atom));
  1631. /* the value must be an object literal OR an array */
  1632. JSParseNode * value = p->pn_right;
  1633. if (! (value->pn_type == TOK_RC || value->pn_type == TOK_RB)) {
  1634. result = -1;
  1635. goto done;
  1636. }
  1637. JSParseNode * array = NULL;
  1638. JSParseNode * source = NULL;
  1639. if (value->pn_type == TOK_RB) {
  1640. /* an array */
  1641. array = value;
  1642. }
  1643. else if (value->pn_type == TOK_RC) {
  1644. /* an object literal */
  1645. if (value->pn_count != 2) {
  1646. result = -1;
  1647. goto done;
  1648. }
  1649. for (JSParseNode * element = value->pn_head; element != NULL; element = element->pn_next) {
  1650. if (element->pn_type != TOK_COLON) {
  1651. result = -1;
  1652. goto done;
  1653. }
  1654. JSParseNode * left = element->pn_left;
  1655. if (left->pn_type != TOK_STRING || ! ATOM_IS_STRING(left->pn_atom)) {
  1656. result = -1;
  1657. goto done;
  1658. }
  1659. const char * s = JS_GetStringBytes(ATOM_TO_STRING(left->pn_atom));
  1660. if (strcmp(s, "coverage") == 0) {
  1661. array = element->pn_right;
  1662. if (array->pn_type != TOK_RB) {
  1663. result = -1;
  1664. goto done;
  1665. }
  1666. }
  1667. else if (strcmp(s, "source") == 0) {
  1668. source = element->pn_right;
  1669. if (source->pn_type != TOK_RB) {
  1670. result = -1;
  1671. goto done;
  1672. }
  1673. }
  1674. else {
  1675. result = -1;
  1676. goto done;
  1677. }
  1678. }
  1679. }
  1680. else {
  1681. result = -1;
  1682. goto done;
  1683. }
  1684. if (array == NULL) {
  1685. result = -1;
  1686. goto done;
  1687. }
  1688. /* look up the file in the coverage table */
  1689. FileCoverage * file_coverage = (FileCoverage *) JS_HashTableLookup(coverage->coverage_table, id_bytes);
  1690. if (file_coverage == NULL) {
  1691. /* not there: create a new one */
  1692. char * id = xstrdup(id_bytes);
  1693. file_coverage = (FileCoverage *) xmalloc(sizeof(FileCoverage));
  1694. file_coverage->id = id;
  1695. file_coverage->num_coverage_lines = array->pn_count;
  1696. file_coverage->coverage_lines = xnew(int, array->pn_count);
  1697. file_coverage->source_lines = NULL;
  1698. /* set coverage for all lines */
  1699. uint32 i = 0;
  1700. for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
  1701. if (element->pn_type == TOK_NUMBER) {
  1702. file_coverage->coverage_lines[i] = (int) element->pn_dval;
  1703. }
  1704. else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
  1705. file_coverage->coverage_lines[i] = -1;
  1706. }
  1707. else {
  1708. result = -1;
  1709. goto done;
  1710. }
  1711. }
  1712. assert(i == array->pn_count);
  1713. /* add to the hash table */
  1714. JS_HashTableAdd(coverage->coverage_table, id, file_coverage);
  1715. struct FileCoverageList * coverage_list = (FileCoverageList *) xmalloc(sizeof(struct FileCoverageList));
  1716. coverage_list->file_coverage = file_coverage;
  1717. coverage_list->next = coverage->coverage_list;
  1718. coverage->coverage_list = coverage_list;
  1719. }
  1720. else {
  1721. /* sanity check */
  1722. assert(strcmp(file_coverage->id, id_bytes) == 0);
  1723. if (file_coverage->num_coverage_lines != array->pn_count) {
  1724. result = -2;
  1725. goto done;
  1726. }
  1727. /* merge the coverage */
  1728. uint32 i = 0;
  1729. for (JSParseNode * element = array->pn_head; element != NULL; element = element->pn_next, i++) {
  1730. if (element->pn_type == TOK_NUMBER) {
  1731. if (file_coverage->coverage_lines[i] == -1) {
  1732. result = -2;
  1733. goto done;
  1734. }
  1735. file_coverage->coverage_lines[i] += (int) element->pn_dval;
  1736. }
  1737. else if (element->pn_type == TOK_PRIMARY && element->pn_op == JSOP_NULL) {
  1738. if (file_coverage->coverage_lines[i] != -1) {
  1739. result = -2;
  1740. goto done;
  1741. }
  1742. }
  1743. else {
  1744. result = -1;
  1745. goto done;
  1746. }
  1747. }
  1748. assert(i == array->pn_count);
  1749. }
  1750. /* if this JSON file has source, use it */
  1751. if (file_coverage->source_lines == NULL && source != NULL) {
  1752. file_coverage->num_source_lines = source->pn_count;
  1753. file_coverage->source_lines = xnew(char *, source->pn_count);
  1754. uint32 i = 0;
  1755. for (JSParseNode * element = source->pn_head; element != NULL; element = element->pn_next, i++) {
  1756. if (element->pn_type != TOK_STRING) {
  1757. result = -1;
  1758. goto done;
  1759. }
  1760. file_coverage->source_lines[i] = xstrdup(JS_GetStringBytes(ATOM_TO_STRING(element->pn_atom)));
  1761. }
  1762. assert(i == source->pn_count);
  1763. }
  1764. }
  1765. done:
  1766. js_FinishParseContext(context, &parse_context);
  1767. return result;
  1768. }