/lib/cfa2/jscfa.js
JavaScript | 4374 lines | 3399 code | 376 blank | 599 comment | 495 complexity | 3494bb25257993d63f58adbd0e432c64 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
Large files files are truncated, but you can click here to view the full file
- /* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an 'AS IS' basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Bespin.
- *
- * The Initial Developer of the Original Code is
- * Dimitris Vardoulakis <dimvar@gmail.com>
- * Portions created by the Initial Developer are Copyright (C) 2010
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Dimitris Vardoulakis <dimvar@gmail.com>
- * Patrick Walton <pcwalton@mozilla.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
- /*
- * Narcissus - JS implemented in JS.
- *
- * Control-flow analysis to infer types. The output is in ctags format.
- */
- /* (Possible) TODOs:
- * - now all objs are in heap. If it's too imprecise, treat them as heap vars.
- * Create on stack & heap, and if heap changes when u need the obj then join.
- * - representation of Aobj: in the common case, an abstract obj has one proto
- * and one constructor. Specialize for this case.
- */
- /*
- * Semantics of: function foo (args) body:
- * It's not the same as: var foo = function foo (args) body
- * If it appears in a script then it's hoisted at the top, so it's in funDecls
- * If it appears in a block then it's visible after it's appearance, in the
- * whole rest of the script!!
- * {foo(); {function foo() {print("foo");}}; foo();}
- * The 1st call to foo throws, but if you remove it the 2nd call succeeds.
- */
- /* (POSSIBLY) UNSOUND ASSUMPTIONS:
- * - Won't iterate loops to fixpt.
- * - Return undefined not tracked, eg (if sth return 12;) always returns number.
- * - If the prototype property of a function object foo is accessed in a weird
- * way, eg foo["proto" + "type"] the analysis won't figure it out.
- * - when popping from an array, I do nothing. This is hard to make sound.
- */
- ////////////////////////////////////////////////////////////////////////////////
- ///////////////////////////// UTILITIES /////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////
- if (!Array.prototype.forEach)
- Array.prototype.forEach = function(fun) {
- for (var i = 0, len = this.length; i < len; i++)
- /* if (i in this) */ fun(this[i], i, this);
- };
- // search for an elm in the array that satisfies pred
- Array.prototype.member = function(pred) {
- for (var i = 0, len = this.length; i < len; i++)
- /* if (i in this) */ if (pred(this[i])) return this[i];
- return false;
- };
- Array.prototype.memq = function(sth) {
- for (var i = 0, len = this.length; i < len; i++)
- /* if (i in this) */ if (sth === this[i]) return this[i];
- return false;
- };
- // starting at index, remove all elms that satisfy the pred in linear time.
- Array.prototype.rmElmAfterIndex = function(pred, index) {
- if (index >= this.length) return;
- for (var i = index, j = index, len = this.length; i < len; i++)
- if (!pred(this[i])) this[j++] = this[i];
- this.length = j;
- };
- // remove all duplicates from array (keep first occurence of each elm)
- // pred determines the equality of elms
- Array.prototype.rmDups = function(pred) {
- var i = 0, self = this;
- while (i < (this.length - 1)) {
- this.rmElmAfterIndex(function(elm) { return pred(elm, self[i]); }, i+1);
- i++;
- }
- };
- // compare two arrays for structural equality
- function arrayeq(eq, a1, a2) {
- var len = a1.length, i;
- if (len !== a2.length) return false;
- for (i=0; i<len; i++) if (!eq(a1[i], a2[i])) return false;
- return true;
- }
- function buildArray(size, elm) {
- var a = new Array(size);
- for (var i=0; i<size; i++) a[i] = elm;
- return a;
- }
- function buildString(size, elm) {
- return buildArray(size, elm).join("");
- }
- // merge two sorted arrays of numbers into a sorted new array, remove dups!
- function arraymerge(a1, a2) {
- var i=0, j=0, len1 = a1.length, len2 = a2.length, a = new Array();
- while (true) {
- if (i === len1) {
- for (; j < len2; j++) a.push(a2[j]);
- return a;
- }
- if (j === len2) {
- for (; i<len1; i++) a.push(a1[i]);
- return a;
- }
- var diff = a1[i] - a2[j];
- if (diff < 0)
- a.push(a1[i++]);
- else if (diff > 0)
- a.push(a2[j++]);
- else
- i++;
- }
- }
- const UNHANDLED_CONSTRUCT = 0;
- const CFA_ERROR = 1;
- // number, string -> Error
- // returns an Error w/ a "code" property, so that DrJS can classify errors
- function errorWithCode(code, msg) {
- var e = new Error(msg);
- e.code = code;
- return e;
- }
- ////////////////////////////////////////////////////////////////////////////////
- //////////////////// PREPARE AST FOR FLOW ANALYSIS ///////////////////////
- ////////////////////////////////////////////////////////////////////////////////
- var jsparse = require('../../narcissus/lib/parser');
- var Node = jsparse.Node;
- const DECLARED_FORM = jsparse.DECLARED_FORM;
- const STATEMENT_FORM = jsparse.STATEMENT_FORM;
- eval(require('../../narcissus/lib/definitions').consts);
- var print = console.log;
- var fs = require('fs');
- function printf(fd, s) { fs.writeSync(fd, s, null, encoding='utf8'); }
- // count is used to generate a unique ID for each node in the AST.
- var count;
- function newCount() { return ++count; }
- // Use token_count to get fresh IDs for new non-terminals
- var token_count = (require('../../narcissus/lib/definitions').tokens).length;
- const DOT_PROTO = token_count++;
- const ARGUMENTS = token_count++;
- const PLUS_ASSIGN = token_count++;
- // Instead of a flat long case dispatch, arities create a tree-like dispatch.
- // Nodes are grouped in arities by how we recur down their structure.
- const NULLARY = [NULL, THIS, TRUE, FALSE, IDENTIFIER, NUMBER, STRING, REGEXP];
- const UNARY = [DELETE, VOID, TYPEOF, NOT, BITWISE_NOT, UNARY_PLUS, UNARY_MINUS,
- NEW, INCREMENT, DECREMENT, DOT_PROTO, ARGUMENTS];
- const BINARY = [BITWISE_OR, BITWISE_XOR, BITWISE_AND, EQ, NE, STRICT_EQ,
- STRICT_NE, LT, LE, GE, GT, INSTANCEOF, LSH, RSH, URSH, PLUS,
- MINUS, MUL, DIV, MOD, AND, OR, ASSIGN, INDEX, IN, DOT,
- PLUS_ASSIGN];
- const N_ARY = [COMMA, ARRAY_INIT];
- // expr node -> stm node
- function semiNode(n) {
- var sn = new Node(n.tokenizer, {type:SEMICOLON});
- sn.expression = n;
- return sn;
- }
- // tokenizer, string -> identifier node
- function idNode(t, name) {
- var n = new Node(t, {type:IDENTIFIER});
- n.name = n.value = name;
- return n;
- }
- var astSize; // calculated for statistics
- // node, optional string -> node
- // Does some cleanup on the input expression node in-place.
- // funName is used to name some anonymous funs using heuristics from the context
- function fixExp(n, funName) {
- var nt = n.type, ch = n.children;
- astSize++;
- function fixe(n, i, ch) { ch[i] = fixExp(n); }
- if (NULLARY.memq(nt)) {
- if (nt === IDENTIFIER) n.name = n.value;
- else if (nt === STRING) n.value += "-";
- }
- else if (UNARY.memq(nt)) {
- if (nt === UNARY_MINUS && ch[0].type === NUMBER) {
- n.type = NUMBER;
- n.value = -ch[0].value;
- n.children = [];
- return n;
- }
- if (nt === NEW) { // unify NEW and NEW_WITH_ARGS
- n.type = NEW_WITH_ARGS;
- ch.push(new Node(n.tokenizer, {type:LIST, children:[]}));
- }
- ch[0] = fixExp(ch[0]);
- }
- else if (BINARY.memq(nt)) {
- if (nt === INDEX) {
- ch.forEach(fixe);
- if (ch[1].type === NUMBER) {
- ch[1].type = STRING;
- ch[1].value += "-";
- }
- return n;
- }
- else if (nt === DOT) {
- var ch1 = ch[1];
- // jsparse makes the 1st child of DOTs an ID b/c that's what is parsed.
- // For an evaluator it makes more sense to make the 1st child a string,
- // b/c functions that recur on DOTs won't try to look it up as a name.
- ch1.type = STRING;
- delete ch1.name;
- // DOT_PROTO has no new semantic meaning, it's an optimization so that we
- // dont check at every property access if the property name is "prototype"
- if (ch1.value === "prototype") n.type = DOT_PROTO;
- }
- else if (nt === ASSIGN && ch[0].assignOp === PLUS)
- n.type = PLUS_ASSIGN;
- else if (nt === ASSIGN && !ch[0].assignOp) {
- var n0 = ch[0];
- ch[0] = fixExp(n0);
- if (n0.type === IDENTIFIER)
- funName = n0.name;
- else if (n0.type === DOT) {
- var fname = n0.children[1].value;
- funName = fname.substring(0, fname.length - 1);
- }
- ch[1] = fixExp(ch[1], funName);
- return n;
- }
- ch.forEach(fixe);
- }
- else if (nt === HOOK || N_ARY.memq(nt)) {
- // Uncomment this if Narcissus produces empty COMMA nodes.
- if (nt === COMMA && ch.length === 0)
- ch.push(new Node(n.tokenizer, {type:TRUE}));
- if (nt === ARRAY_INIT) {
- ch.forEach(function(kid, i, ch) {
- if (kid === null) ch[i] = idNode(n.tokenizer, "undefined");
- });
- }
- ch.forEach(fixe);
- }
- else if (nt === CALL || nt === NEW_WITH_ARGS) {
- ch[0] = fixExp(ch[0]);
- ch[1].children.forEach(fixe);
- }
- else if (nt === FUNCTION) {
- fixFun(n, funName);
- }
- else if (nt === OBJECT_INIT) {
- ch.forEach(function(prop) {
- if (prop.type === GETTER || prop.type === SETTER) {
- // FIXME: for now, just don't crash on getters/setters
- prop.children = [new Node(n.tokenizer,
- { type : STRING, value : prop.name + "-" }),
- new Node(n.tokenizer, {type:TRUE})];
- }
- else {
- var pch = prop.children, pch0 = pch[0], pname = pch0.value;
- pch0.type = STRING;
- delete pch0.name;
- pch0.value += "-";
- pch[1] = fixExp(pch[1], pname);
- }
- });
- }
- else if (nt === LET_BLOCK) {
- n.varDecls.forEach(function(vd, i, vds) {
- vd.name = vd.value;
- vd.initializer && (vd.initializer = fixExp(vd.initializer, vd.value));
- });
- n.expression = fixExp(n.expression);
- }
- else if (nt === YIELD) { // FIXME: for now, just don't crash on yield
- n.type = TRUE;
- delete n.value
- }
- return n;
- }
- // node, optional string -> void
- function fixFun(n, funName) {
- var t = n.tokenizer;
- // replace name w/ a property fname which is an IDENTIFIER node.
- n.fname = idNode(t, n.name || funName);
- delete n.name;
- // turn the formals to nodes, I'll want to attach stuff to them later.
- n.params.forEach(function(p, i, ps) { ps[i] = idNode(t, p); });
- // don't tag builtin funs, they never have RETURN when used as constructors.
- n.hasReturn = fixStm(n.body);
- }
- // Array of id nodes, tokenizer -> comma node
- // Convert variable initializations (coming from VAR,CONST,LET) to assignments.
- function initsToAssigns(inits, t) {
- var n, vdecl, a, i, len, ch;
- n = new Node(t, {type:COMMA});
- ch = n.children;
- for (i = 0, len = inits.length; i < len; i++) {
- vdecl = inits[i];
- if (vdecl.initializer) {
- a = new Node(vdecl.tokenizer, {type:ASSIGN});
- a.children = [idNode(vdecl.tokenizer, vdecl.name), vdecl.initializer];
- ch.push(a);
- }
- }
- // if no initialization values, push dummy node to avoid empty comma node.
- ch.length || ch.push(new Node(t, {type:TRUE}));
- return n;
- }
- // node, optional script node -> boolean
- // returns true iff n contains RETURN directly (not RETURNs of inner functions).
- function fixStm(n, parentScript) {
- var i, j, n2, ans1, ans2, ch = n.children;
- astSize++;
-
- switch (n.type) {
- case SCRIPT:
- case BLOCK:
- var ans1 = false, parscr = (n.type === SCRIPT) ? n : parentScript;
- i=0;
- while (i < ch.length) {
- n2 = ch[i];
- switch (n2.type) {
- case VAR:
- case CONST:
- ch.splice(i++, 1,
- semiNode(fixExp(initsToAssigns(n2.children, n2.tokenizer))));
- break;
- case SWITCH:
- if (n2.cases.length === 0) {// switch w/out branches becomes semi node
- n2.discriminant = fixExp(n2.discriminant);
- ch[i] = semiNode(n2.discriminant);
- }
- else
- ans1 = fixStm(n2, parscr) || ans1;
- i++;
- break;
- case FUNCTION: //rm functions from Script bodies, they're in funDecls
- fixFun(n2);
- // To handle funs in blocks, unsoundly add them to funDecls
- if (n2.functionForm === STATEMENT_FORM) parscr.funDecls.push(n2);
- ch.splice(i, 1);
- // The code below is for when we don't handle funs in blocks.
- // if (n2.functionForm === DECLARED_FORM)
- // ch.splice(i, 1);
- // else
- // ++i;
- break;
- case SEMICOLON: // remove empty SEMICOLON nodes
- if (n2.expression == null) {
- ch.splice(i, 1);
- break;
- } // o/w fall thru to fix the node
- default:
- ans1 = fixStm(n2, parscr) || ans1;
- i++;
- break;
- }
- }
- return ans1;
- case LET: // let definitions
- n.children.forEach(function(vd) {
- vd.name = vd.value;
- vd.initializer && (vd.initializer = fixExp(vd.initializer));
- });
- return false;
- case LET_BLOCK:
- n.varDecls.forEach(function(vd) {
- vd.name = vd.value;
- vd.initializer && (vd.initializer = fixExp(vd.initializer));
- });
- if (n.expression) { // let-exp in stm context
- n.expression = fixExp(n.expression);
- n.block = semiNode(n.expression);
- delete n.expression;
- return false;
- }
- else // let-stm
- return fixStm(n.block, parentScript);
- case BREAK: case CONTINUE: case DEBUGGER:
- n.type = BLOCK;
- n.varDecls = [];
- n.children = [];
- return false;
- case SEMICOLON:
- if (!n.expression)
- n.expression = new Node(n.tokenizer, {type:TRUE});
- else
- n.expression = fixExp(n.expression); //n.expression can't be null
- return false;
- case IF:
- n.condition = fixExp(n.condition);
- ans1 = fixStm(n.thenPart, parentScript);
- return (n.elsePart && fixStm(n.elsePart, parentScript)) || ans1;
- case SWITCH:
- n.discriminant = fixExp(n.discriminant);
- ans1 = false;
- n.cases.forEach( function(branch) {
- branch.caseLabel && (branch.caseLabel = fixExp(branch.caseLabel));
- ans2 = fixStm(branch.statements, parentScript);
- ans1 = ans1 || ans2;
- });
- return ans1;
- case FOR:
- n2 = n.setup;
- if (n2) {
- if (n2.type === VAR || n2.type === CONST)
- n.setup = fixExp(initsToAssigns(n2.children, n2.tokenizer));
- else
- n.setup = fixExp(n2);
- }
- n.condition && (n.condition = fixExp(n.condition));
- n.update && (n.update = fixExp(n.update));
- return fixStm(n.body, parentScript);
- case FOR_IN:
- n.iterator = fixExp(n.iterator);
- n.object = fixExp(n.object);
- if (n.body.type !== BLOCK) {
- var fibody = new Node(n.tokenizer, {type:BLOCK});
- fibody.children = [n.body];
- n.body = fibody;
- }
- return fixStm(n.body, parentScript);
-
- case WHILE:
- case DO:
- n.condition = fixExp(n.condition);
- return fixStm(n.body, parentScript);
- case TRY: // turn the varName of each catch-clause to a node called exvar
- ans1 = fixStm(n.tryBlock, parentScript);
- n.catchClauses.forEach(function(clause) {
- clause.exvar = idNode(clause.tokenizer, clause.varName);
- clause.guard && (clause.guard = fixExp(clause.guard));
- ans2 = fixStm(clause.block, parentScript);
- ans1 = ans1 || ans2;
- });
- return (n.finallyBlock && fixStm(n.finallyBlock, parentScript)) || ans1;
- case THROW:
- n.exception = fixExp(n.exception);
- return false;
- case RETURN:
- if (n.value === undefined)
- n.value = idNode(n.tokenizer, "undefined");
- else
- n.value = fixExp(n.value);
- return true;
-
- case VAR: case CONST: // very rare to not appear in a block or script.
- n.type = SEMICOLON;
- n.expression = fixExp(initsToAssigns(n.children, n.tokenizer));
- ch = [];
- return false;
- case FUNCTION:
- n2 = new Node(n.tokenizer, {type:FUNCTION});
- n2.name = n.name; delete n.name;
- n2.params = n.params; delete n.params;
- n2.functionForm = n.functionForm; delete n.functionForm;
- n2.body = n.body; delete n.body;
- fixFun(n2);
- // To handle funs not in scripts, unsoundly add them to funDecls
- parentScript.funDecls.push(n2);
- n.type = SEMICOLON;
- n.expression = new Node(n.tokenizer, {type:TRUE});
- return false;
- case WITH:
- n.object = fixExp(n.object);
- return fixStm(n.body, parentScript);
- case LABEL: //replace LABEL nodes by their statement (forget labels)
- n.type = BLOCK;
- n.varDecls = [];
- n.children = [n.statement];
- delete n.statement;
- return fixStm(n.children[0], parentScript);
-
- default:
- throw errorWithCode(UNHANDLED_CONSTRUCT,
- "fixStm: " + n.type + ", line " + n.lineno);
- }
- }
- // Invariants of the AST after fixStm:
- // - no NEW nodes, they became NEW_WITH_ARGS
- // - the formals of functions are nodes, not strings
- // - functions have a property fname which is an IDENTIFIER node, name deleted
- // - no VAR and CONST nodes, they've become semicolon comma nodes.
- // - no BREAK and CONTINUE nodes.
- // Unfortunately, this isn't independent of exceptions.
- // If a finally-block breaks or continues, the exception isn't propagated.
- // I will falsely propagate it (still sound, just more approximate).
- // - no LABEL nodes
- // - function nodes only in blocks, not in scripts
- // - no empty SEMICOLON nodes
- // - no switches w/out branches
- // - each catch clause has a property exvar which is an IDENTIFIER node
- // - all returns have .value (the ones that didn't, got an undefined)
- // - the lhs of a property initializer of an OBJECT_INIT is always a string
- // - the property names in DOT and OBJECT_INIT end with a dash.
- // - there is no DOT whose 2nd arg is "prototype", they've become NODE_PROTOs.
- // - the second kid of DOT is always a STRING, not an IDENTIFIER.
- // - value of a NUMBER can be negative (UNARY_MINUS of constant became NUMBER).
- // - the operator += has its own non-terminal, PLUS_ASSIGN.
- // - each function node has a property hasReturn to show if it uses RETURN.
- // - there is no INDEX whose 2nd arg has type NUMBER, they've become STRINGs.
- // - The body of a LET_BLOCK in statement context is always a statement.
- // - FUNCTIONs in BLOCKs are added to funDecls of enclosing SCRIPT.
- // - Array literals don't have holes (null children) in them.
- // - The body of FOR_IN is always a BLOCK.
- function walkASTgenerator() {
- var jt = new Array(token_count); // jump table
- var o = {}; // dummy object for the calls to apply
-
- function recur(n) { return jt[n.type].apply(o, arguments); }
- function override(tt, fun) { jt[tt] = fun; }
- function _nokids() {}
- NULLARY.forEach(function(tt) { jt[tt] = _nokids; });
- function _haskids() {
- var args = arguments, ch = args[0].children;
- ch.forEach(function(kid) {
- args[0] = kid;
- recur.apply(o, args);
- });
- }
- [HOOK].concat(N_ARY, UNARY, BINARY).forEach(function(tt) {jt[tt]=_haskids;});
- jt[CALL] = jt[NEW_WITH_ARGS] = function() {
- var args = arguments, n = args[0], ch = n.children;
- args[0] = ch[0];
- recur.apply(o, args);
- ch[1].children.forEach(function(kid) {
- args[0] = kid;
- recur.apply(o, args);
- });
- };
- jt[OBJECT_INIT] = function() {
- var args = arguments;
- args[0].children.forEach(function(kid) {
- var ch = kid.children;
- args[0] = ch[0];
- recur.apply(o, args);
- args[0] = ch[1];
- recur.apply(o, args);
- });
- };
- jt[FUNCTION] = function() {
- arguments[0] = arguments[0].body;
- recur.apply(o, arguments);
- };
- jt[SCRIPT] = function() {
- var args = arguments, n = args[0];
- n.funDecls.forEach(function(fd) {
- args[0] = fd;
- recur.apply(o, args);
- });
- n.children.forEach(function(kid) {
- args[0] = kid;
- recur.apply(o, args);
- });
- };
- jt[BLOCK] = function() {
- var args = arguments;
- args[0].children.forEach(function(kid) {
- args[0] = kid;
- recur.apply(o, args);
- });
- };
- jt[SEMICOLON] = function() {
- arguments[0] = arguments[0].expression;
- recur.apply(o, arguments);
- };
- jt[IF] = function() {
- var n = arguments[0];
- arguments[0] = n.condition;
- recur.apply(o, arguments);
- arguments[0] = n.thenPart;
- recur.apply(o, arguments);
- if (n.elsePart) {
- arguments[0] = n.elsePart;
- recur.apply(o, arguments);
- }
- };
- jt[SWITCH] = function() {
- var args = arguments, n = args[0];
- args[0] = n.discriminant;
- recur.apply(o, args);
- n.cases.forEach(function(branch) {
- if (branch.caseLabel) {
- args[0] = branch.caseLabel;
- recur.apply(o, args);
- }
- args[0] = branch.statements;
- recur.apply(o, args);
- });
- };
- jt[FOR] = function() {
- var n = arguments[0];
- if (n.setup) {
- arguments[0] = n.setup;
- recur.apply(o, arguments);
- }
- if (n.condition) {
- arguments[0] = n.condition;
- recur.apply(o, arguments);
- }
- if (n.update) {
- arguments[0] = n.update;
- recur.apply(o, arguments);
- }
- arguments[0] = n.body;
- recur.apply(o, arguments);
- };
- jt[FOR_IN] = function() {
- var n = arguments[0];
- arguments[0] = n.iterator;
- recur.apply(o, arguments);
- arguments[0] = n.object;
- recur.apply(o, arguments);
- arguments[0] = n.body;
- recur.apply(o, arguments);
- };
- jt[WHILE] = jt[DO] = function() {
- var n = arguments[0];
- arguments[0] = n.condition;
- recur.apply(o, arguments);
- arguments[0] = n.body;
- recur.apply(o, arguments);
- };
- jt[TRY] = function() {
- var args = arguments, n = args[0];
- args[0] = n.tryBlock;
- recur.apply(o, args);
- n.catchClauses.forEach(function(clause) {
- if (clause.guard) {
- args[0] = clause.guard;
- recur.apply(o, args);
- }
- args[0] = clause.block;
- recur.apply(o, args);
- });
- if (n.finallyBlock) {
- args[0] = n.finallyBlock;
- recur.apply(o, args);
- }
- };
- jt[THROW] = function() {
- arguments[0] = arguments[0].exception;
- recur.apply(o, arguments);
- };
- jt[RETURN] = function() {
- var n = arguments[0];
- if (n.value) {
- arguments[0] = n.value;
- recur.apply(o, arguments);
- }
- };
- jt[WITH] = function() {
- var n = arguments[0];
- arguments[0] = n.object;
- recur.apply(o, arguments);
- arguments[0] = n.body;
- recur.apply(o, arguments);
- };
- jt[LET_BLOCK] = function() {
- var args = arguments, n = args[0];
- n.varDecls.forEach(function(vd) {
- (args[0] = vd.initializer) && recur.apply(o, args);
- });
- (args[0] = n.expression) || (args[0] = n.block);
- recur.apply(o, args);
- };
- jt[LET] = function() {
- var args = arguments;
- args[0].children.forEach(function(vd) {
- (args[0] = vd.initializer) && recur.apply(o, args);
- });
- };
- return { walkAST: recur, override: override};
- }
- // node -> void
- // Adds an "addr" property to nodes, which is a number unique for each node.
- var labelAST, labelAST_override;
- (function() {
- var walkerobj = walkASTgenerator(), override = walkerobj.override;
- labelAST = walkerobj.walkAST;
- labelAST_override = override;
- override(ARRAY_INIT, function(n) {
- n.addr = newCount();
- n.children.forEach(labelAST);
- });
- override(NEW_WITH_ARGS, function(n) {
- n.addr = newCount();
- labelAST(n.children[0]);
- n.children[1].children.forEach(labelAST);
- });
- override(REGEXP, function(n) { n.addr = newCount(); });
- override(OBJECT_INIT, function(n) {
- n.addr = newCount();
- n.children.forEach(function(prop) {
- labelAST(prop.children[0]);
- labelAST(prop.children[1]);
- });
- });
- override(TRY, function(n) {
- labelAST(n.tryBlock);
- n.catchClauses.forEach(function(c) {
- c.exvar.addr = newCount();
- c.guard && labelAST(c.guard);
- labelAST(c.block);
- });
- n.finallyBlock && labelAST(n.finallyBlock);
- });
- override(SCRIPT, function(n) {
- n.varDecls.forEach(function(vd) {vd.addr = newCount();});
- n.funDecls.forEach(labelAST);
- n.children.forEach(labelAST);
- });
- override(FUNCTION, function _function(n) {
- n.addr = newCount();
- n.defaultProtoAddr = newCount();
- n.fname.addr = newCount();
- n.params.forEach(function(p) { p.addr = newCount(); });
- labelAST(n.body);
- });
- })();
- // Node, number, Array of id nodes -> void
- // WITH node, address of the fresh variable, bound variables
- // After desugarWith is executed, there are no WITHs in the AST.
- var desugarWith;
- // FIXME: the desugaring handles nested WITHs incorrectly.
- (function () {
- var walkerobj = walkASTgenerator(), override = walkerobj.override;
- desugarWith = walkerobj.walkAST;
- function withIdNode(t, addr) {
- var n = idNode(t, "internalVar: with");
- n.addr = addr;
- return n;
- }
- override(SCRIPT, function(n, withAddr, boundvars) {
- n.funDecls.forEach(function(fd) { desugarWith(fd, withAddr, boundvars); });
- var blen = boundvars.length;
- Array.prototype.push.apply(boundvars, n.varDecls);
- n.children.forEach(function(stm) {
- desugarWith(stm, withAddr, boundvars, n.varDecls);
- });
- boundvars.splice(blen, boundvars.length);
- });
- override(FUNCTION, function(n, withAddr, boundvars) {
- var blen = boundvars.length;
- Array.prototype.push.apply(boundvars, n.params);
- desugarWith(n.body, withAddr, boundvars);
- boundvars.splice(blen, boundvars.length);
- });
- // Turn to a block with two statements.
- // The 4th argument is the varDecls of the innermost enclosing SCRIPT, so that
- // the fresh variable of the WITH can be added there.
- override(WITH, function(n, withAddr, boundvars, vardecls) {
- var newaddr = newCount(), t = n.tokenizer;
- var freshvar = withIdNode(t, newaddr), assgn;
- vardecls.push(freshvar);
- assgn = new Node(t, {type:ASSIGN});
- assgn.children = [freshvar, n.object];
- desugarWith(n.body, newaddr, [], vardecls);
- n.type = BLOCK;
- n.varDecls = [];
- n.children = [semiNode(assgn), n.body];
- delete n.object;
- delete n.body;
- });
- // Add the CATCH variable to the bound variables
- override(TRY, function(n, withAddr, boundvars, vardecls) {
- desugarWith(n.tryBlock, withAddr, boundvars, vardecls);
- n.catchClauses.forEach(function(clause) {
- boundvars.push(clause.exvar);
- clause.guard && desugarWith(clause.guard, withAddr, boundvars);
- desugarWith(clause.block, withAddr, boundvars, vardecls);
- boundvars.pop();
- });
- n.finallyBlock && desugarWith(n.finallyBlock,withAddr,boundvars,vardecls);
- });
- // May change n to an IF node
- override(FOR_IN, function(n, withAddr, boundvars, vardecls) {
- var it = n.iterator, it, obj = n.object, b = n.body;
- if (it.type === IDENTIFIER)
- // (Temporary) Copy to avoid sharing introduced by desugaring.
- n.iterator = it = idNode(it.tokenizer, it.value);
- desugarWith(obj, withAddr, boundvars);
- desugarWith(b, withAddr, boundvars, vardecls);
- if (it.type !== IDENTIFIER) {
- desugarWith(it, withAddr, boundvars);
- return;
- }
- if (_makeHook(n, it, withAddr, boundvars)) {
- var t = n.tokenizer, ch = n.children;
- n.type = IF;
- n.children = [];
- n.condition = ch[0];
- var forinn = new Node(t, {type:FOR_IN});
- forinn.iterator = ch[1];
- forinn.object = obj;
- forinn.body = b;
- n.thenPart = forinn;
- forinn = new Node(t, {type:FOR_IN});
- forinn.iterator = ch[2];
- forinn.object = obj;
- // The body b must be cloned o/w markConts will overwrite the conts of the
- // true branch when it processes the false branch.
- forinn.body = b;
- n.elsePart = forinn;
- }
- });
- // Change the 1st arg to a HOOK node.
- // n may be an ID node, but it doesn't appear in varDecls b/c it's an rvalue,
- // it doesn't come from a VAR, so it can be mutated safely.
- // Returns true iff it edits the node.
- function _makeHook(n, idn, withAddr, boundvars) {
- var t = idn.tokenizer, name = idn.name;
- if (!withAddr) return;
- if (boundvars.member(function(bv) { return bv.name === name; })) return;
- var inn = new Node(t, {type : IN});
- inn.children = [new Node(t, {type : STRING, value : name + "-"}),
- withIdNode(t, withAddr)];
- var dotn = new Node(t, {type: DOT});
- dotn.children = [withIdNode(t, withAddr),
- new Node(t, {type:STRING, value : name + "-"})];
- n.type = HOOK;
- n.children = [inn, dotn, idNode(t, name)];
- return true;
- }
- override(IDENTIFIER, function(n, withAddr, boundvars) {
- _makeHook(n, n, withAddr, boundvars);
- });
- // May change n to a HOOK node
- function _assign(n, withAddr, boundvars) {
- var t = n.tokenizer, type = n.type, lval = n.children[0],
- aop = lval.assignOp, rval = n.children[1];
- desugarWith(rval, withAddr, boundvars);
- if (lval.type !== IDENTIFIER) {
- desugarWith(lval, withAddr, boundvars);
- return;
- }
- if (_makeHook(n, lval, withAddr, boundvars)) {
- var branch = n.children[1], assgn = new Node(t, {type:type});
- branch.assignOp = aop;
- assgn.children = [branch, rval];
- n.children[1] = assgn;
- branch = n.children[2], assgn = new Node(t, {type:type});
- branch.assignOp = aop;
- assgn.children = [branch, rval];
- n.children[2] = assgn;
- }
- }
- override(ASSIGN, _assign);
- override(PLUS_ASSIGN, _assign);
- })();
- // Node, Map from strings to id nodes, Array of id nodes -> void
- // Rename variables according to the input substitution map
- var subst;
- (function () {
- var walkerobj = walkASTgenerator(), override = walkerobj.override;
- subst = walkerobj.walkAST;
- // The only substitution happens here. All other cases are binders, so they
- // update boundvars to prevent erroneous substitutions.
- override(IDENTIFIER, function(n, smap, boundvars) {
- var vname = n.value;
- if (boundvars.member(function(bv) { return bv.value === vname; })) return;
- var newvar = smap[vname];
- if (newvar) {
- n.name = n.value = newvar.value;
- n.addr = newvar.addr;
- }
- });
- override(FUNCTION, function(n, smap, boundvars) {
- var blen = boundvars.length;
- boundvars.push(n.fname);
- Array.prototype.push.apply(boundvars, n.params);
- subst(n.body, smap, boundvars);
- boundvars.splice(blen, boundvars.length);
- });
- function _block(vds, fds, stms, smap, boundvars) {
- var blen = boundvars.length;
- Array.prototype.push.apply(boundvars, vds);
- if (fds) {
- fds.forEach(function(fd) { boundvars.push(fd.fname); });
- fds.forEach(function(fd) { subst(fd, smap, boundvars); });
- }
- stms.forEach(function(stm) { subst(stm, smap, boundvars); });
- boundvars.splice(blen, boundvars.length);
- }
- override(SCRIPT, function(n, smap, boundvars) {
- _block(n.varDecls, n.funDecls, n.children, smap, boundvars);
- });
- override(BLOCK, function(n, smap, boundvars) {
- _block(n.varDecls, undefined, n.children, smap, boundvars);
- });
- override(FOR, function(n, smap, boundvars) {
- var blen = boundvars.length;
- n.varDecls && Array.prototype.push.apply(boundvars, n.varDecls);
- n.setup && subst(n.setup, smap, boundvars);
- n.condition && subst(n.condition, smap, boundvars);
- n.update && subst(n.update, smap, boundvars);
- subst(n.body, smap, boundvars);
- boundvars.splice(blen, boundvars.length);
- });
- override(LET_BLOCK, function(n, smap, boundvars) {
- var vds = n.varDecls, stms;
- if (n.expression)
- stms = [semiNode(n.expression)];
- else {
- vds.concat(n.block.varDecls);
- stms = n.block.children;
- }
- _block(vds, undefined, stms, smap, boundvars);
- });
- override(TRY, function(n, smap, boundvars) {
- subst(n.tryBlock, smap, boundvars);
- n.catchClauses.forEach(function(clause) {
- boundvars.push(clause.exvar);
- clause.guard && subst(clause.guard, smap, boundvars);
- subst(clause.block, smap, boundvars);
- boundvars.pop();
- });
- n.finallyBlock && subst(n.finallyBlock, smap, boundvars);
- });
- })();
- // Must happen after desugaring of WITH, o/w when I create fresh vars for LETs,
- // I may subst erroneously in the body of a WITH.
- // After desugarLet is executed, there are no LETs or LET_BLOCKs in the AST.
- var desugarLet;
- (function () {
- var walkerobj = walkASTgenerator(), override = walkerobj.override;
- desugarLet = walkerobj.walkAST;
- // Array of id nodes, Array of id nodes, tokenizer ->
- // { smap : Substitution map, comman : comma node }
- function letInitsToAssigns(vds, scriptVds, t) {
- var smap = {}, newaddr, freshvar, comman;
- for (var i = 0, len = vds.length; i < len; i++) {
- newaddr = newCount();
- // New vars must have different names for tagVarRefs to work.
- freshvar = idNode(t, "internalVar: let" + newaddr);
- freshvar.addr = newaddr;
- smap[vds[i].value] = freshvar;
- scriptVds.push(freshvar);
- }
- comman = initsToAssigns(vds, t);
- subst(comman, smap, []);
- desugarLet(comman, scriptVds);
- return {smap : smap, comman : comman};
- }
- override(LET_BLOCK, function(n, scriptVds) {
- var tmp = letInitsToAssigns(n.varDecls, scriptVds, n.tokenizer),
- smap = tmp.smap,
- comman = tmp.comman;
- delete n.variables;
- var body;
- if (body = n.expression) {
- subst(body, smap, []);
- desugarLet(body, scriptVds);
- n.type = COMMA;
- n.children = comman.children;
- n.children.push(body);
- delete n.expression;
- }
- else if (body = n.block) {
- subst(body, smap, []);
- desugarLet(body, scriptVds, body);
- n.type = BLOCK;
- n.children = body.children;
- n.children.unshift(semiNode(comman));
- delete n.block;
- n.varDecls = [];
- }
- });
- override(BLOCK, function(n, scriptVds) {
- n.children.forEach(function(stm) { desugarLet(stm, scriptVds, n); });
- });
- override(FOR, function(n, scriptVds) {
- n.setup && desugarLet(n.setup, scriptVds, n); // LET can appear in setup
- n.condition && desugarLet(n.condition, scriptVds);
- n.update && desugarLet(n.update, scriptVds);
- desugarLet(n.body, scriptVds);
- });
- // LET definitions can appear in SCRIPTs or BLOCKs
- override(LET, function(n, scriptVds, innerBlock) {
- var tmp = letInitsToAssigns(n.children, scriptVds, n.tokenizer),
- smap = tmp.smap,
- comman = tmp.comman;
- // Call subst on the sub-nodes of innerBlock directly, to avoid binding vars
- // that must be substituted.
- if (innerBlock.type === FOR) {
- subst(innerBlock.setup, smap, []);
- innerBlock.condition && subst(innerBlock.condition, smap, []);
- innerBlock.update && subst(innerBlock.update, smap, []);
- subst(innerBlock.body, smap, []);
- n.type = COMMA;
- n.children = comman.children;
- }
- else {
- innerBlock.children.forEach(function(stm) { subst(stm, smap, []); });
- n.type = SEMICOLON;
- n.expression = comman;
- delete n.children;
- }
- });
- override(SCRIPT, function(n) {
- var vds = n.varDecls;
- n.funDecls.forEach(desugarLet);
- n.children.forEach(function(stm) { desugarLet(stm, vds, n); });
- });
- })();
- const STACK = 0, HEAP = 1;
- // helper function for the IDENTIFIER case of tagVarRefs
- function tagVarRefsId(classifyEvents) {
- return function(n, innerscope, otherscopes) {
- var boundvar, varname = n.name;
- // search var in innermost scope
- for (var i = innerscope.length - 1; i >= 0; i--) {
- boundvar = innerscope[i];
- if (boundvar.name === varname) {
- //print("stack ref: " + varname);
- n.kind = STACK;
- // if boundvar is a heap var and some of its heap refs get mutated,
- // we may need to update bindings in frames during the cfa.
- n.addr = boundvar.addr;
- return;
- }
- }
- // search var in other scopes
- for (var i = otherscopes.length - 1; i >= 0; i--) {
- boundvar = otherscopes[i];
- if (boundvar.name === varname) {
- // print("heap ref: " + varname);
- n.kind = boundvar.kind = HEAP;
- n.addr = boundvar.addr;
- flags[boundvar.addr] = true;
- return;
- }
- }
- // var has no binder in the program
- if (commonJSmode && varname === "exports") {
- n.kind = HEAP;
- n.addr = exports_object_av_addr;
- var p = arguments[3]; // exported property name passed as extra arg
- if (p && p.type === STRING)
- exports_object.lines[p.value.slice(0, -1)] = p.lineno;
- return;
- }
- //print("global: " + varname + " :: " + n.lineno);
- n.type = DOT;
- var nthis = idNode({lineno: n.lineno}, "internalVar: global object");
- nthis.kind = HEAP;
- if (classifyEvents && varname === "addEventListener")
- nthis.addr = chrome_obj_av_addr;
- else
- nthis.addr = global_object_av_addr;
- n.children = [nthis, new Node({}, {type:STRING, value:n.name + "-"})];
- };
- }
- // node, array of id nodes, array of id nodes -> void
- // Classify variable references as either stack or heap references.
- var tagVarRefs, tagVarRefs_override;
- (function() {
- var walkerobj = walkASTgenerator(), override = walkerobj.override;
- tagVarRefs = walkerobj.walkAST;
- tagVarRefs_override = override;
- override(DOT, function(n, innerscope, otherscopes) {
- var ch = n.children;
- var n0 = ch[0];
- // don't classify property names
- if (commonJSmode && n0.type === IDENTIFIER && n0.name === "exports")
- tagVarRefs(n0, innerscope, otherscopes, ch[1]);
- else
- tagVarRefs(n0, innerscope, otherscopes);
- });
- override(INDEX, function(n, innerscope, otherscopes) {
- var ch = n.children, n0 = ch[0], shadowed = false;
- // use new non-terminal only if "arguments" refers to the arguments array
- if (n0.type === IDENTIFIER && n0.name === "arguments") {
- for (var i = innerscope.length - 1; i >= 0; i--)
- if (innerscope[i].name === "arguments") {
- shadowed = true;
- break;
- }
- if (!shadowed) {
- n.type = ARGUMENTS;
- n.arguments = innerscope; //hack: innerscope is params (maybe extended)
- ch[0] = ch[1];
- ch.splice(1, 1);
- // undo the changes made for INDEX nodes only in fixExp
- if (ch[0].type === STRING && propIsNumeric(ch[0].value)) {
- ch[0].type = NUMBER;
- ch[0].value = ch[0].value.slice(0, -1) - 0;
- }
- }
- }
- ch.forEach(function(kid) { tagVarRefs(kid, innerscope, otherscopes); });
- });
- override(IDENTIFIER, tagVarRefsId(false));
- override(FUNCTION, function(n, innerscope, otherscopes) {
- var fn = n.fname, len, params = n.params;
- len = otherscopes.length;
- // extend otherscopes
- Array.prototype.push.apply(otherscopes, innerscope);
- // fun name is visible in the body & not a heap ref, add it to scope
- params.push(fn);
- tagVarRefs(n.body, params, otherscopes);
- params.pop();
- if (fn.kind !== HEAP) fn.kind = STACK;
- params.forEach(function(p) {if (p.kind !== HEAP) p.kind=STACK;});
- // trim otherscopes
- otherscopes.splice(len, innerscope.length);
- });
- override(SCRIPT, function(n, innerscope, otherscopes) {
- var i, j, len, vdecl, vdecls = n.varDecls, fdecl, fdecls = n.funDecls;
- // extend inner scope
- j = innerscope.length;
- Array.prototype.push.apply(innerscope, vdecls);
- fdecls.forEach(function(fd) { innerscope.push(fd.fname); });
- // tag refs in fun decls
- fdecls.forEach(function(fd) { tagVarRefs(fd, innerscope, otherscopes); });
- // tag the var refs in the body
- var as = arguments;
- n.children.forEach(function(stm) {tagVarRefs(stm,innerscope,otherscopes);});
- // tag formals
- vdecls.forEach(function(vd) {
- // for toplevel vars, assigning flags causes the Aval`s to be recorded
- // in the heap. After the analysis, we use that for ctags.
- if (as[3] === "toplevel") flags[vd.addr] = true;
- if (vd.kind !== HEAP) vd.kind = STACK;
- });
- fdecls.forEach(function(fd) { if (fd.kind !== HEAP) fd.kind = STACK; });
- //trim inner scope
- innerscope.splice(j, vdecls.length + fdecls.length);
- });
- override(TRY, function(n, innerscope, otherscopes) {
- tagVarRefs(n.tryBlock, innerscope, otherscopes);
- n.catchClauses.forEach(function(clause) {
- var xv = clause.exvar;
- innerscope.push(xv);
- clause.guard &&
- tagVarRefs(clause.guard, innerscope, otherscopes);
- tagVarRefs(clause.block, innerscope, otherscopes);
- innerscope.pop();
- if (xv.kind !== HEAP) xv.kind = STACK;
- });
- n.finallyBlock && tagVarRefs(n.finallyBlock, innerscope, otherscopes);
- });
- })();
- // node, node, node -> void
- // For every node N in the AST, add refs from N to the node that is normally
- // exec'd after N and to the node that is exec'd if N throws an exception.
- var markConts;
- (function() {
- var walkerobj = walkASTgenerator(), override = walkerobj.override;
- markConts = walkerobj.walkAST;
- function _fun(n) { markConts(n.body, undefined, undefined); }
- override(FUNCTION, _fun);
- function _block(n, kreg, kexc) {
- var ch = n.children, i, len;
- n.kexc = kexc;
- len = ch.length;
- if (len === 0)
- n.kreg = kreg;
- else {
- n.kreg = ch[0];
- len--;
- for (i=0; i < len; i++) markConts(ch[i], ch[i+1], kexc);
- markConts(ch[len], kreg, kexc);
- }
- }
- override(BLOCK, _block);
- override(SCRIPT, function(n, kreg, kexc) {
- n.funDecls.forEach(_fun);
- _block(n, kreg, kexc);
- });
- override(SEMICOLON, function(n, kreg, kexc) {
- n.kreg = kreg;
- n.kexc = kexc;
- markConts(n.expression);
- });
- // normally, return & throw don't use their kreg. But this analysis allows
- // more permissive control flow, to be faster.
- override(THROW, function(n, kreg, kexc) {
- n.kreg = kreg;
- n.kexc = kexc;
- markConts(n.exception);
- });
- override(RETURN, function(n, kreg, kexc) {
- n.kreg = kreg;
- n.kexc = kexc;
- markConts(n.value);
- });
- override(IF, function(n, kreg, kexc) {
- var thenp = n.thenPart, elsep = n.elsePart, condStm;
- condStm = semiNode(n.condition);
- n.kreg = condStm; // first run the test
- n.kexc = kexc;
- markConts(condStm, thenp, kexc); // ignore result & run the true branch
- markConts(thenp, elsep || kreg, kexc); // then run the false branch
- elsep && markConts(elsep, kreg, kexc);
- });
- function markContsCase(n, kreg, kexc) {
- var clabel = n.caseLabel, clabelStm, stms = n.statements;
- n.kexc = kexc;
- if (clabel) {
- clabelStm = semiNode(clabel);
- n.kreg = clabelStm;
- markConts(clabelStm, stms, kexc);
- }
- else n.kreg = stms;
- markConts(stms, kreg, kexc);
- }
- override(SWITCH, function(n, kreg, kexc) {
- var cases = n.cases, discStm, i, len;
- discStm = semiNode(n.discriminant);
- n.kreg = discStm; // first run the discriminant, then all branches in order
- n.kexc = kexc;
- markConts(discStm, cases[0], kexc);
- for (i = 0, len = cases.length - 1; i < len; i++) //no empty switch, len >=0
- markContsCase(cases[i], cases[i+1], kexc);
- markContsCase(cases[len], kreg, kexc);
- });
- override(FOR, function(n, kreg, kexc) {
- var body = n.body;
- n.kexc = kexc;
- // Do setup, condition, body & update once.
- var setup = n.setup, setupStm, condition = n.condition, condStm;
- var update = n.update, updStm;
- n.kexc = kexc;
- if (!setup && !condition)
- n.kreg = body;
- else if (setup && !condition) {
- setupStm = semiNode(setup);
- n.kreg = setupStm;
- markConts(setupStm, body, kexc);
- }
- else {// condition exists
- condStm = semiNode(condition);
- markConts(condStm, body, kexc);
- if (setup) {
- setupStm = semiNode(setup);
- n.kreg = setupStm;
- markConts(setupStm, condStm, kexc);
- }
- else n.kreg = condStm;
- }
- if (update) {
- updStm = semiNode(update);
- markConts(body, updStm, kexc);
- markConts(updStm, kreg, kexc);
- }
- else markConts(body, kreg, kexc);
- });
- override(FOR_IN, function(n, kreg, kexc) {
- var body = n.body;
- n.kexc = kexc;
- n.kreg = body;
- markConts(body, kreg, kexc);
- });
- override(WHILE, function(n, kreg, kexc) {
- var condStm = semiNode(n.condition), body = n.body;
- n.kreg = condStm;
- n.kexc = kexc;
- markConts(condStm, body, kexc);
- markConts(body, kreg, kexc);
- });
- override(DO, function(n, kreg, kexc) {
- var condStm = semiNode(n.condition), body = n.body;
- n.kreg = body;
- n.kexc = kexc;
- markConts(body, condStm, kexc);
- markConts(condStm, kreg, kexc);
- });
- function markContsCatch(n, knocatch, kreg, kexc) {
- var guard = n.guard, guardStm, block = n.block;
- if (guard) {// Mozilla catch
- // The guard is of the form (var if expr).
- // If expr is truthy, the catch body is run w/ var bound to the exception.
- // If expr is falsy, we go to the next block (another catch or finally).
- // If the guard or the body throw, the next catches (if any) can't handle
- // the exception, we go to the finally block (if any) directly.
- markConts(guard);
- guardStm = semiNode(guard);
- n.kreg = guardStm;
- guardStm.kcatch = block; // this catch handles the exception
- guardStm.knocatch = knocatch; // this catch doesn't handle the exception
- guardStm.kexc = kexc; // the guard throws a new exception
- }
- markConts(block, kreg, kexc);
- }
- override(TRY, function(n, kreg, kexc) {
- var fin = n.finallyBlock, clause, clauses=n.catchClauses, knocatch, i, len;
- // process back-to-front to avoid if-madness
- if (fin) {
- markConts(fin, kreg, kexc);
- knocatch = kexc = kreg = fin; // TRY & CATCHes go to fin no matter what
- }
- for (len = clauses.length, i = len-1; i>=0; i--) {
- clause = clauses[i];
- markContsCatch(clause, knocatch, kreg, kexc);
- knocatch = clause;
- }
- markConts(n.tryBlock, kreg, knocatch || kexc);
- n.kreg = n.tryBlock;
- });
- })();
- ////////////////////////////////////////////////////////////////////////////////
- //////////////////////////// CFA2 code /////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////
- // abstract objects and abstract values are different!!!
- var heap;
- // modified[addr] is a timestamp that shows the last time heap[addr] was updated
- var modified;
- var timestamp;
- // If i is the addr of a var, flags[i] is true if the var is a heap var.
- var flags;
- var exports_object;
- var exports_object_av_addr;
- var commonJSmode;
- var timedout = false, timeout = 120; // stop after 2 minutes if you're not done
- // A summary contains a function node (fn), an array of abstract values (args),
- // a timestamp (ts) and abstract values (res) and (err). It means: when we call
- // fn w/ args and the heap's timestamp is ts, the result is res and if fn can
- // throw an exception, the value thrown is err.
- // summaries: a map from addresses of fun nodes to triples {ts, insouts, type},
- // where ts is a timestamp, insouts is an array of args-result pairs,
- // and type is the join of all args-result pairs.
- var summaries;
- // pending contains info that exists in the runtime stack. For each pending call
- // to evalFun, pending contains an object {args, ts} where args is the arguments
- // of the call and ts is the timestamp at the time of the call.
- var pending;
- // core JS functions also use pending but differently.
- // when initGlobals is called, count has its final value (core objs are in heap)
- // FIXME, I'm violating the invariant in "function cfa2". Change it?
- function initGlobals() {
- big_ts = 1000; // used only when debugging
- timestamp = 0;
- heap = new Array(count); // reserve heap space, don't grow it gradually
- modified = buildArray(count, timestamp);
- summaries = {}; // We use {} instead of an Array b/c it's sparse.
- pending = {};
- flags = {};
- exports_object = {lines : {}};
- }
- // string -> void
- // works only in NodeJS
- function dumpHeap(filename) {
- var fd = fs.openSync(filename, "w", mode=0777);
- for (var i = 0, l = heap.length; i < l; i++)
- printf(fd, "[" + i + "]\n" + (heap[i] ? heap[i].toString(2) : "") + "\n");
- fs.closeSync(fd);
- }
- // non-empty array of strings -> void
- function normalizeUnionType(types) {
- // any is a supertype of all types
- if (types.memq("any")) {
- types[0] = "any";
- types.length = 1;
- }
- else
- types.rmDups(function(e1, e2) {return e1 === e2;});
- }
- // Constructor for abstract properties
- // Takes an object w/ the property's attributes
- // Don't call from outside the abstract-values module, use addProp instead.
- function Aprop(attribs){
- this.aval = attribs.aval;
- // writable, enumerable and configurable default to true
- this.write = ("write" in attribs) ? attribs.write : true;
- this.enum = ("enum" in attribs) ? attribs.enum : true;
- this.config = ("config" in attribs) ? attribs.config : true;
- }
- // optional number -> string
- Aprop.prototype.toString = function(indent) {
- return this.aval.toString(indent);
- };
- // An abstract object o1 is represented as a JS object o2.
- // A property…
Large files files are truncated, but you can click here to view the full file