diff --git a/com.ibm.wala.cast.js.rhino.test/harness-src/com/ibm/wala/cast/js/test/TestCorrelatedPairExtractionRhino.java b/com.ibm.wala.cast.js.rhino.test/harness-src/com/ibm/wala/cast/js/test/TestCorrelatedPairExtractionRhino.java new file mode 100644 index 000000000..9c79a5a7e --- /dev/null +++ b/com.ibm.wala.cast.js.rhino.test/harness-src/com/ibm/wala/cast/js/test/TestCorrelatedPairExtractionRhino.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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.IOException; + +import com.ibm.wala.cast.js.ipa.callgraph.correlations.CorrelationFinder; +import com.ibm.wala.cast.js.translator.CAstRhinoTranslatorFactory; +import com.ibm.wala.cast.tree.CAstEntity; +import com.ibm.wala.cast.tree.impl.CAstImpl; +import com.ibm.wala.classLoader.SourceModule; +import org.mozilla.javascript.RhinoToAstTranslator; + +public class TestCorrelatedPairExtractionRhino extends TestCorrelatedPairExtraction { + protected CorrelationFinder makeCorrelationFinder() { + return new CorrelationFinder(new CAstRhinoTranslatorFactory()); + } + + protected CAstEntity parseJS(CAstImpl ast, SourceModule module) throws IOException { + RhinoToAstTranslator.resetGensymCounters(); + RhinoToAstTranslator translator = new RhinoToAstTranslator(ast, module, module.getName()); + CAstEntity entity = translator.translate(); + return entity; + } +} diff --git a/com.ibm.wala.cast.js.rhino.test/harness-src/com/ibm/wala/cast/js/test/TestForInBodyExtractionRhino.java b/com.ibm.wala.cast.js.rhino.test/harness-src/com/ibm/wala/cast/js/test/TestForInBodyExtractionRhino.java new file mode 100644 index 000000000..661221223 --- /dev/null +++ b/com.ibm.wala.cast.js.rhino.test/harness-src/com/ibm/wala/cast/js/test/TestForInBodyExtractionRhino.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * 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.IOException; + +import org.mozilla.javascript.RhinoToAstTranslator; + +import com.ibm.wala.cast.tree.CAstEntity; +import com.ibm.wala.cast.tree.impl.CAstImpl; +import com.ibm.wala.classLoader.SourceModule; + +public class TestForInBodyExtractionRhino extends TestForInBodyExtraction { + protected CAstEntity parseJS(CAstImpl ast, SourceModule module) throws IOException { + RhinoToAstTranslator.resetGensymCounters(); + RhinoToAstTranslator translator = new RhinoToAstTranslator(ast, module, module.getName()); + CAstEntity entity = translator.translate(); + return entity; + } +} diff --git a/com.ibm.wala.cast.js.rhino/META-INF/MANIFEST.MF b/com.ibm.wala.cast.js.rhino/META-INF/MANIFEST.MF index 425d4531f..947ffa5fb 100644 --- a/com.ibm.wala.cast.js.rhino/META-INF/MANIFEST.MF +++ b/com.ibm.wala.cast.js.rhino/META-INF/MANIFEST.MF @@ -11,4 +11,5 @@ Require-Bundle: org.eclipse.core.runtime, com.ibm.wala.core;bundle-version="1.1.3" Bundle-RequiredExecutionEnvironment: J2SE-1.5 Bundle-ActivationPolicy: lazy -Export-Package: com.ibm.wala.cast.js.translator +Export-Package: com.ibm.wala.cast.js.translator, + org.mozilla.javascript diff --git a/com.ibm.wala.cast.js.test/harness-src/com/ibm/wala/cast/js/test/CAstDumper.java b/com.ibm.wala.cast.js.test/harness-src/com/ibm/wala/cast/js/test/CAstDumper.java new file mode 100644 index 000000000..0311db161 --- /dev/null +++ b/com.ibm.wala.cast.js.test/harness-src/com/ibm/wala/cast/js/test/CAstDumper.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * 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.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Map; +import java.util.Map.Entry; + +import com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction.NodeLabeller; +import com.ibm.wala.cast.tree.CAstControlFlowMap; +import com.ibm.wala.cast.tree.CAstEntity; +import com.ibm.wala.cast.tree.CAstNode; +import com.ibm.wala.cast.util.CAstPrinter; +import com.ibm.wala.util.collections.HashMapFactory; + +/** + * A class for dumping a textual representation of a CAst syntax tree. + * + *

Similar to {@link CAstPrinter}, but additionally prints control flow information.

+ * + *

It also suppresses certain kinds of spurious nodes such as empty statements within a block or + * block expressions with a single child, which are simply artifacts of the translation process. This + * is not nice, but needed for our tests.

+ * + * @author mschaefer + * + */ +public class CAstDumper { + private final NodeLabeller labeller; + + public CAstDumper() { + labeller = new NodeLabeller(); + } + + public CAstDumper(NodeLabeller labeller) { + this.labeller = labeller; + } + + private static String indent(int indent) { + StringBuilder buf = new StringBuilder(); + for(int i=0;i scopedEntities = Collections.emptySet(); + if(entity.getKind() == CAstEntity.SCRIPT_ENTITY) { + buf.append(indent(indent) + entity.getName() + ":\n"); + scopedEntities = dumpScopedEntities(entity, indent+2, buf); + dump(entity.getAST(), indent, buf, entity.getControlFlow()); + } else if(entity.getKind() == CAstEntity.FUNCTION_ENTITY) { + buf.append(indent(indent) + "function " + entity.getName() + "("); + for(int i=0;i0) + buf.append(", "); + buf.append(entity.getArgumentNames()[i]); + } + buf.append(") {\n"); + scopedEntities = dumpScopedEntities(entity, indent+2, buf); + dump(entity.getAST(), indent+2, buf, entity.getControlFlow()); + buf.append(indent(indent) + "}\n\n"); + } else { + throw new Error("Unknown entity kind " + entity.getKind()); + } + for(CAstEntity scopedEntity : scopedEntities) + dump(scopedEntity, indent, buf); + } + + private Collection dumpScopedEntities(CAstEntity entity, int indent, StringBuilder buf) { + ArrayList scopedEntities = new ArrayList(); + Map m = HashMapFactory.make(); + for(Entry> e : entity.getAllScopedEntities().entrySet()) + for(CAstEntity scopedEntity : e.getValue()) { + scopedEntities.add(scopedEntity); + m.put(scopedEntity, e.getKey()); + } + Collections.sort(scopedEntities, new Comparator() { + public int compare(CAstEntity o1, CAstEntity o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + + buf.append(indent(indent) + "> "); + boolean first = true; + for(CAstEntity scopedEntity : scopedEntities) { + if(first) + first = false; + else + buf.append(", "); + buf.append(scopedEntity.getName() + "@" + labeller.addNode(m.get(scopedEntity))); + } + buf.append("\n"); + return scopedEntities; + } + + private void dump(CAstNode node, int indent, StringBuilder buf, CAstControlFlowMap cfg) { + // normalise away single-child block expressions + if(node.getKind() == CAstNode.BLOCK_EXPR && node.getChildCount() == 1) { + dump(node.getChild(0), indent, buf, cfg); + } else { + buf.append(indent(indent) + labeller.addNode(node) + ": "); + if(node.getKind() == CAstNode.CONSTANT) { + if(node.getValue() == null) + buf.append("null"); + else if(node.getValue() instanceof Integer) + buf.append(node.getValue()+""); + else + buf.append("\"" + node.getValue() + "\""); + } else if(node.getKind() == CAstNode.OPERATOR) { + buf.append(node.getValue().toString()); + } else { + buf.append(CAstPrinter.kindAsString(node.getKind())); + } + Collection labels = cfg.getTargetLabels(node); + if(!labels.isEmpty()) { + buf.append(" ["); + boolean first = true; + for(Object label : labels) { + CAstNode target = cfg.getTarget(node, label); + if(first) { + first = false; + } else { + buf.append(", "); + } + if(label instanceof CAstNode) + buf.append("CAstNode@" + labeller.addNode((CAstNode)label) + ": "); + else + buf.append(label + ": "); + buf.append(labeller.addNode(target)); + } + buf.append("]"); + } + buf.append("\n"); + for(int i=0;i 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)); + + FileUtil.writeFile(tmp, out); + String expected = new CAstDumper().dump(parseJS(tmp, ast)); + + if(ASSERT_EQUALS) { + Assert.assertEquals(testName, expected, actual); + } else { + FileUtil.writeFile(new File("expected.dump"), expected); + FileUtil.writeFile(new File("actual.dump"), 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); + 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" + @Test + 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 @Ignore + 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 = properties[i], value; (function _forin_body_0(property, thi$) { value = source[property];\n" + + " thi$.prototype[property] = 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" + + " if(foo(p)) (function _forin_body_0(p) { write.call(dest, p, src[p]); })(p);\n" + + "}\n" + + "function write(p, v) { this[p] = v; }"); + } + + @Test + 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" + + "}"); + } +} diff --git a/com.ibm.wala.cast.js.test/harness-src/com/ibm/wala/cast/js/test/TestForInBodyExtraction.java b/com.ibm.wala.cast.js.test/harness-src/com/ibm/wala/cast/js/test/TestForInBodyExtraction.java new file mode 100644 index 000000000..83e9bc178 --- /dev/null +++ b/com.ibm.wala.cast.js.test/harness-src/com/ibm/wala/cast/js/test/TestForInBodyExtraction.java @@ -0,0 +1,762 @@ +/******************************************************************************* + * 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 org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction.ClosureExtractor; +import com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction.ForInBodyExtractionPolicy; +import com.ibm.wala.cast.tree.CAstEntity; +import com.ibm.wala.cast.tree.impl.CAstImpl; +import com.ibm.wala.classLoader.SourceFileModule; +import com.ibm.wala.classLoader.SourceModule; +import com.ibm.wala.util.io.FileUtil; + +public abstract class TestForInBodyExtraction { + 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); + CAstImpl ast = new CAstImpl(); + String actual = new CAstDumper().dump(new ClosureExtractor(ast, ForInBodyExtractionPolicy.FACTORY).rewrite(parseJS(tmp, ast))); + + FileUtil.writeFile(tmp, out); + String expected = new CAstDumper().dump(parseJS(tmp, ast)); + + Assert.assertEquals(testName, expected, actual); + } catch (IOException 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); + return parseJS(ast, module); + } + + protected abstract CAstEntity parseJS(CAstImpl ast, SourceModule module) throws IOException; + + // example from the paper + @Test + public void test1() { + testRewriter("function extend(dest, src) {" + + " for(var p in src) {" + + " dest[p] = src[p];" + + " }" + + "}", + "function extend(dest, src) {" + + " for(var p in src) {" + + " (function _forin_body_0(p) {" + + " dest[p] = src[p];" + + " })(p);" + + " }" + + "}"); + } + + // example from the paper, but with single-statement loop body + @Test + public void test2() { + testRewriter("function extend(dest, src) {" + + " for(var p in src)" + + " dest[p] = src[p];" + + "}", + "function extend(dest, src) {" + + " for(var p in src)" + + " (function _forin_body_0(p) {" + + " dest[p] = src[p];" + + " })(p);" + + "}"); + } + + // example from the paper, but without var decl + @Test + public void test3() { + testRewriter("function extend(dest, src) {" + + " for(p in src)" + + " dest[p] = src[p];" + + "}", + "function extend(dest, src) {" + + " for(p in src)" + + " (function _forin_body_0(p) {" + + " dest[p] = src[p];" + + " })(p);" + + "}"); + } + + // example from the paper, but with separate var decl + @Test + public void test4() { + testRewriter("function extend(dest, src) {" + + " var p;" + + " for(p in src)" + + " dest[p] = src[p];" + + "}", + "function extend(dest, src) {" + + " var p;" + + " for(p in src)" + + " (function _forin_body_0(p) {" + + " dest[p] = src[p];" + + " })(p);" + + "}"); + } + + // example from the paper, but with weirdly placed var decl + @Test + public void test5() { + testRewriter("function extend(dest, src) {" + + " for(p in src) {" + + " var p;" + + " dest[p] = src[p];" + + " }" + + "}", + "function extend(dest, src) {" + + " for(p in src) {" + + " var p;" + + " (function _forin_body_0(p) {" + + " dest[p] = src[p];" + + " })(p);" + + " }" + + "}"); + } + + // example from the paper, but with weirdly placed var decl in a different place + @Test + public void test6() { + testRewriter("function extend(dest, src) {" + + " for(p in src) {" + + " dest[p] = src[p];" + + " var p;" + + " }" + + "}", + "function extend(dest, src) {" + + " for(p in src) {" + + " var p;" + + " (function _forin_body_0(p) {" + + " dest[p] = src[p];" + + " })(p);" + + " }" + + "}"); + } + + // example where loop variable is referenced after the loop + // this isn't currently handled, hence the test fails + @Test @Ignore + public void test7() { + testRewriter("function extend(dest, src) {" + + " for(var p in src) {" + + " dest[p] = src[p];" + + " p = true;" + + " }" + + " return p;" + + "}", + "function extend(dest, src) {" + + " for(var p in src)" + + " (function _let_0(_let_parm_0) {" + + " (function _forin_body_0(p) {" + + " try {" + + " dest[p] = src[p];" + + " p = true;" + + " } finally {" + + " _let_parm_0 = p;" + + " }" + + " })(p);" + + " p = _let_parm_0;" + + " })(p);" + + " return p;" + + "}"); + } + + // example with "this" + @Test + public void test8() { + testRewriter("Object.prototype.extend = function(src) {" + + " for(var p in src)" + + " this[p] = src[p];" + + "}", + "Object.prototype.extend = function(src) {" + + " for(var p in src)" + + " (function _forin_body_0(p, thi$) {" + + " thi$[p] = src[p];" + + " })(p, this);" + + "}"); + } + + // another example with "this" + @Test + public void test9() { + testRewriter("function defglobals(globals) {" + + " for(var p in globals) {" + + " (function() {" + + " this[p] = globals[p];" + + " })();" + + " }" + + "}", + "function defglobals(globals) {" + + " for(var p in globals) {" + + " (function _forin_body_0(p) {" + + " (function() {" + + " this[p] = globals[p];" + + " })()" + + " })(p);" + + " }" + + "}"); + } + + // an example with "break" + @Test + public void test10() { + testRewriter("function extend(dest, src) {" + + " for(var p in src) {" + + " if(p == \"stop\")" + + " break;" + + " dest[p] = src[p];" + + " }" + + "}", + "function extend(dest, src) {" + + " for(var p in src) {" + + " re$ = (function _forin_body_0(p) {" + + " if(p == \"stop\")" + + " return {type: 'goto', target: 0};" + + " dest[p] = src[p];" + + " })(p);" + + " if(re$) {" + + " if(re$.type == 'goto') {" + + " if(re$.target == 0)" + + " break;" + + " }" + + " }" + + " }" + + "}"); + } + + // another example with "break" + @Test + public void test11() { + testRewriter("function extend(dest, src) {" + + " for(var p in src) {" + + " while(true) {" + + " dest[p] = src[p];" + + " break;" + + " }" + + " }" + + "}", + "function extend(dest, src) {" + + " for(var p in src) {" + + " (function _forin_body_0(p) {" + + " while(true) {" + + " dest[p] = src[p];" + + " break;" + + " }" + + " })(p);" + + " }" + + "}"); + } + + // an example with labelled "break" + @Test + public void test12() { + testRewriter("function extend(dest, src) {" + + " outer: for(var p in src) {" + + " while(true) {" + + " dest[p] = src[p];" + + " break outer;" + + " }" + + " }" + + "}", + "function extend(dest, src) {" + + " outer: for(var p in src) {" + + " re$ = (function _forin_body_0(p) {" + + " while(true) {" + + " dest[p] = src[p];" + + " return {type: 'goto', target: 0};" + + " }" + + " })(p);" + + " if(re$) {" + + " if(re$.type == 'goto') {" + + " if(re$.target == 0)" + + " break outer;" + + " }" + + " }" + + " }" + + "}"); + } + + // an example with exceptions + @Test + public void test13() { + testRewriter("function extend(dest, src) {" + + " for(var p in src) {" + + " if(p == '__proto__')" + + " throw new Exception('huh?');" + + " dest[p] = src[p];" + + " }" + + "}", + "function extend(dest, src) {" + + " for(var p in src) {" + + " (function _forin_body_0(p) {" + + " if(p == '__proto__')" + + " throw new Exception('huh?');" + + " dest[p] = src[p];" + + " })(p);" + + " }" + + "}"); + + } + + // an example with a var decl + // this test fails due to a trivial difference between transformed and expected CAst that isn't semantically relevant + @Test @Ignore + public void test14() { + testRewriter("x = 23;" + + "function foo() {" + + " x = 42;" + + " for(var p in {toString : 23}) {" + + " var x = 56;" + + " alert(x);" + + " }" + + " alert(x);" + + "}" + + "foo();" + + "alert(x);", + "x = 23;" + + "function foo() {" + + " x = 42;" + + " for(var p in {toString : 23}) {" + + " var x;" + + " (function _forin_body_0(p) {" + + " x = 56;" + + " alert(x);" + + " })(p);" + + " }" + + " alert(x);" + + "}" + + "foo();" + + "alert(x);"); + } + + // another example with a var decl + @Test + public void test15() { + testRewriter("x = 23;" + + "function foo() {" + + " x = 42;" + + " for(var p in {toString : 23}) {" + + " (function() {" + + " var x = 56;" + + " alert(x);" + + " })();" + + " }" + + " alert(x);" + + "}" + + "foo();" + + "alert(x);", + "x = 23;" + + "function foo() {" + + " x = 42;" + + " for(var p in {toString : 23}) {" + + " (function _forin_body_0(p) {" + + " (function() {" + + " var x = 56;" + + " alert(x);" + + " })();" + + " })(p);" + + " }" + + " alert(x);" + + "}" + + "foo();" + + "alert(x);"); + } + + // an example with a "with" block + @Test + public void test16() { + testRewriter("function extend(dest, src) {" + + " var o = { dest: dest };" + + " with(o) {" + + " for(var p in src) {" + + " dest[p] = src[p];" + + " }" + + " }" + + "}", + "function extend(dest, src) {" + + " var o = { dest: dest };" + + " with(o) {" + + " for(var p in src) {" + + " (function _forin_body_0(p) {" + + " dest[p] = src[p];" + + " })(p);" + + " }" + + " }" + + "}"); + } + + // top-level for-in loop + @Test + public void test17() { + testRewriter("var o = {x:23};" + + "for(x in o) {" + + " o[x] += 19;" + + "}", + "var o = {x:23};" + + "for(x in o) {" + + " (function _forin_body_0(x) {" + + " o[x] += 19;" + + " })(x);" + + "}"); + } + + + // nested for-in loops + @Test + public void test18() { + testRewriter("var o = {x:{y:23}};" + + "for(x in o) {" + + " for(y in o[x]) {" + + " o[x][y] += 19;" + + " }" + + "}", + "var o = {x:{y:23}};" + + "for(x in o) {" + + " (function _forin_body_0(x) {" + + " for(y in o[x]) {" + + " (function _forin_body_1(y) {" + + " o[x][y] += 19;" + + " })(y);" + + " }" + + " })(x);" + + "}"); + } + + // return in loop body + @Test + public void test19() { + testRewriter("function foo(x) {" + + " for(var p in x) {" + + " if(p == 'ret')" + + " return x[p];" + + " x[p]++;" + + " }" + + "}", + "function foo(x) {" + + " for(var p in x) {" + + " re$ = (function _forin_body_0(p) {" + + " if(p == 'ret')" + + " return {type: 'return', value: x[p]};" + + " x[p]++;" + + " })(p);" + + " if(re$) {" + + " if(re$.type == 'return')" + + " return re$.value;" + + " }" + + " }" + + "}"); + } + + // example with two functions + @Test + public void test20() { + testRewriter("function extend(dest, src) {" + + " for(var p in src)" + + " dest[p] = src[p];" + + "}" + + "function foo() {" + + " extend({}, {});" + + "}" + + "foo();", + "function extend(dest, src) {" + + " for(var p in src)" + + " (function _forin_body_0(p) {" + + " dest[p] = src[p];" + + " })(p);" + + "}" + + "function foo() {" + + " extend({}, {});" + + "}" + + "foo();"); + } + + // example with nested for-in loops and this (adapted from MooTools) + @Test + public void test21() { + testRewriter("function foo() {" + + " var result = [];" + + " for(var style in Element.ShortStyles) {" + + " for(var s in Element.ShortStyles[style]) {" + + " result.push(this.getStyle(s));" + + " }" + + " }" + + "}", + "function foo() {" + + " var result = [];" + + " for(var style in Element.ShortStyles) {" + + " var s;" + + " (function _forin_body_0(style, thi$) {" + + " for(s in Element.ShortStyles[style]) {" + + " (function _forin_body_1(s) {" + + " result.push(thi$.getStyle(s));" + + " })(s);" + + " }" + + " })(style, this);" + + " }" + + "}"); + } + + // example with nested for-in loops and continue (adapted from MooTools) + @Test + public void test22() { + testRewriter("function foo(property) {" + + " var result = [];" + + " for(var style in Element.ShortStyles) {" + + " if(property != style) continue; " + + " for(var s in Element.ShortStyles[style]) {" + + " ;" + + " }" + + " }" + + "}", + "function foo(property) {" + + " var result = [];" + + " for(var style in Element.ShortStyles) {" + + " var s;" + + " re$ = (function _forin_body_0(style) {" + + " if(property != style) return {type:'goto', target:0}; " + + " for(s in Element.ShortStyles[style]) {" + + " (function _forin_body_1(s) {" + + " ;" + + " })(s);" + + " }" + + " })(style);" + + " if(re$) {" + + " if(re$.type == 'goto') {" + + " if(re$.target == 0)" + + " continue;" + + " }" + + " }" + + " }" + + "}"); + } + + @Test + public void test23() { + testRewriter("function foo(obj) {" + + " for(var p in obj) {" + + " if(p != 'bar')" + + " continue;" + + " return obj[p];" + + " }" + + "}", + "function foo(obj) {" + + " for(var p in obj) {" + + " re$ = (function _forin_body_0(p) {" + + " if(p != 'bar')" + + " return {type:'goto', target:0};" + + " return {type:'return', value:obj[p]};" + + " })(p);" + + " if(re$) {" + + " if(re$.type == 'return')" + + " return re$.value;" + + " if(re$.type == 'goto') {" + + " if(re$.target == 0)" + + " continue;" + + " }" + + " }" + + " }" + + "}"); + } + + // this test fails since the rewritten CAst contains one more level of blocks + @Test @Ignore + public void test24() { + testRewriter("var addSlickPseudos = function() {" + + " for(var name in pseudos)" + + " if(pseudos.hasOwnProperty(name)) {" + + " ;" + + " }" + + "}", + "var addSlickPseudos = function() {" + + " for(var name in pseudos)" + + " (function _forin_body_0(name) {" + + " if(pseudos.hasOwnProperty(name)) {" + + " ;" + + " }" + + " })(name);" + + "}"); + } + + @Test + public void test25() { + testRewriter("function ext(dest, src) {" + + " for(var p in src)" + + " do_ext(dest, p, src);" + + "}" + + "function do_ext(x, p, y) { x[p] = y[p]; }", + "function ext(dest, src) {" + + " for(var p in src)" + + " (function _forin_body_0(p) {" + + " do_ext(dest, p, src);" + + " })(p);" + + "}" + + "function do_ext(x, p, y) { x[p] = y[p]; }"); + } + + @Test + public void test26() { + testRewriter("function foo(x) {" + + " for(p in x) {" + + " for(q in p[x]) {" + + " if(b)" + + " return 23;" + + " }" + + " }" + + "}", + "function foo(x) {" + + " for(p in x) {" + + " re$ = (function _forin_body_0(p) {" + + " for(q in p[x]) {" + + " re$ = (function _forin_body_1(q) {" + + " if(b)" + + " return { type: 'return', value: 23 };" + + " })(q);" + + " if(re$) {" + + " return re$;" + + " }" + + " }" + + " })(p);" + + " if(re$) {" + + " if(re$.type == 'return')" + + " return re$.value;" + + " }" + + " }" + + "}"); + } + + // variation of test22 + @Test + public void test27() { + testRewriter("function foo(property) {" + + " var result = [];" + + " outer: for(var style in Element.ShortStyles) {" + + " for(var s in Element.ShortStyles[style]) {" + + " if(s != style) continue outer;" + + " }" + + " }" + + "}", + "function foo(property) {" + + " var result = [];" + + " outer: for(var style in Element.ShortStyles) {" + + " var s;" + + " re$ = (function _forin_body_0(style) {" + + " for(s in Element.ShortStyles[style]) {" + + " re$ = (function _forin_body_1(s) {" + + " if(s != style) return {type:'goto', target:0};" + + " })(s);" + + " if(re$) {" + + " return re$;" + + " }" + + " }" + + " })(style);" + + " if(re$) {" + + " if(re$.type == 'goto') {" + + " if(re$.target == 0)" + + " continue outer;" + + " }" + + " }" + + " }" + + "}"); + } + + // another variation of test22 + @Test + public void test28() { + testRewriter("function foo(property) {" + + " var result = [];" + + " outer: for(var style in Element.ShortStyles) {" + + " for(var s in Element.ShortStyles[style]) {" + + " if(s != style) continue;" + + " }" + + " }" + + "}", + "function foo(property) {" + + " var result = [];" + + " outer: for(var style in Element.ShortStyles) {" + + " var s;" + + " (function _forin_body_0(style) {" + + " for(s in Element.ShortStyles[style]) {" + + " re$ = (function _forin_body_1(s) {" + + " if(s != style) return {type:'goto', target:0};" + + " })(s);" + + " if(re$) {" + + " if(re$.type == 'goto') {" + + " if(re$.target == 0)" + + " continue;" + + " }" + + " }" + + " }" + + " })(style);" + + " }" + + "}"); + } + + // test where the same entity (namely the inner function "copy") is rewritten more than once + // this probably shouldn't happen + @Test + public void test29() { + testRewriter("Element.addMethods = function(methods) {" + + " function copy() {" + + " for (var property in methods) {" + + " }" + + " }" + + " for (var tag in methods) {" + + " }" + + "};", + "Element.addMethods = function(methods) {" + + " function copy() {" + + " for (var property in methods) {" + + " (function _forin_body_1(property) {" + + " })(property);" + + " }" + + " }" + + " for (var tag in methods) {" + + " (function _forin_body_0(tag){ })(tag);" + + " }" + + "};"); + } + + @Test + public void test30() { + testRewriter("try {" + + " for(var i in {}) {" + + " f();" + + " }" + + "} catch(_) {}", + "try {" + + " for(var i in {}) {" + + " (function _forin_body_0(i) {" + + " f();" + + " })(i);" + + " }" + + "} catch(_) {}"); + } +} diff --git a/com.ibm.wala.cast.js.test/harness-src/com/ibm/wala/cast/js/test/Util.java b/com.ibm.wala.cast.js.test/harness-src/com/ibm/wala/cast/js/test/Util.java index 5793e3fcf..1e2121b67 100755 --- a/com.ibm.wala.cast.js.test/harness-src/com/ibm/wala/cast/js/test/Util.java +++ b/com.ibm.wala.cast.js.test/harness-src/com/ibm/wala/cast/js/test/Util.java @@ -119,7 +119,7 @@ public class Util extends com.ibm.wala.cast.js.ipa.callgraph.Util { protected static JSCFABuilder makeCG(JavaScriptLoaderFactory loaders, AnalysisScope scope, boolean useOneCFA, boolean handleCallApply) throws IOException { try { IClassHierarchy cha = makeHierarchy(scope, loaders); - com.ibm.wala.cast.test.Util.checkForFrontEndErrors(cha); + com.ibm.wala.cast.js.util.Util.checkForFrontEndErrors(cha); Iterable roots = makeScriptRoots(cha); JSAnalysisOptions options = makeOptions(scope, cha, roots); options.setHandleCallApply(handleCallApply); diff --git a/com.ibm.wala.cast.js/META-INF/MANIFEST.MF b/com.ibm.wala.cast.js/META-INF/MANIFEST.MF index e5b9d9e7e..f93b2ee2d 100644 --- a/com.ibm.wala.cast.js/META-INF/MANIFEST.MF +++ b/com.ibm.wala.cast.js/META-INF/MANIFEST.MF @@ -17,11 +17,14 @@ Export-Package: com.ibm.wala.cast.js, com.ibm.wala.cast.js.html, com.ibm.wala.cast.js.html.jericho, com.ibm.wala.cast.js.ipa.callgraph, + com.ibm.wala.cast.js.ipa.callgraph.correlations, + com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction, com.ibm.wala.cast.js.ipa.summaries, com.ibm.wala.cast.js.loader, com.ibm.wala.cast.js.ssa, com.ibm.wala.cast.js.translator, com.ibm.wala.cast.js.types, + com.ibm.wala.cast.js.util, com.ibm.wala.cast.js.vis Require-Bundle: com.ibm.wala.cast, com.ibm.wala.core, diff --git a/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/Correlation.java b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/Correlation.java new file mode 100644 index 000000000..2a0c215db --- /dev/null +++ b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/Correlation.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * 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.ipa.callgraph.correlations; + +import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; + +/** + * A correlation exists between a dynamic property read r and a dynamic property write w such that + * the value read in r may flow into w, and r and w are guaranteed to access a property of the same name. + * + * @author mschaefer + * + */ +public abstract class Correlation { + private final String indexName; + + protected Correlation(String indexName) { + this.indexName = indexName; + } + + public String getIndexName() { + return indexName; + } + + public abstract Position getStartPosition(SSASourcePositionMap positions); + public abstract Position getEndPosition(SSASourcePositionMap positions); + public abstract String pp(SSASourcePositionMap positions); + public abstract T accept(CorrelationVisitor visitor); +} \ No newline at end of file diff --git a/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/CorrelationFinder.java b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/CorrelationFinder.java new file mode 100644 index 000000000..6614a61dc --- /dev/null +++ b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/CorrelationFinder.java @@ -0,0 +1,272 @@ +/****************************************************************************** + * 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.ipa.callgraph.correlations; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.ibm.wala.cast.ipa.callgraph.CAstAnalysisScope; +import com.ibm.wala.cast.ir.ssa.AbstractReflectiveGet; +import com.ibm.wala.cast.ir.ssa.AbstractReflectivePut; +import com.ibm.wala.cast.ir.ssa.AstIRFactory; +import com.ibm.wala.cast.js.html.WebPageLoaderFactory; +import com.ibm.wala.cast.js.html.WebUtil; +import com.ibm.wala.cast.js.loader.JavaScriptLoader; +import com.ibm.wala.cast.js.translator.JavaScriptTranslatorFactory; +import com.ibm.wala.cast.js.util.Util; +import com.ibm.wala.cast.loader.AstMethod; +import com.ibm.wala.cast.loader.AstMethod.LexicalInformation; +import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; +import com.ibm.wala.classLoader.IClass; +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.classLoader.SourceModule; +import com.ibm.wala.ipa.callgraph.impl.Everywhere; +import com.ibm.wala.ipa.cha.ClassHierarchy; +import com.ibm.wala.ipa.cha.ClassHierarchyException; +import com.ibm.wala.ipa.cha.IClassHierarchy; +import com.ibm.wala.shrikeBT.IBinaryOpInstruction; +import com.ibm.wala.shrikeBT.IBinaryOpInstruction.IOperator; +import com.ibm.wala.ssa.DefUse; +import com.ibm.wala.ssa.IR; +import com.ibm.wala.ssa.IRFactory; +import com.ibm.wala.ssa.SSAAbstractInvokeInstruction; +import com.ibm.wala.ssa.SSABinaryOpInstruction; +import com.ibm.wala.ssa.SSAInstruction; +import com.ibm.wala.ssa.SSAOptions; +import com.ibm.wala.ssa.SSAPhiInstruction; +import com.ibm.wala.util.collections.HashMapFactory; +import com.ibm.wala.util.collections.Iterator2Iterable; +import com.ibm.wala.util.collections.ObjectArrayMapping; +import com.ibm.wala.util.collections.Pair; +import com.ibm.wala.util.intset.BitVectorIntSet; +import com.ibm.wala.util.intset.MutableIntSet; +import com.ibm.wala.util.intset.OrdinalSetMapping; +import com.ibm.wala.util.io.FileProvider; + +/** + * Helper class for identifying correlated read/write pairs. + * + * @author mschaefer + * + */ +public class CorrelationFinder { + private final static boolean TRACK_ESCAPES = true; + private final static boolean IGNORE_NUMERIC_INDICES = false; + + private final JavaScriptTranslatorFactory translatorFactory; + + @SuppressWarnings("unused") + private CorrelationSummary findCorrelatedAccesses(IMethod method, IR ir) { + AstMethod astMethod = (AstMethod)method; + DefUse du = new DefUse(ir); + OrdinalSetMapping instrIndices = new ObjectArrayMapping(ir.getInstructions()); + CorrelationSummary summary = new CorrelationSummary(method, instrIndices); + + // collect all dynamic property writes in the method + LinkedList puts = new LinkedList(); + for(SSAInstruction inst : Iterator2Iterable.make(ir.iterateNormalInstructions())) + if(inst instanceof AbstractReflectivePut) + puts.addFirst((AbstractReflectivePut)inst); + + instrs: for(SSAInstruction inst : Iterator2Iterable.make(ir.iterateNormalInstructions())) + if(inst instanceof AbstractReflectiveGet) { + AbstractReflectiveGet get = (AbstractReflectiveGet)inst; + int index = get.getMemberRef(); + + if(ir.getSymbolTable().isConstant(index)) + continue; + + if(ir.getSymbolTable().isParameter(index)) + continue; + + // try to determine what "index" is called at the source level + String indexName = getSourceLevelName(astMethod, index); + if(indexName == null) + continue instrs; + + // check that "index" is not accessed in an inner function + LexicalInformation lexicalInfo = astMethod.lexicalInfo(); + if (lexicalInfo.getExposedNames() != null) { + for(Pair n : lexicalInfo.getExposedNames()) { + if (n.fst.equals(indexName) && lexicalInfo.getScopingName().equals(n.snd)) + continue instrs; + } + } + + // if "index" is a numeric variable, it is not worth extracting + if(IGNORE_NUMERIC_INDICES && mustBeNumeric(ir, du, index)) + continue instrs; + + // set of SSA variables into which the value read by 'get' may flow + MutableIntSet reached = new BitVectorIntSet(); + reached.add(get.getDef()); + // saturate reached by following def-use chains through phi instructions and across function calls + LinkedList worklist = new LinkedList(); + MutableIntSet done = new BitVectorIntSet(); + worklist.add(get.getDef()); + while(!worklist.isEmpty()) { + Integer i = worklist.pop(); + done.add(i); + for(SSAInstruction inst2 : Iterator2Iterable.make(du.getUses(i))) { + if(inst2 instanceof SSAPhiInstruction) { + int def = inst2.getDef(); + if(reached.add(def) && !done.contains(def)) + worklist.add(def); + } else if(inst2 instanceof SSAAbstractInvokeInstruction) { + int def = inst2.getDef(); + if(reached.add(def) && !done.contains(def)) + worklist.add(def); + // if the index also flows into this invocation, record an escape correlation + if(TRACK_ESCAPES) { + for(int j=0;j worklist = new LinkedList(); + MutableIntSet done = new BitVectorIntSet(); + worklist.add(v); + while(!worklist.isEmpty()) { + int i = worklist.pop(); + done.add(i); + if(ir.getSymbolTable().isConstant(i) && ir.getSymbolTable().getConstantValue(i) instanceof Number) + continue; + SSAInstruction inst2 = du.getDef(i); + if(inst2 instanceof SSAPhiInstruction) { + for(int j=0;j summaries = findCorrelatedAccesses(url); + List> correlations = new ArrayList>(); + for(CorrelationSummary summary : summaries.values()) + correlations.addAll(summary.pp()); + + Collections.sort(correlations, new Comparator>() { + @SuppressWarnings("unchecked") + @Override + public int compare(Pair o1, Pair o2) { + return o1.fst.compareTo(o2.fst); + } + }); + int i = 0; + for(Pair p : correlations) + System.out.println((i++) + " -- " + p.fst + ": " + p.snd); + } + + public Map findCorrelatedAccesses(URL url) throws IOException, ClassHierarchyException { + JavaScriptLoader.addBootstrapFile(WebUtil.preamble); + Set script = WebUtil.extractScriptFromHTML(url); + Map summaries = findCorrelatedAccesses(script); + return summaries; + } + + public Map findCorrelatedAccesses(Set script) throws IOException, + ClassHierarchyException { + SourceModule[] scripts = script.toArray(new SourceModule[script.size()]); + WebPageLoaderFactory loaders = new WebPageLoaderFactory(translatorFactory); + CAstAnalysisScope scope = new CAstAnalysisScope(scripts, loaders, Collections.singleton(JavaScriptLoader.JS)); + IClassHierarchy cha = ClassHierarchy.make(scope, loaders, JavaScriptLoader.JS); + Util.checkForFrontEndErrors(cha); + IRFactory factory = AstIRFactory.makeDefaultFactory(); + + Map correlations = HashMapFactory.make(); + for(IClass klass : cha) { + for(IMethod method : klass.getAllMethods()) { + IR ir = factory.makeIR(method, Everywhere.EVERYWHERE, SSAOptions.defaultOptions()); + CorrelationSummary summary = findCorrelatedAccesses(method, ir); + if(!summary.getCorrelations().isEmpty()) + correlations.put(method, summary); + } + } + return correlations; + } + + @SuppressWarnings("unused") + private URL toUrl(String src) throws MalformedURLException { + // first try interpreting as local file name, if that doesn't work just assume it's a URL + try { + File f = FileProvider.getFileFromClassLoader(src, this.getClass().getClassLoader()); + URL url = f.toURI().toURL(); + return url; + } catch(FileNotFoundException fnfe) { + return new URL(src); + } + } + + public CorrelationFinder(JavaScriptTranslatorFactory translatorFactory) { + this.translatorFactory = translatorFactory; + } +} diff --git a/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/CorrelationSummary.java b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/CorrelationSummary.java new file mode 100644 index 000000000..f7b51dacd --- /dev/null +++ b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/CorrelationSummary.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * 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.ipa.callgraph.correlations; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.ibm.wala.cast.loader.AstMethod; +import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; +import com.ibm.wala.classLoader.IMethod; +import com.ibm.wala.ssa.SSAInstruction; +import com.ibm.wala.util.collections.HashSetFactory; +import com.ibm.wala.util.collections.Pair; +import com.ibm.wala.util.intset.OrdinalSetMapping; + +/** + * A utility class holding information about correlations identified by a {@link CorrelationFinder}. + * + * @author mschaefer + * + */ +public final class CorrelationSummary { + private final SSASourcePositionMap positions; + private final Set correlations = HashSetFactory.make(); + + public CorrelationSummary(IMethod method, OrdinalSetMapping instrIndices) { + positions = new SSASourcePositionMap((AstMethod)method, instrIndices); + } + + public void addCorrelation(Correlation correlation) { + correlations.add(correlation); + } + + public List> pp() { + List> res = new ArrayList>(); + for(Correlation correlation : correlations) { + res.add(Pair.make(correlation.getStartPosition(positions), correlation.pp(positions))); + } + return res; + } + + public Set getCorrelations() { + return correlations; + } + + public SSASourcePositionMap getPositions() { + return positions; + } +} \ No newline at end of file diff --git a/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/CorrelationVisitor.java b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/CorrelationVisitor.java new file mode 100644 index 000000000..f36ebecc3 --- /dev/null +++ b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/CorrelationVisitor.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * 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.ipa.callgraph.correlations; + +/** + * Visitor class for performing case analysis on {@link Correlation}s. + * + * @author mschaefer + * + * @param + */ +public interface CorrelationVisitor { + public T visitReadWriteCorrelation(ReadWriteCorrelation rwc); + public T visitEscapeCorrelation(EscapeCorrelation ec); +} diff --git a/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/EscapeCorrelation.java b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/EscapeCorrelation.java new file mode 100644 index 000000000..583dfac67 --- /dev/null +++ b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/EscapeCorrelation.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * 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.ipa.callgraph.correlations; + +import com.ibm.wala.cast.ir.ssa.AbstractReflectiveGet; +import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; +import com.ibm.wala.ssa.SSAAbstractInvokeInstruction; + +/** + * An escape correlation conservatively captures inter-procedural correlated pairs: for a dynamic property + * read r of the form e[p], if both the result of r and the value of p + * flow into a function call c, we consider r and c to be a correlated pair to account + * for the fact that the function called by c may perform a write of property p. + * + * @author mschaefer + * + */ +public class EscapeCorrelation extends Correlation { + private final AbstractReflectiveGet get; + private final SSAAbstractInvokeInstruction invoke; + + public EscapeCorrelation(AbstractReflectiveGet get, SSAAbstractInvokeInstruction invoke, String indexName) { + super(indexName); + this.get = get; + this.invoke = invoke; + } + + @Override + public Position getStartPosition(SSASourcePositionMap positions) { + return positions.getPosition(get); + } + + @Override + public Position getEndPosition(SSASourcePositionMap positions) { + return positions.getPosition(invoke); + } + + public int getNumberOfArguments() { + return invoke.getNumberOfParameters() - 2; // deduct one for the function object, one for the receiver + } + + @Override + public String pp(SSASourcePositionMap positions) { + return get + "@" + positions.getPosition(get) + " [" + getIndexName() + "] ->? " + invoke + "@" + positions.getPosition(invoke); + } + + @Override + public T accept(CorrelationVisitor visitor) { + return visitor.visitEscapeCorrelation(this); + } +} \ No newline at end of file diff --git a/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/ReadWriteCorrelation.java b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/ReadWriteCorrelation.java new file mode 100644 index 000000000..49bd39f30 --- /dev/null +++ b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/ReadWriteCorrelation.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * 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.ipa.callgraph.correlations; + +import com.ibm.wala.cast.ir.ssa.AbstractReflectiveGet; +import com.ibm.wala.cast.ir.ssa.AbstractReflectivePut; +import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; + +/** + * The most basic form of correlation: an intra-procedurally correlated pair of a dynamic property read + * and a dynamic property write. + * + * @author mschaefer + * + */ +public class ReadWriteCorrelation extends Correlation { + private final AbstractReflectiveGet get; + private final AbstractReflectivePut put; + + public ReadWriteCorrelation(AbstractReflectiveGet get, AbstractReflectivePut put, String indexName) { + super(indexName); + this.get = get; + this.put = put; + } + + @Override + public Position getStartPosition(SSASourcePositionMap positions) { + return positions.getPosition(get); + } + + @Override + public Position getEndPosition(SSASourcePositionMap positions) { + return positions.getPosition(put); + } + + @Override + public String pp(SSASourcePositionMap positions) { + return get + "@" + positions.getPosition(get) + " [" + getIndexName() + "]-> " + put + "@" + positions.getPosition(put); + } + + @Override + public T accept(CorrelationVisitor visitor) { + return visitor.visitReadWriteCorrelation(this); + } +} \ No newline at end of file diff --git a/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/SSASourcePositionMap.java b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/SSASourcePositionMap.java new file mode 100644 index 000000000..b373d5304 --- /dev/null +++ b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/SSASourcePositionMap.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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.ipa.callgraph.correlations; + +import com.ibm.wala.cast.loader.AstMethod; +import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; +import com.ibm.wala.ssa.SSAInstruction; +import com.ibm.wala.util.intset.OrdinalSetMapping; + +/** + * Utility class used by {@link CorrelationSummary} to map SSA instructions to source positions. + * + * @author mschaefer + * + */ +public class SSASourcePositionMap { + private final AstMethod method; + private final OrdinalSetMapping instrIndices; + + public SSASourcePositionMap(AstMethod method, OrdinalSetMapping instrIndices) { + this.method = method; + this.instrIndices = instrIndices; + } + + public Position getPosition(SSAInstruction inst) { + return method.getSourcePosition(instrIndices.getMappedIndex(inst)); + } +} \ No newline at end of file diff --git a/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/CAstRewriterExt.java b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/CAstRewriterExt.java new file mode 100644 index 000000000..4d14b51a8 --- /dev/null +++ b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/CAstRewriterExt.java @@ -0,0 +1,245 @@ +/****************************************************************************** + * 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.ipa.callgraph.correlations.extraction; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +import com.ibm.wala.cast.tree.CAst; +import com.ibm.wala.cast.tree.CAstControlFlowMap; +import com.ibm.wala.cast.tree.CAstEntity; +import com.ibm.wala.cast.tree.CAstNode; +import com.ibm.wala.cast.tree.CAstSourcePositionMap; +import com.ibm.wala.cast.tree.impl.CAstBasicRewriter.NoKey; +import com.ibm.wala.cast.tree.impl.CAstControlFlowRecorder; +import com.ibm.wala.cast.tree.impl.CAstRewriter; +import com.ibm.wala.util.collections.HashMapFactory; +import com.ibm.wala.util.collections.HashSetFactory; +import com.ibm.wala.util.collections.Pair; +import com.ibm.wala.util.debug.Assertions; + +/** + * Extension of {@link CAstRewriter} which allows adding or deleting control flow edges, and keeps track of + * the current entity. + * + * TODO: This class is an unholy mess. It should be restructured considerably. + * + * @author mschaefer + * + */ +public abstract class CAstRewriterExt extends CAstRewriter { + + /** + * A control flow edge to be added to the CFG. + * + * @author mschaefer + */ + protected static class Edge { + private CAstNode from; + private Object label; + private CAstNode to; + + public Edge(CAstNode from, Object label, CAstNode to) { + assert from != null; + assert to != null; + this.from = from; + this.label = label; + this.to = to; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime + from.hashCode(); + result = prime * result + ((label == null) ? 0 : label.hashCode()); + result = prime * result + to.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof Edge)) + return false; + Edge that = (Edge)obj; + return this.from.equals(that.from) && + (this.label == null ? that.label == null : this.label.equals(that.label)) && + this.to.equals(that.to); + } + } + + private final Map> extra_nodes = HashMapFactory.make(); + private final Map> extra_flow = HashMapFactory.make(); + private final Map> flow_to_delete = HashMapFactory.make(); + + // information about an entity to add to the AST + private static class Entity { + private final CAstNode anchor; + private final CAstEntity me; + public Entity(CAstNode anchor, CAstEntity me) { + assert me != null; + this.anchor = anchor; + this.me = me; + } + @Override + public int hashCode() { + final int prime = 31; + int result = prime + ((anchor == null) ? 0 : anchor.hashCode()); + return prime * result + me.hashCode(); + } + } + private final HashSet entities_to_add = HashSetFactory.make(); + private final Stack entities = new Stack(); + + public CAstNode addNode(CAstNode node, CAstControlFlowMap flow) { + Set nodes = extra_nodes.get(flow); + if(nodes == null) + extra_nodes.put(flow, nodes = HashSetFactory.make()); + nodes.add(node); + return node; + } + + public CAstNode addFlow(CAstNode node, Object label, CAstNode target, CAstControlFlowMap flow) { + Set edges = extra_flow.get(flow); + if(edges == null) + extra_flow.put(flow, edges = HashSetFactory.make()); + edges.add(new Edge(node, label, target)); + return node; + } + + public void deleteFlow(CAstNode node, CAstEntity entity) { + CAstControlFlowMap flow = entity.getControlFlow(); + Set tmp = flow_to_delete.get(flow); + if(tmp == null) + flow_to_delete.put(flow, tmp = HashSetFactory.make()); + tmp.add(node); + } + + protected boolean isFlowDeleted(CAstNode node, CAstEntity entity) { + CAstControlFlowMap flow = entity.getControlFlow(); + return flow_to_delete.containsKey(flow) && flow_to_delete.get(flow).contains(node); + } + + public CAstEntity getCurrentEntity() { + return entities.peek(); + } + + public Iterable getEnclosingEntities() { + return entities; + } + + public void addEntity(CAstNode anchor, CAstEntity entity) { + entities_to_add.add(new Entity(anchor, entity)); + } + + @Override + protected Map> copyChildren(CAstNode root, Map,CAstNode> nodeMap, Map> children) { + Map> map = super.copyChildren(root, nodeMap, children); + // extend with local mapping information + for(Iterator es = entities_to_add.iterator(); es.hasNext(); ) { + Entity e = es.next(); + boolean relevant = NodePos.inSubtree(e.anchor, nodeMap.get(Pair.make(root, null))) || NodePos.inSubtree(e.anchor, root); + if(relevant) { + Collection c = map.get(e.anchor); + if(c == null) + map.put(e.anchor, c = HashSetFactory.make()); + c.add(e.me); + es.remove(); + } + } + return map; + } + + @Override + protected CAstNode flowOutTo(Map, CAstNode> nodeMap, + CAstNode oldSource, Object label, CAstNode oldTarget, + CAstControlFlowMap orig, CAstSourcePositionMap src) { + if(oldTarget == CAstControlFlowMap.EXCEPTION_TO_EXIT) + return oldTarget; + Assertions.UNREACHABLE(); + return super.flowOutTo(nodeMap, oldSource, label, oldTarget, orig, src); + } + + @Override + protected CAstControlFlowMap copyFlow(Map,CAstNode> nodeMap, CAstControlFlowMap orig, CAstSourcePositionMap newSrc) { + Map,CAstNode> nodeMapCopy = HashMapFactory.make(nodeMap); + // delete flow if necessary + // TODO: this is bad; what if one of the deleted nodes occurs as a cflow target? + if(flow_to_delete.containsKey(orig)) { + for(CAstNode node : flow_to_delete.get(orig)) { + nodeMapCopy.remove(Pair.make(node, null)); + } + //flow_to_delete.remove(orig); + } + CAstControlFlowRecorder flow = (CAstControlFlowRecorder)super.copyFlow(nodeMapCopy, orig, newSrc); + // extend with local flow information + if(extra_nodes.containsKey(orig)) { + for(CAstNode nd : extra_nodes.get(orig)) + flow.map(nd, nd); + } + if(extra_flow.containsKey(orig)) { + for(Edge e : extra_flow.get(orig)) { + CAstNode from = e.from; + Object label = e.label; + CAstNode to = e.to; + if(nodeMap.containsKey(Pair.make(from, null))) + from = nodeMap.get(Pair.make(from, null)); + if(nodeMap.containsKey(Pair.make(to, null))) + to = nodeMap.get(Pair.make(to, null)); + if(!flow.isMapped(from)) + flow.map(from, from); + if(!flow.isMapped(to)) + flow.map(to, to); + flow.add(from, to, label); + } + /* + * Here, we would like to say extra_flow.remove(orig) to get rid of the extra control flow + * information, but that would not be correct: a single old cfg may be carved up into several + * new ones, each of which needs to be extended with the appropriate extra flow from the old cfg. + * + * Unfortunately, we now end up extending _every_ new cfg with _all_ the extra flow from the old + * cfg, which doesn't sound right either. + */ + } + return flow; + } + + Map rewrite_cache = HashMapFactory.make(); + @Override + public CAstEntity rewrite(CAstEntity root) { + // avoid rewriting the same entity more than once + // TODO: figure out why this happens in the first place + if(rewrite_cache.containsKey(root)) { + return rewrite_cache.get(root); + } else { + entities.push(root); + enterEntity(root); + CAstEntity entity = super.rewrite(root); + rewrite_cache.put(root, entity); + leaveEntity(root); + entities.pop(); + return entity; + } + } + + protected void enterEntity(CAstEntity entity) {} + protected void leaveEntity(CAstEntity entity) {} + + public CAstRewriterExt(CAst Ast, boolean recursive, NodePos rootContext) { + super(Ast, recursive, rootContext); + } + +} \ No newline at end of file diff --git a/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/ChildPos.java b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/ChildPos.java new file mode 100644 index 000000000..8f82005be --- /dev/null +++ b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/ChildPos.java @@ -0,0 +1,92 @@ +/****************************************************************************** + * 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.ipa.callgraph.correlations.extraction; + +import com.ibm.wala.cast.tree.CAstNode; + +/** + * A {@link NodePos} for a non-root node; includes information about the parent node, the child index, and + * the position of the parent node. + * + * @author mschaefer + * + */ +public class ChildPos extends NodePos { + private CAstNode parent; + private int index; + private NodePos parent_pos; + + public ChildPos(CAstNode parent, int index, NodePos parent_pos) { + this.parent = parent; + this.index = index; + this.parent_pos = parent_pos; + } + + public CAstNode getParent() { + return parent; + } + + public int getIndex() { + return index; + } + + public NodePos getParentPos() { + return parent_pos; + } + + public CAstNode getChild() { + return parent.getChild(index); + } + + public ChildPos getChildPos(int index) { + return new ChildPos(this.getChild(), index, this); + } + + @Override + public A accept(PosSwitch ps) { + return ps.caseChildPos(this); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + index; + result = prime * result + ((parent == null) ? 0 : parent.hashCode()); + result = prime * result + ((parent_pos == null) ? 0 : parent_pos.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ChildPos other = (ChildPos) obj; + if (index != other.index) + return false; + if (parent == null) { + if (other.parent != null) + return false; + } else if (!parent.equals(other.parent)) + return false; + if (parent_pos == null) { + if (other.parent_pos != null) + return false; + } else if (!parent_pos.equals(other.parent_pos)) + return false; + return true; + } +} diff --git a/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/ClosureExtractor.java b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/ClosureExtractor.java new file mode 100644 index 000000000..a41fba314 --- /dev/null +++ b/com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/ClosureExtractor.java @@ -0,0 +1,727 @@ +/****************************************************************************** + * 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.ipa.callgraph.correlations.extraction; + +import static com.ibm.wala.cast.tree.CAstNode.ASSIGN; +import static com.ibm.wala.cast.tree.CAstNode.BINARY_EXPR; +import static com.ibm.wala.cast.tree.CAstNode.BLOCK_STMT; +import static com.ibm.wala.cast.tree.CAstNode.BREAK; +import static com.ibm.wala.cast.tree.CAstNode.CALL; +import static com.ibm.wala.cast.tree.CAstNode.CONSTANT; +import static com.ibm.wala.cast.tree.CAstNode.CONTINUE; +import static com.ibm.wala.cast.tree.CAstNode.EMPTY; +import static com.ibm.wala.cast.tree.CAstNode.FUNCTION_EXPR; +import static com.ibm.wala.cast.tree.CAstNode.FUNCTION_STMT; +import static com.ibm.wala.cast.tree.CAstNode.GOTO; +import static com.ibm.wala.cast.tree.CAstNode.IFGOTO; +import static com.ibm.wala.cast.tree.CAstNode.LABEL_STMT; +import static com.ibm.wala.cast.tree.CAstNode.LOOP; +import static com.ibm.wala.cast.tree.CAstNode.OBJECT_LITERAL; +import static com.ibm.wala.cast.tree.CAstNode.OBJECT_REF; +import static com.ibm.wala.cast.tree.CAstNode.OPERATOR; +import static com.ibm.wala.cast.tree.CAstNode.RETURN; +import static com.ibm.wala.cast.tree.CAstNode.SWITCH; +import static com.ibm.wala.cast.tree.CAstNode.THROW; +import static com.ibm.wala.cast.tree.CAstNode.TRY; +import static com.ibm.wala.cast.tree.CAstNode.VAR; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.ibm.wala.cast.js.types.JavaScriptTypes; +import com.ibm.wala.cast.tree.CAst; +import com.ibm.wala.cast.tree.CAstControlFlowMap; +import com.ibm.wala.cast.tree.CAstEntity; +import com.ibm.wala.cast.tree.CAstNode; +import com.ibm.wala.cast.tree.CAstNodeTypeMap; +import com.ibm.wala.cast.tree.CAstSourcePositionMap; +import com.ibm.wala.cast.tree.impl.CAstBasicRewriter.NoKey; +import com.ibm.wala.cast.tree.impl.CAstOperator; +import com.ibm.wala.util.collections.HashMapFactory; +import com.ibm.wala.util.collections.HashSetFactory; +import com.ibm.wala.util.collections.Pair; +import com.ibm.wala.util.debug.UnimplementedError; + +/** + * A CAst rewriter for extracting bits of code into one-shot closures. What to extract is determined + * by an {@link ExtractionPolicy}. + * + *

For instance, a {@link ForInBodyExtractionPolicy} extracts the body of every for-in loop in + * the program, whereas a {@link CorrelatedPairExtractionPolicy} extracts pieces of code containing + * correlated property reads and writes of the same property.

+ * + *

As an example, consider the following function:

+ *
+ *   function extend(dest, src) {
+ *     for(var p in src)
+ *       dest[p] = src[p];
+ *   }
+ * 
+ *

Under both {@link ForInBodyExtractionPolicy} and {@link CorrelatedPairExtractionPolicy}, this + * should be transformed into

+ *
+ *   function extend(dest, src) {
+ *     for(var p in src)
+ *       (function _forin_body_0(p) {
+ *         dest[p] = src[p];
+ *       })(p);
+ *   }
+ * 
+ * + *

There are four issues to be considered here.

+ *
    + *
  • References to this: + *

    If the code to extract contains references to this, these references have to be + * rewritten; otherwise they would refer to the global object in the transformed code.

    + *

    We do this by giving the extracted function an extra parameter thi$, and rewriting + * this to thi$ within the extracted code.

    + *

    For instance,

    + *
    + *   Object.prototype.extend = function(src) {
    + *     for(var p in src)
    + *       this[p] = src[p];
    + *   }
    + *   
    + *

    becomes

    + *
    + *   Object.prototype.extend = function(src) {
    + *     for(var p in src)
    + *       (function _forin_body_0(p, thi$) {
    + *         thi$[p] = src[p];
    + *       })(p, this);
    + *   }
    + *   
    + *
  • + *
  • Local variable declarations: + *

    Local variable declarations inside the extracted code have to be hoisted to the enclosing function; + * otherwise they would become local variables of the extracted function instead.

    + *

    This is already taken care of by the translation from Rhino's AST to CAst.

    + *
  • + *
  • break,continue,return: + *

    A break or continue statement within the extracted loop body that refers + * to the loop itself or an enclosing loop would become invalid in the transformed code. A return + * statement would no longer return from the enclosing function, but instead from the extracted function.

    + *

    We transform all three statements into return statements returning an object literal with a + * property type indicating whether this is a 'goto' (i.e., break or return) + * or a 'return'. In the former case, the 'target' property contains an integer identifying the jump target; in + * the latter case, the 'value' property contains the value to return.

    + *

    The return value of the extracted function is then examined to determine whether it completed normally + * (i.e., returned undefined), or whether it returned an object indicating special control flow.

    + *

    For example, consider this code from MooTools:

    + *
    + *   for(var style in Element.ShortStyles) {
    + *     if(property != style)
    + *       continue;
    + *     for(var s in Element.ShortStyles[style])
    + *       result.push(this.getStyle(s));
    + *     return result.join(' ');
    + *   }
    + *   
    + *

    Under {@link ForInBodyExtractionPolicy}, this is transformed into

    + *
    + *   for(var style in Element.ShortStyles) {
    + *     var s;
    + *     re$ = (function _forin_body_0(style, thi$) {
    + *       if(property != style)
    + *         return { type: 'goto', target: 1 };
    + *       for(s in Element.ShortStyles[style]) {
    + *         (function _forin_body_2(s) {
    + *           result.push(thi$.getStyle(s));
    + *         })(s);
    + *       }
    + *       return { type: 'return', value: result.join(' ') };
    + *     })(style, this);
    + *     if(re$) {
    + *       if(re$.type == 'return')
    + *         return re$.value;
    + *       if(re$.type == 'goto') {
    + *         if(re$.target == 1)
    + *           continue;
    + *       }
    + *     }
    + *   }
    + *   
    + *

    Note that at the CAst level, break and continue are represented as goto + * statements, which simplifies the translation somewhat. The numerical encoding of jump targets does not matter + * as long as the extracted function and the fixup code agree on which number represents which label.

    + *
  • + *
  • Assignment to loop variable: + *

    The loop body may assign to the loop variable. If the variable is referenced after the loop, this assignment + * needs to be propagated back to the enclosing function in the extracted code.

    + *

    TODO: This is not handled at the moment.

    + *
  • + *
+ * + *

Finally, note that exceptions do not need to be handled specially.

+ * + * @author mschaefer + * + */ +public class ClosureExtractor extends CAstRewriterExt { + private LinkedList policies = new LinkedList(); + private final ExtractionPolicyFactory policyFactory; + + // names for extracted functions are built from this string with a number appended + private static final String EXTRACTED_FUN_BASENAME = "_forin_body_"; + + private NodeLabeller labeller = new NodeLabeller(); + + public ClosureExtractor(CAst Ast, ExtractionPolicyFactory policyFactory) { + super(Ast, true, new RootPos()); + this.policyFactory = policyFactory; + } + + @Override + protected void enterEntity(CAstEntity entity) { + policies.push(policyFactory.createPolicy(entity)); + } + + @Override + protected void leaveEntity(CAstEntity entity) { + policies.pop(); + } + + @Override + protected CAstNode copyNodes(CAstNode root, NodePos context, Map, CAstNode> nodeMap) { + switch(root.getKind()) { + case OPERATOR: + return root; + case CONSTANT: + return copyConstant(root, context, nodeMap); + case BLOCK_STMT: + return copyBlock(root, context, nodeMap); + case RETURN: + return copyReturn(root, context, nodeMap); + case VAR: + return copyVar(root, context, nodeMap); + case GOTO: + return copyGoto(root, context, nodeMap); + default: + return copyNode(root, context, nodeMap); + } + } + + /* Constants are not affected by the rewriting, they are just copied. */ + private CAstNode copyConstant(CAstNode root, NodePos context, Map, CAstNode> nodeMap) { + CAstNode newNode = Ast.makeConstant(root.getValue()); + nodeMap.put(Pair.make(root, context.key()), newNode); + return newNode; + } + + /* Ask the policy whether it wants anything extracted from this block; otherwise the node is simply copied. */ + private CAstNode copyBlock(CAstNode root, NodePos context, Map, CAstNode> nodeMap) { + List regions = policies.getFirst().extract(root); + if(regions == null) { + return copyNode(root, context, nodeMap); + } else { + ArrayList copied_children = new ArrayList(); + int next_child = 0; + // code in between regions is handled by invoking copyNodes, the regions themselves by extractRegion + for(ExtractionRegion region : regions) { + for(;next_child, CAstNode> nodeMap) { + /* + * If this node is a "this" reference, the outermost enclosing extracted function needs to pass in + * the value of "this" as a parameter. + * + * NB: This has to be done by the _outermost_ function. If it were done by an inner function instead, + * the outermost one may not pass in the value of "this" at all, so the inner one would + * get the wrong value. + */ + if(root.getChild(0).getValue().equals("this")) { + ExtractionPos epos = ExtractionPos.getOutermostEnclosingExtractionPos(context); + if(epos != null) { + epos.addThis(); + CAstNode newNode = makeVarRef(epos.getThisParmName()); + addExnFlow(newNode, JavaScriptTypes.ReferenceError, getCurrentEntity(), context); + nodeMap.put(Pair.make(root, context.key()), newNode); + return newNode; + } else { + return copyNode(root, context, nodeMap); + } + } else { + return copyNode(root, context, nodeMap); + } + } + + /* + * 'break' and 'continue' statements are both encoded as GOTO. If they refer to a target outside the innermost + * enclosing extracted function body, they are rewritten into a 'return' statement. + */ + private CAstNode copyGoto(CAstNode root, NodePos context, Map, CAstNode> nodeMap) { + CAstNode target = getCurrentEntity().getControlFlow().getTarget(root, null); + ExtractionPos epos = ExtractionPos.getEnclosingExtractionPos(context); + if(epos != null && !NodePos.inSubtree(target, epos.getParent())) { + epos.addGotoTarget(target); + int label = labeller.addNode(target); + // return { type: 'goto', target: