/******************************************************************************* * Copyright (c) 2011 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 *******************************************************************************/ package com.ibm.wala.cast.js.test; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.Map; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import com.ibm.wala.cast.js.ipa.callgraph.correlations.CorrelationFinder; import com.ibm.wala.cast.js.ipa.callgraph.correlations.CorrelationSummary; import com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction.ClosureExtractor; import com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction.CorrelatedPairExtractionPolicy; import com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction.ExtractionPolicy; import com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction.ExtractionPolicyFactory; import com.ibm.wala.cast.tree.CAstEntity; import com.ibm.wala.cast.tree.impl.CAstImpl; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.classLoader.SourceFileModule; import com.ibm.wala.classLoader.SourceModule; import com.ibm.wala.classLoader.SourceURLModule; import com.ibm.wala.ipa.cha.ClassHierarchyException; import com.ibm.wala.util.io.FileUtil; public abstract class TestCorrelatedPairExtraction { // set to "true" to use JUnit's assertEquals to check whether a test case passed; // if it is set to "false", expected and actual output are instead written to files expected.dump and actual.dump // this is useful if the outputs are too big/too different for Eclipse's diff view to handle private static final boolean ASSERT_EQUALS = true; public void testRewriter(String in, String out) { testRewriter(null, in, out); } public void testRewriter(String testName, String in, String out) { File tmp = null; try { tmp = File.createTempFile("test", ".js"); FileUtil.writeFile(tmp, in); final Map<IMethod, CorrelationSummary> summaries = makeCorrelationFinder().findCorrelatedAccesses(Collections.singleton(new SourceURLModule(tmp.toURI().toURL()))); CAstImpl ast = new CAstImpl(); CAstEntity inEntity = parseJS(tmp, ast); ExtractionPolicyFactory policyFactory = new ExtractionPolicyFactory() { @Override public ExtractionPolicy createPolicy(CAstEntity entity) { CorrelatedPairExtractionPolicy policy = CorrelatedPairExtractionPolicy.make(entity, summaries); Assert.assertNotNull(policy); return policy; } }; String actual = new CAstDumper().dump(new ClosureExtractor(ast, policyFactory).rewrite(inEntity)); actual = TestForInBodyExtraction.eraseGeneratedNames(actual); FileUtil.writeFile(tmp, out); String expected = new CAstDumper().dump(parseJS(tmp, ast)); expected = TestForInBodyExtraction.eraseGeneratedNames(expected); FileUtil.writeFile(new File("expected.dump"), expected); FileUtil.writeFile(new File("actual.dump"), actual); if(ASSERT_EQUALS) { Assert.assertEquals(testName, expected, actual); } } catch (IOException e) { e.printStackTrace(); } catch (ClassHierarchyException e) { e.printStackTrace(); } finally { if(tmp != null && tmp.exists()) tmp.delete(); } } protected CAstEntity parseJS(File tmp, CAstImpl ast) throws IOException { String moduleName = tmp.getName(); SourceFileModule module = new SourceFileModule(tmp, moduleName, null); return parseJS(ast, module); } protected abstract CAstEntity parseJS(CAstImpl ast, SourceModule module) throws IOException; protected abstract CorrelationFinder makeCorrelationFinder(); // example from the paper @Test public void test1() { testRewriter("function extend(dest, src) {\n" + " for(var p in src) {\n" + " dest[p] = src[p];\n" + " }\n" + "}", "function extend(dest, src) {\n" + " for(var p in src) {\n" + " (function _forin_body_0(p) {\n" + " dest[p] = src[p];\n" + " })(p);\n" + " }\n" + "}"); } // example from the paper, but with single-statement loop body @Test public void test2() { testRewriter("function extend(dest, src) {\n" + " for(var p in src)\n" + " dest[p] = src[p];\n" + "}", "function extend(dest, src) {\n" + " for(var p in src)\n" + " (function _forin_body_0(p) {\n" + " dest[p] = src[p];\n" + " })(p);\n" + "}"); } // example from the paper, but without var decl // currently fails because the loop index is a global variable @Test @Ignore public void test3() { testRewriter("function extend(dest, src) {\n" + " for(p in src)\n" + " dest[p] = src[p];\n" + "}", "function extend(dest, src) {\n" + " for(p in src)\n" + " (function _forin_body_0(p) {\n" + " dest[p] = src[p];\n" + " })(p);\n" + "}"); } // example from the paper, but with separate var decl @Test public void test4() { testRewriter("function extend(dest, src) {\n" + " var p;\n" + " for(p in src)\n" + " dest[p] = src[p];\n" + "}", "function extend(dest, src) {\n" + " var p;\n" + " for(p in src)\n" + " (function _forin_body_0(p) {\n" + " dest[p] = src[p];\n" + " })(p);\n" + "}"); } // example from the paper, but with weirdly placed var decl @Test public void test5() { testRewriter("function extend(dest, src) {\n" + " for(p in src) {\n" + " var p;\n" + " dest[p] = src[p];\n" + " }\n" + "}", "function extend(dest, src) {\n" + " for(p in src) {\n" + " var p;\n" + " (function _forin_body_0(p) {\n" + " dest[p] = src[p];\n" + " })(p);\n" + " }\n" + "}"); } // example from the paper, but with weirdly placed var decl in a different place @Test public void test6() { testRewriter("function extend(dest, src) {\n" + " for(p in src) {\n" + " dest[p] = src[p];\n" + " var p;\n" + " }\n" + "}", "function extend(dest, src) {\n" + " for(p in src) {\n" + " var p;\n" + " (function _forin_body_0(p) {\n" + " dest[p] = src[p];\n" + " })(p);\n" + " }\n" + "}"); } // example where loop variable is referenced after the loop // currently fails because the check is not implemented yet @Test @Ignore public void test7() { testRewriter("function extend(dest, src) {\n" + " for(var p in src) {\n" + " dest[p] = src[p];\n" + " p = true;\n" + " }\n" + " return p;\n" + "}", null); } // example with "this" @Test public void test8() { testRewriter("Object.prototype.extend = function(src) {\n" + " for(var p in src)\n" + " this[p] = src[p];\n" + "}", "Object.prototype.extend = function(src) {\n" + " for(var p in src)\n" + " (function _forin_body_0(p, thi$) {\n" + " thi$[p] = src[p];\n" + " })(p, this);\n" + "}"); } // another example with "this" // fails since variables from enclosing functions are no longer in SSA form, hence no correlation is found @Test @Ignore public void test9() { testRewriter("function defglobals(globals) {\n" + " for(var p in globals) {\n" + " (function() {\n" + " this[p] = globals[p];\n" + " })();\n" + " }\n" + "}", "function defglobals(globals) {\n" + " for(var p in globals) {\n" + " (function() {\n" + " (function _forin_body_0(p, thi$) {\n" + " thi$[p] = globals[p];\n" + " })(p, this)\n" + " })();\n" + " }\n" + "}"); } // an example with "break" @Test public void test10() { testRewriter("function extend(dest, src) {\n" + " for(var p in src) {\n" + " if(p == \"stop\")\n" + " break;\n" + " dest[p] = src[p];\n" + " }\n" + "}", "function extend(dest, src) {\n" + " for(var p in src) {\n" + " if(p == \"stop\")\n" + " break;" + " (function _forin_body_0(p) {\n" + " dest[p] = src[p];\n" + " })(p);\n" + " }\n" + "}"); } // another example with "break" @Test public void test11() { testRewriter("function extend(dest, src) {\n" + " for(var p in src) {\n" + " while(true) {\n" + " dest[p] = src[p];\n" + " break;\n" + " }\n" + " }\n" + "}", "function extend(dest, src) {\n" + " for(var p in src) {\n" + " while(true) {\n" + " (function _forin_body_0(p) {\n" + " dest[p] = src[p];\n" + " })(p);\n" + " break;\n" + " }\n" + " }\n" + "}"); } // an example with labelled "break" @Test public void test12() { testRewriter("function extend(dest, src) {\n" + " outer: for(var p in src) {\n" + " while(true) {\n" + " dest[p] = src[p];\n" + " break outer;\n" + " }\n" + " }\n" + "}", "function extend(dest, src) {\n" + " outer: for(var p in src) {\n" + " while(true) {\n" + " (function _forin_body_0(p) {\n" + " dest[p] = src[p];\n" + " })(p);" + " break outer;\n" + " }\n" + " }\n" + "}"); } // an example with exceptions @Test public void test13() { testRewriter("function extend(dest, src) {\n" + " for(var p in src) {\n" + " if(p == '__proto__')\n" + " throw new Exception('huh?');\n" + " dest[p] = src[p];\n" + " }\n" + "}", "function extend(dest, src) {\n" + " for(var p in src) {\n" + " if(p == '__proto__')\n" + " throw new Exception('huh?');\n" + " (function _forin_body_0(p) {\n" + " dest[p] = src[p];\n" + " })(p);\n" + " }\n" + "}"); } // an example with a "with" block @Test public void test14() { testRewriter("function extend(dest, src) {\n" + " var o = { dest: dest };\n" + " with(o) {\n" + " for(var p in src) {\n" + " dest[p] = src[p];\n" + " }\n" + " }\n" + "}", "function extend(dest, src) {\n" + " var o = { dest: dest };\n" + " with(o) {\n" + " for(var p in src) {\n" + " (function _forin_body_0(p) {\n" + " dest[p] = src[p];\n" + " })(p);\n" + " }\n" + " }\n" + "}"); } // example with two functions @Test public void test15() { testRewriter("function extend(dest, src) {\n" + " for(var p in src)\n" + " dest[p] = src[p];\n" + "}\n" + "function foo() {\n" + " extend({}, {});\n" + "}\n" + "foo();", "function extend(dest, src) {\n" + " for(var p in src)\n" + " (function _forin_body_0(p) {\n" + " dest[p] = src[p];\n" + " })(p);\n" + "}\n" + "function foo() {\n" + " extend({}, {});\n" + "}\n" + "foo();"); } @Test public void test16() { testRewriter("function ext(dest, src) {\n" + " for(var p in src)\n" + " do_ext(dest, p, src);\n" + "}\n" + "function do_ext(x, p, y) { x[p] = y[p]; }", "function ext(dest, src) {\n" + " for(var p in src)\n" + " do_ext(dest, p, src);\n" + "}\n" + "function do_ext(x, p, y) { x[p] = y[p]; }"); } @Test public void test17() { testRewriter("function implement(dest, src) {\n" + " for(var p in src) {\n" + " dest.prototype[p] = src[p];\n" + " }\n" + "}", "function implement(dest, src) {\n" + " for(var p in src) {\n" + " (function _forin_body_0(p) {\n" + " dest.prototype[p] = src[p];\n" + " })(p);\n" + " }\n" + "}"); } // fails since the assignment to "value" in the extracted version gets a (spurious) reference error CFG edge @Test public void test18() { testRewriter("function addMethods(source) {\n" + " var properties = Object.keys(source);\n" + " for (var i = 0, length = properties.length; i < length; i++) {\n" + " var property = properties[i], value = source[property];\n" + " this.prototype[property] = value;\n" + " }\n" + " return this;\n" + "}", "function addMethods(source) {\n" + " var properties = Object.keys(source);\n" + " for (var i = 0, length = properties.length; i < length; i++) {\n" + " var property, value; property = properties[i]; value = (function _forin_body_0(property, thi$) { var value = source[property]; \n" + " thi$.prototype[property] = value; return value; })(property, this);\n" + " }\n" + " return this;\n" + "}"); } // slight variation of test18 @Test public void test18_b() { testRewriter("function addMethods(source) {\n" + " var properties = Object.keys(source);\n" + " for (var i = 0, length = properties.length; i < length; i++) {\n" + " var property = properties[i], foo = 23, value = source[property];\n" + " this.prototype[property] = value;\n" + " }\n" + " return this;\n" + "}", "function addMethods(source) {\n" + " var properties = Object.keys(source);\n" + " for (var i = 0, length = properties.length; i < length; i++) {\n" + " var property, foo, value; property = properties[i]; foo = 23; value = (function _forin_body_0(property, thi$) { var value = source[property];\n" + " thi$.prototype[property] = value; return value; })(property, this);\n" + " }\n" + " return this;\n" + "}"); } // fails since the assignment to "value" in the extracted version gets a (spurious) reference error CFG edge @Test public void test18_c() { testRewriter("function addMethods(source) {\n" + " var properties = Object.keys(source);\n" + " for (var i = 0, length = properties.length; i < length; i++) {\n" + " var property = properties[i], foo = 23, value = source[property], bar = 42;\n" + " this.prototype[property] = value;\n" + " }\n" + " return this;\n" + "}", "function addMethods(source) {\n" + " var properties = Object.keys(source);\n" + " for (var i = 0, length = properties.length; i < length; i++) {\n" + " var property, foo, value, bar; property = properties[i]; foo = 23; value = function _forin_body_0(property, thi$) { var value = source[property]; bar = 42;\n" + " thi$.prototype[property] = value; return value; }(property, this);\n" + " }\n" + " return this;\n" + "}"); } @Test public void test19() { testRewriter("function extend(dest, src) {\n" + " for(var p in src)\n" + " if(foo(p)) write.call(dest, p, src[p]);\n" + "}\n" + "function write(p, v) { this[p] = v; }", "function extend(dest, src) {\n" + " for(var p in src)\n" + " (function _forin_body_0(p) { if(foo(p)) write.call(dest, p, src[p]); })(p);\n" + "}\n" + "function write(p, v) { this[p] = v; }"); } // fails due to a missing LOCAL_SCOPE node @Test @Ignore public void test20() { testRewriter("function every(object, fn, bind) {\n" + " for(var key in object)\n" + " if(hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false;\n" + "}", "function every(object, fn, bind) {\n" + " for(var key in object) {\n" + " re$ = (function _forin_body_0(key) {\n" + " if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return { type: 'return', value: false };\n" + " })(key);\n" + " if(re$) { if(re$.type == 'return') return re$.value; }\n" + " }\n" + "}"); } @Test public void test21() { testRewriter("function extend(dest, src) {\n" + " var x, y;\n" + " for(var name in src) {\n" + " x = dest[name];\n" + " y = src[name];\n" + " dest[name] = join(x,y);\n" + " }\n" + "}", "function extend(dest, src) {\n" + " var x, y;\n" + " for(var name in src) {\n" + " (function _forin_body_0(name) { x = dest[name];\n" + " y = src[name];\n" + " dest[name] = join(x,y); })(name);\n" + " }\n" + "}"); } @Test public void test22() { testRewriter("function(object, keys){\n" + " var results = {};\n" + " for (var i = 0, l = keys.length; i < l; i++){\n" + " var k = keys[i];\n" + " if (k in object) results[k] = object[k];\n" + " }\n" + " return results;\n" + "}", "function(object, keys){\n" + " var results = {};\n" + " for (var i = 0, l = keys.length; i < l; i++){\n" + " var k = keys[i];\n" + " (function _forin_body_0(k) { if (k in object) results[k] = object[k]; })(k);\n" + " }\n" + " return results;\n" + "}"); } // variant of test1 @Test public void test23() { testRewriter("function extend(dest, src) {\n" + " var s;\n" + " for(var p in src) {\n" + " s = src[p];\n" + " dest[p] = s;\n" + " }\n" + "}", "function extend(dest, src) {\n" + " var s;\n" + " for(var p in src) {\n" + " s = (function _forin_body_0(p) {\n" + " var s;" + " s = src[p];\n" + " dest[p] = s;\n" + " return s;" + " })(p);\n" + " }\n" + "}"); } // cannot extract for-in body referring to "arguments" @Test public void test24() { testRewriter("function extend(dest, src) {" + " for(var p in src) {" + " arguments[0][p] = src[p];" + " }" + "}", "function extend(dest, src) {" + " for(var p in src) {" + " arguments[0][p] = src[p];" + " }" + "}"); } @Test public void test25() { testRewriter( "function eachProp(obj, func) {" + " var prop;" + " for (prop in obj) {" + " if (hasProp(obj, prop)) {" + " if (func(obj[prop], prop)) {" + " break;" + " }" + " }" + " }" + "}", "function eachProp(obj, func) {" + " var prop;" + " for (prop in obj) {" + " if (hasProp(obj, prop)) {" + " re$ = (function _forin_body_0 (prop) { if (func(obj[prop], prop)) { return { type: \"goto\", target: 0 }; } })(prop);" + " if (re$) {" + " if (re$.type == \"goto\") {" + " if (re$.target == 0)" + " break;" + " }" + " }" + " }" + " }" + "}"); } }