From b0d8c90fe5b86494a837c3bd609311115b4f8ecc Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Sun, 5 Jan 2014 16:26:56 +0000 Subject: [PATCH] Initial commit: factoring out WALA-independent features of WALADelta into new package. --- LICENSE.txt | 210 ++++++++++++++++++ README.md | 62 ++++++ combinators.js | 96 +++++++++ config.js | 13 ++ delta.js | 562 +++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 10 + timeout.sh | 10 + 7 files changed, 963 insertions(+) create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 combinators.js create mode 100644 config.js create mode 100644 delta.js create mode 100644 package.json create mode 100755 timeout.sh diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..e56f45c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,210 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation +distributed under this Agreement, and + +b) in the case of each subsequent Contributor: + +i) changes to the Program, and + +ii) additions to the Program; + +where such changes and/or additions to the Program originate from and are +distributed by that particular Contributor. A Contribution 'originates' from a +Contributor if it was added to the Program by such Contributor itself or anyone +acting on such Contributor's behalf. Contributions do not include additions to +the Program which: (i) are separate modules of software distributed in +conjunction with the Program under their own license agreement, and (ii) are not +derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + +a) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free copyright license to +reproduce, prepare derivative works of, publicly display, publicly perform, +distribute and sublicense the Contribution of such Contributor, if any, and such +derivative works, in source code and object code form. + +b) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed +Patents to make, use, sell, offer to sell, import and otherwise transfer the +Contribution of such Contributor, if any, in source code and object code +form. This patent license shall apply to the combination of the Contribution and +the Program if, at the time the Contribution is added by the Contributor, such +addition of the Contribution causes such combination to be covered by the +Licensed Patents. The patent license shall not apply to any other combinations +which include the Contribution. No hardware per se is licensed hereunder. + +c) Recipient understands that although each Contributor grants the licenses to +its Contributions set forth herein, no assurances are provided by any +Contributor that the Program does not infringe the patent or other intellectual +property rights of any other entity. Each Contributor disclaims any liability to +Recipient for claims brought by any other entity based on infringement of +intellectual property rights or otherwise. As a condition to exercising the +rights and licenses granted hereunder, each Recipient hereby assumes sole +responsibility to secure any other intellectual property rights needed, if +any. For example, if a third party patent license is required to allow Recipient +to distribute the Program, it is Recipient's responsibility to acquire that +license before distributing the Program. + +d) Each Contributor represents that to its knowledge it has sufficient copyright +rights in its Contribution, if any, to grant the copyright license set forth in +this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under its +own license agreement, provided that: + +a) it complies with the terms and conditions of this Agreement; and + +b) its license agreement: + +i) effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of title and +non-infringement, and implied warranties or conditions of merchantability and +fitness for a particular purpose; + +ii) effectively excludes on behalf of all Contributors all liability for +damages, including direct, indirect, special, incidental and consequential +damages, such as lost profits; + +iii) states that any provisions which differ from this Agreement are offered by +that Contributor alone and not by any other party; and + +iv) states that source code for the Program is available from such Contributor, +and informs licensees how to obtain it in a reasonable manner on or through a +medium customarily used for software exchange. + +When the Program is made available in source code form: + +a) it must be made available under this Agreement; and + +b) a copy of this Agreement must be included with each copy of the Program. + +Contributors may not remove or alter any copyright notices contained within the +Program. + +Each Contributor must identify itself as the originator of its Contribution, if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, if +a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, damages +and costs (collectively "Losses") arising from claims, lawsuits and other legal +actions brought by a third party against the Indemnified Contributor to the +extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor to +control, and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may participate in +any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If that +Commercial Contributor then makes performance claims, or offers warranties +related to Product X, those performance claims and warranties are such +Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a court +requires any other Contributor to pay any damages as a result, the Commercial +Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using and +distributing the Program and assumes all risks associated with its exercise of +rights under this Agreement , including but not limited to the risks and costs +of program errors, compliance with applicable laws, damage to or loss of data, +programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS +GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable +law, it shall not affect the validity or enforceability of the remainder of the +terms of this Agreement, and without further action by the parties hereto, such +provision shall be reformed to the minimum extent necessary to make such +provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted under +Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue and +survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to +time. No one other than the Agreement Steward has the right to modify this +Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse +Foundation may assign the responsibility to serve as the Agreement Steward to a +suitable separate entity. Each new version of the Agreement will be given a +distinguishing version number. The Program (including Contributions) may always +be distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to distribute the Program (including its Contributions) +under the new version. Except as expressly stated in Sections 2(a) and 2(b) +above, Recipient receives no rights or licenses to the intellectual property of +any Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted under +this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2311b05 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +JS Delta +========== + +JS Delta is a [delta debugger](http://www.st.cs.uni-saarland.de/dd/) for debugging JavaScript-processing tools. Given a JavaScript program `test.js` that is causing a JS-processing tool to crash or otherwise misbehave, it shrinks `test.js` by deleting statements, functions and sub-expressions, looking for a small sub-program of `test.js` which still causes the problem. In general, JS Delta can search for a small input satifying some predicate `P` implemented in JavaScript, allowing for arbitrarily complex tests. + +For example, `P` could invoke a static analysis like [WALA](http://wala.sf.net) on its input program and check whether it times out. If `test.js` is very big, it may be hard to see what is causing the timeout. JS Delta will find a (sometimes very much) smaller program on which the analysis still times out, making it easier to diagnose the root cause of the scalability problem. Special support for debugging WALA-based analyses with JS Delta is provided by the [JS Delta](http://github.com/wala/WALADelta) utility. + +JS Delta can also be used to help debug programs taking JSON as input. For this use case, make sure the input file ends with extension `.json`. + +Prerequisites +-------------- +- Node.js +- UglifyJS. + +Run `npm install` in the root directory of your JS Delta checkout to install the dependencies. + +__NOTE__: JS Delta does _not_ work with [UglifyJS2](https://github.com/mishoo/UglifyJS2). To get an appropriate version of UglifyJS, just run `npm install` in the JS Delta directory, or run `npm install uglify-js@1.3.4` in your home directory. + +We've tested JS Delta on Linux and Mac OS X. + +Usage +----- + +JS Delta takes as its input a JavaScript file `f.js` and a predicate `P`. It first copies `f.js` to `/delta_js_0.js`, where `` is a fresh directory created under the `tmp_dir` specified in `config.js` (`/tmp` by default). + +It then evaluates `P` on `/delta_js_0.js`. If `P` does not hold for this file, it aborts with an error. Otherwise, it reduces the input file by removing a number of statements or expressions, writing the result to `/delta_js_1.js`, and evaluating `P` on this new file. While `P` holds, it keeps reducing the input file in this way until it has found a reduced version `/delta_js_n.js` such that `P` holds on it, but not on any further reduced version. At this point, JS Delta stops and copies the smallest reduced version to `/delta_js_smallest.js`. + +There are several ways for providing a predicate `P`. + +At its most general, `P` is an arbitrary Node.js module that exports a function `test`. This function is invoked with the name of the file to test and a continuation `k`. If the predicate holds, `P` should invoke `k` with argument `true`, otherwise with argument `false`. + +A slightly more convenient (but less general) way of writing a predicate is to implement a Node.js module exporting a string `cmd` and a function `checkResult`. In this case, JS Delta provides a default implementation of the function `test` that does the following: + + 1. It invokes `cmd` as a shell command with the file `fn` to test as its only argument. + 2. It captures the standard output and standard error of the command and writes them into files `fn.stdout` and `fn.stderr`. + 3. It invokes function `checkResult` with four arguments: the `error` code returned from executing `cmd` by the `exec` method [in the Node.js standard library](http://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback); a string containing the complete standard output of the command; a string containing the complete standard error of the command; and the time (in milliseconds) it took the command to finish. + 4. The (boolean) return value of `checkResult` is passed to the continuation. + +Finally, you can specify the predicate implicitly through command line arguments: invoking JS Delta with arguments + +> --cmd CMD --timeout N file-to-reduce.js + +has the same effect as defining a predicate module exporting `CMD` as its command, and with a `checkResult` function that returns `true` if the command took longer than `N` milliseconds to complete. Note that if your tool requires other command-line arguments besides the input JavaScript file, you should write a wrapper shell script that takes one argument (the input JS file) and invokes your tool appropriately, and then use that shell script as the `--cmd` argument. + +As an example, if you'd like to see why `file-to-reduce.js` is taking longer than 30 seconds to analyze with your tool `myjstool`, you would run: + +> node delta.js --cmd myjstool --timeout 30000 file-to-reduce.js + +Invoking JS Delta with arguments + +> --cmd CMD --errmsg ERR file-to-reduce.js + +again takes `CMD` to be the command to execute; the predicate is deemed to hold if the command outputs an error message containing string `ERR`. + +As a special case, instead of using the `--timeout` flag you can run your analysis using the `timeout.sh` script bundled with JS Delta, which will output the error message `TIMEOUT` if the given timeout is exceeded, which can be detected by specifying `--errmsg TIMEOUT`. This is sometimes more reliable than using the `--timeout` flag directly. + +Finally, you can just specify a command (without error message or timeout), in which case the predicate is deemed to hold if the command exits with an error. + +License +------- + +JS Delta is distributed under the Eclipse Public License. See the LICENSE.txt file in the root directory or http://www.eclipse.org/legal/epl-v10.html. diff --git a/combinators.js b/combinators.js new file mode 100644 index 0000000..1d51002 --- /dev/null +++ b/combinators.js @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2012 IBM Corporation. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +Number.prototype.upto = function(hi) { + var res = []; + for(var i=this.valueOf();i<=hi;++i) + res.push(i); + return res; +}; + +function If(cond, f) { + if(typeof f !== 'function') + throw new TypeError("If needs a function argument"); + return function(k) { + var t = typeof cond === 'function' ? cond() : cond; + if(t) + f(k); + else + k(); + }; +} + +function Foreach(array, f) { + if(typeof f !== 'function') + throw new TypeError("Foreach needs a function argument"); + function loop(i, k) { + if(i >= array.length) + k(); + else + f(array[i])(function() { loop(i+1, k); }); + } + return function(k) { + loop(0, k); + }; +} + +Function.prototype.AndThen = function(f) { + if(typeof f !== 'function') + throw new TypeError("AndThen needs a function argument"); + var self = this; + return { + OrElse: function(g) { + if(typeof g !== 'function') + throw new TypeError("OrElse needs a function argument"); + return function(k) { + self(function(succ) { + (succ ? f : g)(k); + }); + } + } + }; +} + +Function.prototype.Then = function(f) { + var self = this; + return function(k) { + self(function() { f(k); }); + }; +} + +Function.prototype.OrElse = function(f) { + if(typeof f !== 'function') + throw new TypeError("OrElse needs a function argument"); + var self = this; + return function(k) { + self(function(succ) { + if(succ) + k(); + else + f(k); + }); + }; +} + +// for testing +function Done() { + console.log("done"); +} + +function Print(msg) { + return function(k) { + console.log(msg); + k(); + }; +} + +exports.If = If; +exports.Foreach = Foreach; \ No newline at end of file diff --git a/config.js b/config.js new file mode 100644 index 0000000..abfd9ee --- /dev/null +++ b/config.js @@ -0,0 +1,13 @@ +/******************************************************************************* + * Copyright (c) 2012 IBM Corporation. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +// directory in which to create temporary files +exports.tmp_dir = "/tmp"; \ No newline at end of file diff --git a/delta.js b/delta.js new file mode 100644 index 0000000..a5bba07 --- /dev/null +++ b/delta.js @@ -0,0 +1,562 @@ +/******************************************************************************* + * Copyright (c) 2012 IBM Corporation. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Max Schaefer - refactoring + *******************************************************************************/ + +var fs = require("fs"), + jsp = require("uglify-js").parser, + pro = require("uglify-js").uglify, + util = require("util"), + config = require(__dirname + "/config.js"), + combinators = require(__dirname + "/combinators.js"), + exec = require("child_process").exec, + If = combinators.If, + Foreach = combinators.Foreach; + +function usage() { + console.error("Usage: " + process.argv[0] + " " + process.argv[1] + + " [-q|--quick] [--cmd COMMAND] [--timeout TIMEOUT]" + + " [--errmsg ERRMSG] FILE [PREDICATE] OPTIONS..."); + process.exit(-1); +} + +function log_debug(msg) { + // console.log(msg); +} + +// check whether the given file exists +// TODO: this is a bit rough; surely someone has written a module to do this? +function exists(f) { + try { + fs.statSync(f); + return true; + } catch(e) { + return false; + } +} + +var /** only knock out entire statements */ + quick = false, + + /** command to invoke to determine success/failure */ + cmd = null, + + /** error message indicating failure of command */ + errmsg = null, + + /** time budget to allow for the command to run */ + timeout = null, + + /** file to minimise */ + file = null, + + /** predicate to use for minimisation */ + predicate = {}, + + /** arguments to pass to the predicate */ + predicate_args = []; + +// command line option parsing; manual for now +// TODO: find good npm package to use +for(var i=2;i= array.length) { + k(); + } else { + if(twolevel) { + (function grandchildren_loop(j, k) { + if(!k) + throw new TypeError("no continuation"); + if(!array[i] || j >= array[i].length) { + k(); + } else { + minimise(array[i][j], array[i], j, + function() { grandchildren_loop(j+1, k); }); + } + })(0, (function() { children_loop(i+1, k); })); + } else { + minimise(array[i], array, i, + function() { children_loop(i+1, k); }); + } + } + } + + log_debug("minimising array " + util.inspect(array, false, 1)); + if(!nonempty && array.length === 1) { + // special case: if there is only one element, try removing it + var elt = array[0]; + array.length = 0; + test(function(succ) { + if(!succ) + // didn't work, need to put it back + array[0] = elt; + children_loop(0, k); + }); + } else { + if(!k) + throw new TypeError("no continuation"); + // try removing as many chunks of size sz from array as possible + // once we're done, switch to size sz/2; if size drops to zero, + // recursively invoke minimise on the individual elements + // of the array + (function outer_loop(sz, k) { + if(!k) + throw new TypeError("no continuation"); + if(sz <= 0) { + k(); + } else { + log_debug(" chunk size " + sz); + var nchunks = Math.floor(array.length/sz); + (function inner_loop(i, k) { + if(!k) + throw new TypeError("no continuation"); + if(i < 0) { + k(); + } else { + // try removing chunk i + log_debug(" chunk #" + i); + var lo = i*sz, + hi = i===nchunks-1 ? array.length : (i+1)*sz; + var chunk = array.slice(lo, hi); + + // avoid creating empty array if nonempty is set + if(nonempty && lo === 0 && hi === array.length) { + inner_loop(i-1, k); + } else { + array.splice(lo, hi-lo); + test(function(succ) { + if(!succ) { + // didn't work, need to put it back + Array.prototype.splice.apply(array, + [lo,0].concat(chunk)); + } + inner_loop(i-1, k); + }); + } + } + })(nchunks-1, + function() { outer_loop(Math.floor(sz/2), k); }); + } + })(Math.floor(array.length/2), + function() { children_loop(0, k); }); + } +} + +// the main minimisation function +function minimise(nd, parent, idx, k) { + log_debug("minimising " + util.inspect(nd)); + if(!k) + throw new TypeError("no continuation"); + var ndtp = typeOf(nd); + if(ndtp === "toplevel") { + MinimiseArray(nd, 1)(k); + } else if(ndtp === "block") { + // knock out as many statements in the block as possible + // if we end up with a single statement, replace the block with + // that statement + If(nd[1], MinimiseArray(nd, 1). + Then(If(function() { return !quick && nd[1].length === 1; }, + function(k) { Replace(parent, idx).With(nd[1][0])(k); })))(k); + } else if(ndtp === "defun" || ndtp === "function") { + // try removing the function name, shrinking the parameter list, and shrinking the body; if the body ends up being a block, inline it + If(!quick && ndtp === 'function' && !nd[1], Replace(nd, 1).With(null)). + Then(If(!quick, MinimiseArray(nd, 2))). + Then(MinimiseArray(nd, 3)). + Then(If(function() { return !quick && nd[3].length === 1 && nd[3][0][0] === 'block'; }, + function(k) { Replace(nd, 3).With(nd[3][0][1])(k); }))(k); + } else if (ndtp === "object") { + // minimise object literals even in quick mode + MinimiseArray(nd, 1, false, true)(k); + } else if(ndtp === "var") { + // minimise variable declarations even in quick mode, to avoid + // interpreting variable names in the declarations as object types + // upon recursing + MinimiseArray(nd, 1, true, true)(k); + } else { + // match other node types only if we're not doing quick minimisation + // if quick is set, !quick && ndtp will be undefined, so the + // default branch is taken + switch(!quick && ndtp) { + case "string": + case "num": + case "regexp": + // disable replacing constants. unclear it helps + // minimization much, and can mess up semantics when + // minimizing JSON + // Replace(parent, idx).With(["num", 0]). + // OrElse(Replace(parent, idx).With(["string", ""]))(k); + k(); + break; + case "unary-postfix": + case "unary-prefix": + // try replacing with operand + Replace(parent, idx).With(nd[2]). + AndThen(Minimise(parent, idx)). + OrElse(Minimise(nd, 2))(k); + break; + case "assign": + case "binary": + Replace(parent, idx).With(nd[2]). + AndThen(Minimise(parent, idx)). + OrElse(Replace(parent, idx).With(nd[3]). + AndThen(Minimise(parent, idx)). + OrElse(Minimise(nd, 2).Then(Minimise(nd, 3))))(k); + break; + case "return": + Replace(nd, 1).With(null).OrElse(Minimise(nd, 1))(k); + break; + case "call": + case "new": + Minimise(nd, 1).Then(MinimiseArray(nd, 2))(k); + break; + case "array": + MinimiseArray(nd, 1)(k); + break; + case "if": + case "conditional": + Replace(parent, idx).With(nd[2]). + AndThen(Minimise(parent, idx)). + OrElse(If(nd[3], Replace(parent, idx).With(nd[3])). + AndThen(Minimise(parent, idx)). + OrElse(Minimise(nd, 1). + Then(Minimise(nd, 2). + Then(Minimise(nd, 3)))))(k); + break; + case "var": + MinimiseArray(nd, 1, true, true)(k); + break; + case "switch": + // minimise condition, then knock out cases + // TODO: simplify cases + Minimise(nd, 1).Then(MinimiseArray(nd, 2))(k); + break; + case "for": + // try replacing with body, otherwise try removing init/cond/update + // and then simplify them + // TODO: do we want to be this elaborate? + // Replace(parent, idx).With(nd[4]). + // AndThen(Minimise(parent, idx)). + // OrElse(Replace(nd, 1).With(null). + // Then(Replace(nd, 2).With(null)). + // Then(Replace(nd, 3).With(null)). + // Then(Minimise(nd, 1)). + // Then(Minimise(nd, 2)). + // Then(Minimise(nd, 3)). + // Then(Minimise(nd, 4)))(k); + // MS edited to not replace loop with body and not simplify init, + // to help preserve some var declarations + Replace(nd, 2).With(null). + Then(Replace(nd, 3).With(null)). + Then(Minimise(nd, 1)). + Then(Minimise(nd, 2)). + Then(Minimise(nd, 3)). + Then(Minimise(nd, 4))(k); + break; + default: + if(nd && Array.isArray(nd)) + Foreach((0).upto(nd.length-1), + function(i) { return Minimise(nd, i); })(k); + else + k(); + } + } +} + +// UglifyJS's code generator does not produce valid JSON; +// this function converts an UglifyJS into a plain object (where possible), +// which can then be printed using JSON.stringify +function toJSON(obj) { + var ndtp = typeOf(obj); + switch(ndtp) { + case 'toplevel': + // not quite right, but better than nothing + if(obj[1].length === 0) + return null; + return toJSON(obj[1][0]); + case 'stat': + return toJSON(obj[1]); + case 'object': + var res = {}; + obj[1].forEach(function(prop) { + res[prop[0]] = toJSON(prop[1]); + }); + return res; + case 'array': + return obj[1].map(toJSON); + case 'num': + case 'string': + return obj[1]; + case 'name': + switch(obj[1]) { + case 'true': + return true; + case 'false': + return false; + case 'null': + return null; + default: + throw new Error("unexpected AST node type " + ndtp); + } + case 'unary-prefix': + // special case: negative integer literals are represented as unary prefix by Uglify + if(obj[1] === '-' && typeOf(obj[2]) === 'num') + return -obj[2][1]; + default: + debugger; + throw new Error("unexpected AST node type " + ndtp); + } +} + +function pp(ast) { + if(ext === 'json') { + try { + return JSON.stringify(toJSON(ast)); + } catch(e) { + console.error("Unable to convert to JSON: " + pro.gen_code(ast, { beautify: true })); + throw e; + } + } else { + return pro.gen_code(ast, { beautify: true }); + } +} + +// write the current test case out to disk +function writeTempFile() { + var fn = getTempFileName(); + fs.writeFileSync(fn, pp(ast)); + return fn; +} + +// test the current test case +function test(k) { + if(!k) + throw new TypeError("no continuation"); + var fn = writeTempFile(); + predicate.test(fn, function(succ) { + // if the test succeeded, save it to file 'smallest' + if(succ) + fs.writeFileSync(smallest, pp(ast)); + k(succ); + }); +} + +// save a copy of the original input +var orig = getTempFileName(), + input = fs.readFileSync(file, 'utf-8'); +fs.writeFileSync(orig, input); +fs.writeFileSync(smallest, input); + +// get started +predicate.test(orig, + function(succ) { + if(succ) { + minimise(ast, null, -1, + function() { + console.log("Minimisation finished; " + + "final version is in " + smallest); + process.exit(0); + }); + } else { + console.error("Original file doesn't satisfy predicate."); + process.exit(-1); + } + }); + +// combinators; eventually we want to write the above using these +function Minimise(nd, idx) { + return function(k) { + minimise(nd[idx], nd, idx, k); + }; +} + +function MinimiseArray(nd, idx, nonempty, twolevel) { + return function(k) { + minimise_array(nd[idx], k, nonempty, twolevel); + }; +} + +function Replace(nd, idx) { + var oldval = nd[idx]; + return { + With: function(newval) { + return function(k) { + if(oldval === newval) { + k(true); + } else { + nd[idx] = newval; + test(function(succ) { + if(succ) { + k(true); + } else { + nd[idx] = oldval; + k(false); + } + }); + } + }; + } + }; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..00cb4c9 --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "name": "JSDelta", + "version": "0.1.0", + "author": "Max Schaefer ", + "description": "delta debugger for JavaScript", + "dependencies" : { + "uglify-js": "1.3.4" + }, + "license": "Eclipse" +} diff --git a/timeout.sh b/timeout.sh new file mode 100755 index 0000000..dd87c1f --- /dev/null +++ b/timeout.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# Helper script for running a command under a timeout. +# Prints "TIMEOUT" to stderr if the command timed out. + +timeout -k 1s -s KILL $* +if [ $? -eq 124 ] +then + echo "TIMEOUT" >&2 +fi \ No newline at end of file