From 8b547b4812e080de1778e33fef838e927990c28e Mon Sep 17 00:00:00 2001 From: msridhar1 Date: Fri, 6 Jan 2012 21:52:26 +0000 Subject: [PATCH] Split up ForInLoopRewriter in preparation for merging changes back into open-source WALA. The extraction code is now in com.ibm.wala.cast.js, with some more general stuff moved to com.ibm.wala.cast and com.ibm.wala.util. The tests are in com.ibm.wala.cast.js.test and com.ibm.wala.cast.js.rhino.test. ForInLoopRewriter itself only contains the framework specific tests and the copies of the frameworks themselves. git-svn-id: https://wala.svn.sourceforge.net/svnroot/wala/trunk@4418 f5eafffb-2e1d-0410-98e4-8ec43c5233c4 --- .../TestCorrelatedPairExtractionRhino.java | 34 + .../js/test/TestForInBodyExtractionRhino.java | 29 + .../META-INF/MANIFEST.MF | 3 +- .../com/ibm/wala/cast/js/test/CAstDumper.java | 162 ++++ .../js/test/TestCorrelatedPairExtraction.java | 485 +++++++++++ .../cast/js/test/TestForInBodyExtraction.java | 762 ++++++++++++++++++ .../com/ibm/wala/cast/js/test/Util.java | 2 +- com.ibm.wala.cast.js/META-INF/MANIFEST.MF | 3 + .../callgraph/correlations/Correlation.java | 38 + .../correlations/CorrelationFinder.java | 272 +++++++ .../correlations/CorrelationSummary.java | 59 ++ .../correlations/CorrelationVisitor.java | 24 + .../correlations/EscapeCorrelation.java | 60 ++ .../correlations/ReadWriteCorrelation.java | 54 ++ .../correlations/SSASourcePositionMap.java | 37 + .../extraction/CAstRewriterExt.java | 245 ++++++ .../correlations/extraction/ChildPos.java | 92 +++ .../extraction/ClosureExtractor.java | 727 +++++++++++++++++ .../CorrelatedPairExtractionPolicy.java | 258 ++++++ .../CorrelatedPairExtractorFactory.java | 52 ++ .../extraction/ExtractedFunction.java | 183 +++++ .../extraction/ExtractionPolicy.java | 26 + .../extraction/ExtractionPolicyFactory.java | 18 + .../extraction/ExtractionPos.java | 216 +++++ .../extraction/ExtractionRegion.java | 52 ++ .../extraction/ForInBodyExtractionPolicy.java | 71 ++ .../correlations/extraction/NodeLabeller.java | 58 ++ .../correlations/extraction/NodePos.java | 51 ++ .../correlations/extraction/PosSwitch.java | 18 + .../correlations/extraction/RootPos.java | 29 + .../extraction/TwoLevelExtractionRegion.java | 32 + .../com/ibm/wala/cast/js/util/Util.java | 41 + com.ibm.wala.cast/META-INF/MANIFEST.MF | 1 + .../ibm/wala/cast/tree/pattern/AnyNode.java | 26 + .../wala/cast/tree/pattern/NodeOfKind.java | 45 ++ .../wala/cast/tree/pattern/NodePattern.java | 24 + .../wala/cast/tree/pattern/SomeConstant.java | 40 + .../wala/cast/tree/pattern/SubtreeOfKind.java | 31 + .../src/com/ibm/wala/util/io/FileUtil.java | 12 + 39 files changed, 4370 insertions(+), 2 deletions(-) create mode 100644 com.ibm.wala.cast.js.rhino.test/harness-src/com/ibm/wala/cast/js/test/TestCorrelatedPairExtractionRhino.java create mode 100644 com.ibm.wala.cast.js.rhino.test/harness-src/com/ibm/wala/cast/js/test/TestForInBodyExtractionRhino.java create mode 100644 com.ibm.wala.cast.js.test/harness-src/com/ibm/wala/cast/js/test/CAstDumper.java create mode 100644 com.ibm.wala.cast.js.test/harness-src/com/ibm/wala/cast/js/test/TestCorrelatedPairExtraction.java create mode 100644 com.ibm.wala.cast.js.test/harness-src/com/ibm/wala/cast/js/test/TestForInBodyExtraction.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/Correlation.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/CorrelationFinder.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/CorrelationSummary.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/CorrelationVisitor.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/EscapeCorrelation.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/ReadWriteCorrelation.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/SSASourcePositionMap.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/CAstRewriterExt.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/ChildPos.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/ClosureExtractor.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/CorrelatedPairExtractionPolicy.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/CorrelatedPairExtractorFactory.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/ExtractedFunction.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/ExtractionPolicy.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/ExtractionPolicyFactory.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/ExtractionPos.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/ExtractionRegion.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/ForInBodyExtractionPolicy.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/NodeLabeller.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/NodePos.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/PosSwitch.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/RootPos.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/ipa/callgraph/correlations/extraction/TwoLevelExtractionRegion.java create mode 100644 com.ibm.wala.cast.js/source/com/ibm/wala/cast/js/util/Util.java create mode 100644 com.ibm.wala.cast/source/java/com/ibm/wala/cast/tree/pattern/AnyNode.java create mode 100644 com.ibm.wala.cast/source/java/com/ibm/wala/cast/tree/pattern/NodeOfKind.java create mode 100644 com.ibm.wala.cast/source/java/com/ibm/wala/cast/tree/pattern/NodePattern.java create mode 100644 com.ibm.wala.cast/source/java/com/ibm/wala/cast/tree/pattern/SomeConstant.java create mode 100644 com.ibm.wala.cast/source/java/com/ibm/wala/cast/tree/pattern/SubtreeOfKind.java 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: