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
This commit is contained in:
msridhar1 2012-01-06 21:52:26 +00:00
parent a2c146b2d8
commit 8b547b4812
39 changed files with 4370 additions and 2 deletions

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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.
*
* <p>Similar to {@link CAstPrinter}, but additionally prints control flow information.</p>
*
* <p>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.</p>
*
* @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<indent;++i)
buf.append(' ');
return buf.toString();
}
public String dump(CAstEntity entity) {
StringBuilder buf = new StringBuilder();
dump(entity, 0, buf);
return buf.toString();
}
private void dump(CAstEntity entity, int indent, StringBuilder buf) {
Collection<CAstEntity> 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;i<entity.getArgumentCount();++i) {
if(i>0)
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<CAstEntity> dumpScopedEntities(CAstEntity entity, int indent, StringBuilder buf) {
ArrayList<CAstEntity> scopedEntities = new ArrayList<CAstEntity>();
Map<CAstEntity, CAstNode> m = HashMapFactory.make();
for(Entry<CAstNode, Collection<CAstEntity>> e : entity.getAllScopedEntities().entrySet())
for(CAstEntity scopedEntity : e.getValue()) {
scopedEntities.add(scopedEntity);
m.put(scopedEntity, e.getKey());
}
Collections.sort(scopedEntities, new Comparator<CAstEntity>() {
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<Object> 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<node.getChildCount();++i) {
CAstNode child = node.getChild(i);
// omit empty statements in a block
if(node.getKind() == CAstNode.BLOCK_STMT && child != null && child.getKind() == CAstNode.EMPTY)
continue;
dump(child, indent+2, buf, cfg);
}
}
}
}

View File

@ -0,0 +1,485 @@
/*******************************************************************************
* Copyright (c) 2011 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package com.ibm.wala.cast.js.test;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import com.ibm.wala.cast.js.ipa.callgraph.correlations.CorrelationFinder;
import com.ibm.wala.cast.js.ipa.callgraph.correlations.CorrelationSummary;
import com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction.ClosureExtractor;
import com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction.CorrelatedPairExtractionPolicy;
import com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction.ExtractionPolicy;
import com.ibm.wala.cast.js.ipa.callgraph.correlations.extraction.ExtractionPolicyFactory;
import com.ibm.wala.cast.tree.CAstEntity;
import com.ibm.wala.cast.tree.impl.CAstImpl;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.classLoader.SourceFileModule;
import com.ibm.wala.classLoader.SourceModule;
import com.ibm.wala.classLoader.SourceURLModule;
import com.ibm.wala.ipa.cha.ClassHierarchyException;
import com.ibm.wala.util.io.FileUtil;
public abstract class TestCorrelatedPairExtraction {
// set to "true" to use JUnit's assertEquals to check whether a test case passed;
// if it is set to "false", expected and actual output are instead written to files expected.dump and actual.dump
// this is useful if the outputs are too big/too different for Eclipse's diff view to handle
private static final boolean ASSERT_EQUALS = true;
public void testRewriter(String in, String out) {
testRewriter(null, in, out);
}
public void testRewriter(String testName, String in, String out) {
File tmp = null;
try {
tmp = File.createTempFile("test", ".js");
FileUtil.writeFile(tmp, in);
final Map<IMethod, CorrelationSummary> summaries = makeCorrelationFinder().findCorrelatedAccesses(Collections.singleton(new SourceURLModule(tmp.toURI().toURL())));
CAstImpl ast = new CAstImpl();
CAstEntity inEntity = parseJS(tmp, ast);
ExtractionPolicyFactory policyFactory = new ExtractionPolicyFactory() {
@Override
public ExtractionPolicy createPolicy(CAstEntity entity) {
CorrelatedPairExtractionPolicy policy = CorrelatedPairExtractionPolicy.make(entity, summaries);
Assert.assertNotNull(policy);
return policy;
}
};
String actual = new CAstDumper().dump(new ClosureExtractor(ast, policyFactory).rewrite(inEntity));
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" +
"}");
}
}

View File

@ -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(_) {}");
}
}

View File

@ -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<Entrypoint> roots = makeScriptRoots(cha);
JSAnalysisOptions options = makeOptions(scope, cha, roots);
options.setHandleCallApply(handleCallApply);

View File

@ -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,

View File

@ -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> T accept(CorrelationVisitor<T> visitor);
}

View File

@ -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<SSAInstruction> instrIndices = new ObjectArrayMapping<SSAInstruction>(ir.getInstructions());
CorrelationSummary summary = new CorrelationSummary(method, instrIndices);
// collect all dynamic property writes in the method
LinkedList<AbstractReflectivePut> puts = new LinkedList<AbstractReflectivePut>();
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<String,String> 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<Integer> worklist = new LinkedList<Integer>();
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<inst2.getNumberOfUses();++j) {
if(inst2.getUse(j) == index) {
summary.addCorrelation(new EscapeCorrelation(get, (SSAAbstractInvokeInstruction)inst2, indexName));
break;
}
}
}
}
}
}
// now find property writes with the same index whose RHS is in 'reached'
for(AbstractReflectivePut put : puts)
if(put.getMemberRef() == index && reached.contains(put.getValue()))
summary.addCorrelation(new ReadWriteCorrelation(get, put, indexName));
}
return summary;
}
// tries to determine which source level variable an SSA variable corresponds to
// if it does not correspond to any variable, or to more than one, null is returned
private String getSourceLevelName(AstMethod astMethod, int v) {
String indexName = null;
for(String candidateName : astMethod.debugInfo().getSourceNamesForValues()[v]) {
if(indexName != null) {
indexName = null;
break;
}
if(!candidateName.contains(" ")) // ignore internal names
indexName = candidateName;
}
return indexName;
}
// checks whether the given SSA variable must always be assigned a numeric value
private boolean mustBeNumeric(IR ir, DefUse du, int v) {
LinkedList<Integer> worklist = new LinkedList<Integer>();
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<inst2.getNumberOfUses();++j) {
int use = inst2.getUse(j);
if(!done.contains(use))
worklist.add(use);
}
} else if(inst2 instanceof SSABinaryOpInstruction) {
IOperator operator = ((SSABinaryOpInstruction)inst2).getOperator();
// if it is an ADD, both operands have to be provably numeric
if(operator == IBinaryOpInstruction.Operator.ADD) {
for(int j=0;j<inst2.getNumberOfUses();++j) {
int use = inst2.getUse(j);
if(!done.contains(use))
worklist.add(use);
}
}
// otherwise the result is definitely numeric
} else {
// found a definition that doesn't look numeric
return false;
}
}
// found no non-numeric definitions
return true;
}
@SuppressWarnings("unused")
private void printCorrelatedAccesses(URL url) throws IOException, ClassHierarchyException {
Map<IMethod, CorrelationSummary> summaries = findCorrelatedAccesses(url);
List<Pair<Position, String>> correlations = new ArrayList<Pair<Position,String>>();
for(CorrelationSummary summary : summaries.values())
correlations.addAll(summary.pp());
Collections.sort(correlations, new Comparator<Pair<Position, String>>() {
@SuppressWarnings("unchecked")
@Override
public int compare(Pair<Position, String> o1, Pair<Position, String> o2) {
return o1.fst.compareTo(o2.fst);
}
});
int i = 0;
for(Pair<Position, String> p : correlations)
System.out.println((i++) + " -- " + p.fst + ": " + p.snd);
}
public Map<IMethod, CorrelationSummary> findCorrelatedAccesses(URL url) throws IOException, ClassHierarchyException {
JavaScriptLoader.addBootstrapFile(WebUtil.preamble);
Set<? extends SourceModule> script = WebUtil.extractScriptFromHTML(url);
Map<IMethod, CorrelationSummary> summaries = findCorrelatedAccesses(script);
return summaries;
}
public Map<IMethod, CorrelationSummary> findCorrelatedAccesses(Set<? extends SourceModule> 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<IMethod> factory = AstIRFactory.makeDefaultFactory();
Map<IMethod, CorrelationSummary> 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;
}
}

View File

@ -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<Correlation> correlations = HashSetFactory.make();
public CorrelationSummary(IMethod method, OrdinalSetMapping<SSAInstruction> instrIndices) {
positions = new SSASourcePositionMap((AstMethod)method, instrIndices);
}
public void addCorrelation(Correlation correlation) {
correlations.add(correlation);
}
public List<Pair<Position, String>> pp() {
List<Pair<Position, String>> res = new ArrayList<Pair<Position, String>>();
for(Correlation correlation : correlations) {
res.add(Pair.make(correlation.getStartPosition(positions), correlation.pp(positions)));
}
return res;
}
public Set<Correlation> getCorrelations() {
return correlations;
}
public SSASourcePositionMap getPositions() {
return positions;
}
}

View File

@ -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 <T>
*/
public interface CorrelationVisitor<T> {
public T visitReadWriteCorrelation(ReadWriteCorrelation rwc);
public T visitEscapeCorrelation(EscapeCorrelation ec);
}

View File

@ -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 <i>r</i> of the form <code>e[p]</code>, if both the result of <i>r</i> and the value of <code>p</code>
* flow into a function call <i>c</i>, we consider <i>r</i> and <i>c</i> to be a correlated pair to account
* for the fact that the function called by <i>c</i> may perform a write of property <code>p</code>.
*
* @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> T accept(CorrelationVisitor<T> visitor) {
return visitor.visitEscapeCorrelation(this);
}
}

View File

@ -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> T accept(CorrelationVisitor<T> visitor) {
return visitor.visitReadWriteCorrelation(this);
}
}

View File

@ -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<SSAInstruction> instrIndices;
public SSASourcePositionMap(AstMethod method, OrdinalSetMapping<SSAInstruction> instrIndices) {
this.method = method;
this.instrIndices = instrIndices;
}
public Position getPosition(SSAInstruction inst) {
return method.getSourcePosition(instrIndices.getMappedIndex(inst));
}
}

View File

@ -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<NodePos, NoKey> {
/**
* 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<CAstControlFlowMap, Set<CAstNode>> extra_nodes = HashMapFactory.make();
private final Map<CAstControlFlowMap, Set<Edge>> extra_flow = HashMapFactory.make();
private final Map<CAstControlFlowMap, Set<CAstNode>> 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<Entity> entities_to_add = HashSetFactory.make();
private final Stack<CAstEntity> entities = new Stack<CAstEntity>();
public CAstNode addNode(CAstNode node, CAstControlFlowMap flow) {
Set<CAstNode> 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<Edge> 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<CAstNode> 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<CAstEntity> getEnclosingEntities() {
return entities;
}
public void addEntity(CAstNode anchor, CAstEntity entity) {
entities_to_add.add(new Entity(anchor, entity));
}
@Override
protected Map<CAstNode,Collection<CAstEntity>> copyChildren(CAstNode root, Map<Pair<CAstNode,NoKey>,CAstNode> nodeMap, Map<CAstNode,Collection<CAstEntity>> children) {
Map<CAstNode, Collection<CAstEntity>> map = super.copyChildren(root, nodeMap, children);
// extend with local mapping information
for(Iterator<Entity> 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<CAstEntity> 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<Pair<CAstNode, NoKey>, 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<Pair<CAstNode,NoKey>,CAstNode> nodeMap, CAstControlFlowMap orig, CAstSourcePositionMap newSrc) {
Map<Pair<CAstNode,NoKey>,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<CAstEntity, CAstEntity> 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);
}
}

View File

@ -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> A accept(PosSwitch<A> 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;
}
}

View File

@ -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}.
*
* <p>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.</p>
*
* <p>As an example, consider the following function:</p>
* <pre>
* function extend(dest, src) {
* for(var p in src)
* dest[p] = src[p];
* }
* </pre>
* <p>Under both {@link ForInBodyExtractionPolicy} and {@link CorrelatedPairExtractionPolicy}, this
* should be transformed into</p>
* <pre>
* function extend(dest, src) {
* for(var p in src)
* (function _forin_body_0(p) {
* dest[p] = src[p];
* })(p);
* }
* </pre>
*
* <p>There are four issues to be considered here.</p>
* <ul>
* <li><b>References to <code>this</code></b>:
* <p>If the code to extract contains references to <code>this</code>, these references have to be
* rewritten; otherwise they would refer to the global object in the transformed code.</p>
* <p>We do this by giving the extracted function an extra parameter <code>thi$</code>, and rewriting
* <code>this</code> to <code>thi$</code> within the extracted code.</p>
* <p>For instance,</p>
* <pre>
* Object.prototype.extend = function(src) {
* for(var p in src)
* this[p] = src[p];
* }
* </pre>
* <p>becomes</p>
* <pre>
* Object.prototype.extend = function(src) {
* for(var p in src)
* (function _forin_body_0(p, thi$) {
* thi$[p] = src[p];
* })(p, this);
* }
* </pre>
* </li>
* <li><b>Local variable declarations</b>:
* <p>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.</p>
* <p>This is already taken care of by the translation from Rhino's AST to CAst.</p>
* </li>
* <li><b><code>break</code>,<code>continue</code>,<code>return</code></b>:
* <p>A <code>break</code> or <code>continue</code> statement within the extracted loop body that refers
* to the loop itself or an enclosing loop would become invalid in the transformed code. A <code>return</code>
* statement would no longer return from the enclosing function, but instead from the extracted function.</p>
* <p>We transform all three statements into <code>return</code> statements returning an object literal with a
* property <code>type</code> indicating whether this is a 'goto' (i.e., <code>break</code> or <code>return</code>)
* 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.</p>
* <p>The return value of the extracted function is then examined to determine whether it completed normally
* (i.e., returned <code>undefined</code>), or whether it returned an object indicating special control flow.</p>
* <p>For example, consider this code from MooTools:</p>
* <pre>
* 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(' ');
* }
* </pre>
* <p>Under {@link ForInBodyExtractionPolicy}, this is transformed into</p>
* <pre>
* 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;
* }
* }
* }
* </pre>
* <p>Note that at the CAst level, <code>break</code> and <code>continue</code> are represented as <code>goto</code>
* 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.</p>
* </li>
* <li><b>Assignment to loop variable</b>:
* <p>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.</p>
* <p><b>TODO:</b> This is not handled at the moment.</p>
* </li>
* </ul>
*
* <p>Finally, note that exceptions do not need to be handled specially.</p>
*
* @author mschaefer
*
*/
public class ClosureExtractor extends CAstRewriterExt {
private LinkedList<ExtractionPolicy> policies = new LinkedList<ExtractionPolicy>();
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<Pair<CAstNode, NoKey>, 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<Pair<CAstNode, NoKey>, 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<Pair<CAstNode, NoKey>, CAstNode> nodeMap) {
List<ExtractionRegion> regions = policies.getFirst().extract(root);
if(regions == null) {
return copyNode(root, context, nodeMap);
} else {
ArrayList<CAstNode> copied_children = new ArrayList<CAstNode>();
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<region.getStart();++next_child)
copied_children.add(copyNodes(root.getChild(next_child), new ChildPos(root, next_child, context), nodeMap));
for(CAstNode stmt : extractRegion(root, new ExtractionPos(root, region, context), nodeMap))
copied_children.add(stmt);
next_child = region.getEnd();
}
for(;next_child<root.getChildCount();++next_child)
copied_children.add(copyNodes(root.getChild(next_child), new ChildPos(root, next_child, context), nodeMap));
CAstNode newNode = Ast.makeNode(BLOCK_STMT, copied_children.toArray(new CAstNode[0]));
nodeMap.put(Pair.make(root, context.key()), newNode);
return newNode;
}
}
/*
* Normal variables are just copied, but 'this' references need to be rewritten if we are inside an extracted
* function body.
*/
private CAstNode copyVar(CAstNode root, NodePos context, Map<Pair<CAstNode, NoKey>, 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<Pair<CAstNode, NoKey>, 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: <label> }
CAstNode newNode =
Ast.makeNode(RETURN,
Ast.makeNode(OBJECT_LITERAL,
addExnFlow(Ast.makeNode(CALL,
addExnFlow(makeVarRef("Object"), JavaScriptTypes.ReferenceError, getCurrentEntity(), context),
Ast.makeConstant("ctor")), null, getCurrentEntity(), context),
Ast.makeConstant("type"),
Ast.makeConstant("goto"),
Ast.makeConstant("target"),
Ast.makeConstant(((double)label)+"")));
// remove outgoing cfg edges of the old node
deleteFlow(root, getCurrentEntity());
nodeMap.put(Pair.make(root, context.key()), newNode);
return newNode;
} else {
return copyNode(root, context, nodeMap);
}
}
/* 'return' statements inside an extracted function body need to be encoded in a similar fashion. */
private CAstNode copyReturn(CAstNode root, NodePos context, Map<Pair<CAstNode, NoKey>, CAstNode> nodeMap) {
ExtractionPos epos = ExtractionPos.getEnclosingExtractionPos(context);
// if an extracted function body may terminate normally, we need to append a default RETURN node
// which should not be rewritten; this node is marked as 'synthetic'
if(epos == null || isSynthetic(root))
return copyNode(root, context, nodeMap);
// add a return to every enclosing extracted function body
do {
epos.addReturn();
epos = ExtractionPos.getEnclosingExtractionPos(epos.getParentPos());
} while(epos != null);
// emit appropriate 'return' statement
if(root.getChildCount() > 0) {
// return { type: 'return', value: <retval> }
CAstNode retval = copyNodes(root.getChild(0), new ChildPos(root, 0, context), nodeMap);
CAstNode newNode =
Ast.makeNode(RETURN,
Ast.makeNode(OBJECT_LITERAL,
addExnFlow(Ast.makeNode(CALL,
addExnFlow(makeVarRef("Object"), JavaScriptTypes.ReferenceError, getCurrentEntity(), context),
Ast.makeConstant("ctor")), null, getCurrentEntity(), context),
Ast.makeConstant("type"),
Ast.makeConstant("return"),
Ast.makeConstant("value"),
retval));
nodeMap.put(Pair.make(root, context.key()), newNode);
return newNode;
} else {
// return { type: 'return' }
CAstNode newNode =
Ast.makeNode(RETURN,
Ast.makeNode(OBJECT_LITERAL,
addExnFlow(Ast.makeNode(CALL,
addExnFlow(makeVarRef("Object"), JavaScriptTypes.ReferenceError, getCurrentEntity(), context),
Ast.makeConstant("ctor")), null, getCurrentEntity(), context),
Ast.makeConstant("type"),
Ast.makeConstant("return")));
nodeMap.put(Pair.make(root, context.key()), newNode);
return newNode;
}
}
/* Recursively copy child nodes. */
private CAstNode copyNode(CAstNode node, NodePos context, Map<Pair<CAstNode, NoKey>, CAstNode> nodeMap) {
CAstNode children[] = new CAstNode[node.getChildCount()];
for (int i = 0; i < children.length; i++) {
children[i] = copyNodes(node.getChild(i), new ChildPos(node, i, context), nodeMap);
}
CAstNode newNode = Ast.makeNode(node.getKind(), children);
nodeMap.put(Pair.make(node, context.key()), newNode);
// if this node has a control flow successor beyond the innermost enclosing extracted function loop, we need to reroute
ExtractionPos epos = ExtractionPos.getEnclosingExtractionPos(context);
if(!isFlowDeleted(newNode, getCurrentEntity()) && epos != null) {
CAstControlFlowMap cfg = getCurrentEntity().getControlFlow();
Collection<Object> labels = cfg.getTargetLabels(node);
boolean invalidateCFlow = false;
for(Object label : labels) {
CAstNode target = cfg.getTarget(node, label);
if(target != CAstControlFlowMap.EXCEPTION_TO_EXIT && !epos.contains(target)) {
invalidateCFlow = true;
break;
}
}
if(invalidateCFlow) {
deleteFlow(node, getCurrentEntity());
for(Object label : labels) {
CAstNode target = cfg.getTarget(node, label);
if(epos.contains(target))
addFlow(node, label, target, cfg);
else
addFlow(node, label, CAstControlFlowMap.EXCEPTION_TO_EXIT, cfg);
}
}
}
return newNode;
}
private int anonymous_counter = 0;
private List<CAstNode> extractRegion(CAstNode root, ExtractionPos context, Map<Pair<CAstNode, NoKey>, CAstNode> nodeMap) {
CAstEntity entity = getCurrentEntity();
// whether we are extracting a single statement that is itself a block
boolean extractingBlock = context.getStart() + 1 == context.getEnd() && root.getChild(context.getStart()).getKind() == BLOCK_STMT;
String name = EXTRACTED_FUN_BASENAME + (anonymous_counter++);
// Create a new entity for the extracted function.
ExtractedFunction new_entity = new ExtractedFunction(name, context);
context.setExtractedEntity(new_entity);
// rewrite the code to be extracted
/*
* First, we need to massage the code a little bit, prepending an assignment of the form '<name> = <name>'
* and appending a RETURN statement if it may complete normally (i.e., if execution may 'fall off'
* the end). Additionally, if the extraction starts inside a nested BLOCK_EXPR, we flatten it out into a list
* of statements.
*
* The whole thing is then wrapped into a block.
*/
ArrayList<CAstNode> prologue = new ArrayList<CAstNode>();
ArrayList<CAstNode> fun_body_stmts = new ArrayList<CAstNode>();
CAstNode self_ref = makeVarRef(name);
CAstNode self_assign = Ast.makeNode(ASSIGN, self_ref, makeVarRef(name));
addFlow(self_ref, JavaScriptTypes.ReferenceError, CAstControlFlowMap.EXCEPTION_TO_EXIT, entity.getControlFlow());
fun_body_stmts.add(self_assign);
// if we are extracting a block, unwrap it
if(extractingBlock) {
CAstNode block = root.getChild(context.getStart());
for(int i=0;i<block.getChildCount();++i)
fun_body_stmts.add(block.getChild(i));
if(mayCompleteNormally(block))
fun_body_stmts.add(markSynthetic(Ast.makeNode(RETURN)));
} else {
if(context.getRegion() instanceof TwoLevelExtractionRegion) {
CAstNode start = root.getChild(context.getStart());
TwoLevelExtractionRegion tler = (TwoLevelExtractionRegion)context.getRegion();
if(start.getKind() != CAstNode.BLOCK_EXPR)
throw new IllegalArgumentException("Invalid two-level extraction region.");
if(tler.getEndInner() != -1)
throw new UnimplementedError("Two-level extraction not fully implemented.");
int i;
for(i=0;i<tler.getStartInner();++i)
prologue.add(copyNodes(start.getChild(i),context, nodeMap));
for(;i<start.getChildCount();++i)
fun_body_stmts.add(start.getChild(i));
for(i=context.getStart()+1;i<context.getEnd();++i)
fun_body_stmts.add(root.getChild(i));
} else {
if(context.getEnd() > context.getStart()+1) {
CAstNode[] stmts = new CAstNode[context.getEnd()-context.getStart()];
for(int i=context.getStart();i<context.getEnd();++i)
stmts[i-context.getStart()] = root.getChild(i);
fun_body_stmts.add(Ast.makeNode(BLOCK_STMT, stmts));
} else {
fun_body_stmts.add(root.getChild(context.getStart()));
}
}
if(mayCompleteNormally(root.getChild(context.getEnd()-1)))
fun_body_stmts.add(markSynthetic(Ast.makeNode(RETURN)));
}
CAstNode inner_block = Ast.makeNode(BLOCK_STMT, fun_body_stmts.toArray(new CAstNode[0]));
CAstNode fun_body = Ast.makeNode(BLOCK_STMT, inner_block);
/*
* Now we rewrite the body and construct a Rewrite object.
*/
final Map<Pair<CAstNode, NoKey>, CAstNode> nodes = HashMapFactory.make();
final CAstNode newRoot = copyNodes(fun_body, context, nodes);
final CAstSourcePositionMap theSource = copySource(nodes, entity.getSourceMap());
final CAstControlFlowMap theCfg = copyFlow(nodes, entity.getControlFlow(), theSource);
final CAstNodeTypeMap theTypes = copyTypes(nodes, entity.getNodeTypeMap());
final Map<CAstNode, Collection<CAstEntity>> theChildren = HashMapFactory.make();
for(int i=context.getStart();i<context.getEnd();++i)
theChildren.putAll(copyChildren(root.getChild(i), nodes, entity.getAllScopedEntities()));
Rewrite rw = new Rewrite() {
@Override public CAstNode newRoot() { return newRoot; }
@Override public CAstControlFlowMap newCfg() { return theCfg; }
@Override public CAstSourcePositionMap newPos() { return theSource; }
@Override public CAstNodeTypeMap newTypes() { return theTypes; }
@Override public Map<CAstNode, Collection<CAstEntity>> newChildren() { return theChildren; }
};
new_entity.setRewrite(rw);
/* Now we construct a call to the extracted function.
*
* If the body never referenced 'this', the function call will be of the form
*
* <extracted_fun_expr>("do", <global_object>, <parm1>, ... , <parmn>)
*
* The "do" argument is a dummy argument required by the CAst encoding for JavaScript.
*
* If, on the other hand, there are references to 'this', these will have been rewritten into references
* to an additional parameter 'thi$', so the call will be of the form
*
* <extracted_fun_expr>("do", <global_object>, <parm1>, ..., <parmn>, this)
*
* if we are in a function, and
*
* <extracted_fun_expr>("do", <global_object>, <parm1>, ..., <parmn>, <global_object>)
*
* at the top-level.
*
* In any case, we also add a CFG edge for a ReferenceError exception on the <parmi> arguments, and for
* a null pointer exception on the call; these are infeasible, but we add them anyway to get the same AST
* as with a manual extraction.
*/
List<CAstNode> args = new ArrayList<CAstNode>();
CAstNode funExpr = Ast.makeNode(FUNCTION_EXPR, Ast.makeConstant(new_entity));
args.add(funExpr);
context.setCallSite(funExpr);
ExtractionPos outer = ExtractionPos.getEnclosingExtractionPos(context.getParentPos());
if(outer == null) {
addEntity(funExpr, new_entity);
} else {
outer.addNestedPos(context);
}
args.add(Ast.makeConstant("do"));
args.add(addNode(makeVarRef("__WALA__int3rnal__global"), entity.getControlFlow()));
for(String parmName : context.getParameters())
args.add(addExnFlow(makeVarRef(parmName), JavaScriptTypes.ReferenceError, entity, context));
if(context.containsThis())
args.add(inFunction() ? Ast.makeNode(VAR, Ast.makeConstant("this")) : Ast.makeConstant(null));
CAstNode call = Ast.makeNode(CALL, args.toArray(new CAstNode[0]));
addExnFlow(call, null, entity, context);
// if the extracted code contains jumps, we need to insert some fix-up code
ArrayList<CAstNode> stmts = new ArrayList<CAstNode>(prologue);
if(context.containsJump()) {
// result of call is stored in variable 're$'
// TODO: this should not be an assignment to a global variable, but to a let-scoped one
CAstNode decl = Ast.makeNode(ASSIGN,
addExnFlow(makeVarRef("re$"), JavaScriptTypes.ReferenceError, entity, context),
call);
stmts.add(decl);
CAstNode goto_fixup = null, return_fixup = null;
if(context.containsGoto())
goto_fixup = createGotoFixup(context, entity);
if(context.containsReturn() && context.isOutermost())
return_fixup = createReturnFixup(context, entity);
// empty statement as else branch
CAstNode if_undef = Ast.makeNode(LABEL_STMT, Ast.makeConstant(-1), Ast.makeNode(EMPTY));
// if(re$ != 1) goto if_undef;
CAstNode undef_check = Ast.makeNode(IFGOTO,
CAstOperator.OP_NE,
addExnFlow(makeVarRef("re$"), JavaScriptTypes.ReferenceError, entity, context),
Ast.makeConstant(1));
addFlow(undef_check, true, if_undef, entity.getControlFlow());
List<CAstNode> fixup_stmts = new ArrayList<CAstNode>();
if(return_fixup != null)
fixup_stmts.add(return_fixup);
if(goto_fixup != null)
fixup_stmts.add(goto_fixup);
// if this is a nested for-in loop, we need to pass on unhandled jumps
if(!context.isOutermost() && (context.containsReturn() || context.containsOuterGoto()))
fixup_stmts.add(Ast.makeNode(RETURN, addExnFlow(makeVarRef("re$"), JavaScriptTypes.ReferenceError, entity, context)));
stmts.add(Ast.makeNode(BLOCK_STMT, undef_check, Ast.makeNode(BLOCK_STMT, fixup_stmts.toArray(new CAstNode[0])), if_undef));
} else {
stmts.add(call);
}
if(extractingBlock) {
// put the call and the fixup code together
CAstNode newNode = Ast.makeNode(BLOCK_STMT, stmts.toArray(new CAstNode[0]));
nodeMap.put(Pair.make(root, context.key()), newNode);
deleteFlow(root, getCurrentEntity());
return Collections.singletonList(newNode);
} else {
return stmts;
}
}
private CAstNode createReturnFixup(ExtractionPos context, CAstEntity entity) {
CAstNode return_fixup;
// if((re$.type == 'return') != 1) ...
CAstNode return_check = Ast.makeNode(IFGOTO,
CAstOperator.OP_NE,
Ast.makeNode(BINARY_EXPR,
CAstOperator.OP_EQ,
addExnFlow(Ast.makeNode(OBJECT_REF,
addExnFlow(makeVarRef("re$"), JavaScriptTypes.ReferenceError, entity, context),
Ast.makeConstant("type")), JavaScriptTypes.TypeError, entity, context),
Ast.makeConstant("return")),
Ast.makeConstant(1));
// return re$.value;
CAstNode then_branch =
Ast.makeNode(RETURN,
addExnFlow(Ast.makeNode(OBJECT_REF,
addExnFlow(makeVarRef("re$"), JavaScriptTypes.ReferenceError, entity, context),
Ast.makeConstant("value")), JavaScriptTypes.TypeError, entity, context));
// empty statement as else branch
CAstNode else_branch = Ast.makeNode(LABEL_STMT, Ast.makeConstant(-1), Ast.makeNode(EMPTY));
// effectively, this becomes
// if(re$.type == 'return') return re$.value;
addFlow(return_check, true, else_branch, entity.getControlFlow());
return_fixup = Ast.makeNode(BLOCK_STMT, return_check, then_branch, else_branch);
return return_fixup;
}
private CAstNode createGotoFixup(ExtractionPos context, CAstEntity entity) {
CAstNode goto_fixup;
ArrayList<CAstNode> target_checks = new ArrayList<CAstNode>();
// add fixup code for every goto in the extracted code
for(CAstNode goto_target : context.getGotoTargets()) {
// if((re$.target == <goto_target>) != 1) goto dummy; goto <goto_target>; dummy: ;
CAstNode target_check = Ast.makeNode(IFGOTO,
CAstOperator.OP_NE,
Ast.makeNode(BINARY_EXPR,
CAstOperator.OP_EQ,
addExnFlow(Ast.makeNode(OBJECT_REF,
addExnFlow(makeVarRef("re$"), JavaScriptTypes.ReferenceError, entity, context),
Ast.makeConstant("target")), JavaScriptTypes.TypeError, entity, context),
Ast.makeConstant((double)labeller.getLabel(goto_target)+"")),
Ast.makeConstant(1));
CAstNode then_branch = Ast.makeNode(GOTO, Ast.makeConstant(-1));
CAstNode else_branch = Ast.makeNode(LABEL_STMT, Ast.makeConstant(-1), Ast.makeNode(EMPTY));
addFlow(then_branch, null, goto_target, entity.getControlFlow());
addFlow(target_check, true, else_branch, entity.getControlFlow());
target_checks.add(Ast.makeNode(BLOCK_STMT, target_check, then_branch, else_branch));
}
// add check whether re$ is actually a goto
CAstNode goto_check = Ast.makeNode(IFGOTO,
CAstOperator.OP_NE,
Ast.makeNode(BINARY_EXPR,
CAstOperator.OP_EQ,
addExnFlow(Ast.makeNode(OBJECT_REF,
addExnFlow(makeVarRef("re$"), JavaScriptTypes.ReferenceError, entity, context),
Ast.makeConstant("type")), JavaScriptTypes.TypeError, entity, context),
Ast.makeConstant("goto")),
Ast.makeConstant(1));
CAstNode else_branch = Ast.makeNode(LABEL_STMT, Ast.makeConstant(-1), Ast.makeNode(EMPTY));
addFlow(goto_check, true, else_branch, entity.getControlFlow());
goto_fixup = Ast.makeNode(BLOCK_STMT, goto_check, Ast.makeNode(BLOCK_STMT, target_checks.toArray(new CAstNode[0])), else_branch);
return goto_fixup;
}
// false if execution of this node must result in a jump
private boolean mayCompleteNormally(CAstNode node) {
int kind= node.getKind();
switch(kind) {
case BLOCK_STMT:
if(node.getChildCount()==0)
return true;
return mayCompleteNormally(node.getChild(node.getChildCount()-1));
case GOTO:
case RETURN:
case BREAK:
case CONTINUE:
case THROW:
return false;
case LOOP:
case SWITCH:
return mayCompleteNormally(node.getChild(1));
case TRY:
return mayCompleteNormally(node.getChild(0));
default:
return true;
}
}
// helper functions for adding exceptional CFG edges
private CAstNode addExnFlow(CAstNode node, Object label, CAstEntity entity, NodePos pos) {
return addExnFlow(node, label, entity.getControlFlow(), pos);
}
private CAstNode addExnFlow(CAstNode node, Object label, CAstControlFlowMap flow, NodePos pos) {
CAstNode target = getThrowTarget(pos);
return addFlow(node, label, target, flow);
}
// determine the innermost enclosing throw target at position pos
private CAstNode getThrowTarget(NodePos pos) {
return pos.accept(new PosSwitch<CAstNode>() {
@Override
public CAstNode caseRootPos(RootPos pos) {
return CAstControlFlowMap.EXCEPTION_TO_EXIT;
}
@Override
public CAstNode caseChildPos(ChildPos pos) {
int kind = pos.getParent().getKind();
if(kind == TRY && pos.getIndex() == 0)
return pos.getParent().getChild(1);
if(kind == FUNCTION_EXPR || kind == FUNCTION_STMT)
return CAstControlFlowMap.EXCEPTION_TO_EXIT;
return getThrowTarget(pos.getParentPos());
}
@Override
public CAstNode caseForInLoopBodyPos(ExtractionPos pos) {
return getThrowTarget(pos.getParentPos());
}
});
}
// helper function for creating VAR nodes
private CAstNode makeVarRef(String name) {
return Ast.makeNode(VAR, Ast.makeConstant(name));
}
// determine whether we are inside a function
private boolean inFunction() {
for(CAstEntity e : getEnclosingEntities())
if(e.getKind() == CAstEntity.FUNCTION_ENTITY)
return true;
return false;
}
// keep track of synthetic nodes that are to be treated specially during rewriting
private Set<CAstNode> synthetic = HashSetFactory.make();
private boolean isSynthetic(CAstNode node) {
return synthetic.contains(node);
}
private CAstNode markSynthetic(CAstNode node) {
synthetic.add(node);
return node;
}
}

View File

@ -0,0 +1,258 @@
/*******************************************************************************
* 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.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.ibm.wala.cast.js.ipa.callgraph.correlations.Correlation;
import com.ibm.wala.cast.js.ipa.callgraph.correlations.CorrelationSummary;
import com.ibm.wala.cast.js.ipa.callgraph.correlations.EscapeCorrelation;
import com.ibm.wala.cast.js.ipa.callgraph.correlations.ReadWriteCorrelation;
import com.ibm.wala.cast.loader.AstMethod;
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.CAstSourcePositionMap.Position;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.HashSetFactory;
import com.ibm.wala.util.collections.Pair;
/**
* An {@link ExtractionPolicy} that specifies that correlated pairs should be extracted.
*
* In principle, extracting an arbitrary correlated pair can be very difficult. We restrict
* our attention to the case where both read and write occur within the same block of
* statements, with the read preceding the write. In practice, most correlations are of
* this form.
*
* TODO: The code for finding the correlated instructions is broken since Rhino only gives
* us line number positions. Consequently, it fails to find the relevant instructions every
* once in a while.
*
* @author mschaefer
*
*/
public class CorrelatedPairExtractionPolicy extends ExtractionPolicy {
private static final boolean DEBUG = false;
private final Map<CAstNode, List<ExtractionRegion>> region_map = HashMapFactory.make();
private CorrelatedPairExtractionPolicy() {}
private static void findNodesAtPos(int kind, Position pos, CAstSourcePositionMap spmap, ChildPos nodep, Set<ChildPos> res) {
CAstNode node = nodep.getChild();
if(node == null)
return;
Position ndpos = spmap.getPosition(node);
if(ndpos != null) {
// if we are in the wrong file or past the position pos, abort search
if(!ndpos.getURL().equals(pos.getURL()) || ndpos.getFirstLine() > pos.getLastLine())
return;
if(node.getKind() == kind && ndpos.getFirstLine() == pos.getFirstLine() && ndpos.getLastLine() == pos.getLastLine())
res.add(nodep);
}
for(int i=0;i<node.getChildCount();++i)
findNodesAtPos(kind, pos, spmap, nodep.getChildPos(i), res);
}
private static Set<ChildPos> findNodesAtPos(int kind, Position pos, CAstEntity entity) {
Set<ChildPos> res = HashSetFactory.make();
CAstSourcePositionMap spmap = entity.getSourceMap();
CAstNode ast = entity.getAST();
for(int i=0;i<ast.getChildCount();++i)
findNodesAtPos(kind, pos, spmap, new ChildPos(ast, i, new RootPos()), res);
return res;
}
// create an ExtractRegion for the given correlation
private boolean addCorrelation(CAstEntity entity, Correlation corr, CorrelationSummary correlations) {
Position startPos = corr.getStartPosition(correlations.getPositions()),
endPos = corr.getEndPosition(correlations.getPositions());
Set<ChildPos> startNodes = null,
endNodes = null;
if(!entity.getPosition().getURL().equals(startPos.getURL()))
return true;
startNodes = findNodesAtPos(CAstNode.OBJECT_REF, startPos, entity);
if(corr instanceof ReadWriteCorrelation) {
endNodes = findNodesAtPos(CAstNode.OBJECT_REF, endPos, entity);
} else if(corr instanceof EscapeCorrelation) {
int arity = ((EscapeCorrelation)corr).getNumberOfArguments();
endNodes = findNodesAtPos(CAstNode.CALL, endPos, entity);
for(Iterator<ChildPos> iter=endNodes.iterator();iter.hasNext();) {
CAstNode candidate = iter.next().getChild();
// need to deduct three here: one for the function expression, one for "do"/"ctor", and one for the receiver expression
if(candidate.getChildCount() - 3 != arity)
iter.remove();
}
} else {
throw new IllegalArgumentException("Unknown correlation type.");
}
if(startNodes.isEmpty() || endNodes.isEmpty())
return true;
ChildPos startNode, endNode;
filterNames(startNodes, corr.getIndexName());
filterNames(endNodes, corr.getIndexName());
Iterator<ChildPos> iter = startNodes.iterator();
if(startNodes.size() == 2 && endNodes.equals(startNodes)) {
startNode = iter.next();
endNode = iter.next();
} else if(startNodes.size() > 1 || startNodes.size() == 0) {
if(DEBUG)
System.err.println("Couldn't find unique start node for correlation " + corr.pp(correlations.getPositions()));
return false;
} else if(endNodes.size() > 1 || endNodes.size() == 0) {
if(DEBUG)
System.err.println("Couldn't find unique end node for correlation " + corr.pp(correlations.getPositions()));
return false;
} else {
startNode = startNodes.iterator().next();
endNode = endNodes.iterator().next();
}
Pair<CAstNode, ? extends ExtractionRegion> region_info = findClosestContainingBlock(entity, startNode, endNode, corr.getIndexName());
if(region_info == null) {
if(DEBUG)
System.err.println("Couldn't find enclosing block for correlation " + corr.pp(correlations.getPositions()));
return false;
}
List<ExtractionRegion> regions = region_map.get(region_info.fst);
if(regions == null)
region_map.put(region_info.fst, regions = new LinkedList<ExtractionRegion>());
for(int i=0;i<regions.size();++i) {
ExtractionRegion region2 = regions.get(i);
if(region2.getEnd() <= region_info.snd.getStart())
continue;
if(region2.getStart() < region_info.snd.getEnd()) {
if(region_info.snd.getParameters().equals(region2.getParameters())) {
region2.setStart(Math.min(region_info.snd.getStart(), region2.getStart()));
region2.setEnd(Math.max(region_info.snd.getEnd(), region2.getEnd()));
if(DEBUG)
System.err.println("Successfully processed correlation " + corr.pp(correlations.getPositions()));
return true;
}
if(DEBUG)
System.err.println("Overlapping regions.");
return false;
}
regions.add(i, region_info.snd);
if(DEBUG)
System.out.println("Successfully processed correlation " + corr.pp(correlations.getPositions()));
return true;
}
if(DEBUG)
System.out.println("Successfully processed correlation " + corr.pp(correlations.getPositions()));
regions.add(region_info.snd);
return true;
}
private void filterNames(Set<ChildPos> nodes, String indexName) {
for(Iterator<ChildPos> iter=nodes.iterator();iter.hasNext();) {
CAstNode node = iter.next().getChild();
if(node.getKind() == CAstNode.OBJECT_REF) {
CAstNode index = node.getChild(1);
if(index.getKind() != CAstNode.VAR || !index.getChild(0).getValue().equals(indexName)) {
iter.remove();
}
}
}
}
private Pair<CAstNode, ? extends ExtractionRegion> findClosestContainingBlock(CAstEntity entity, ChildPos startNode, ChildPos endNode, String parmName) {
ChildPos pos = startNode;
CAstNode block = null;
int start = -1, end = 0;
int start_inner = -1, end_inner = -1;
while(end == 0) {
// find the next closest block around the node at position "pos"
while(pos.getParent().getKind() != CAstNode.BLOCK_STMT) {
if(pos.getParentPos() instanceof ChildPos)
pos = (ChildPos)pos.getParentPos();
else
return null;
}
block = pos.getParent();
start = pos.getIndex();
end = getCoveringChildIndex(block, start, endNode.getChild()) + 1;
}
// expand region to include forward goto targets
CAstControlFlowMap cfg = entity.getControlFlow();
for(int i=start;i<end;++i) {
CAstNode stmt = block.getChild(i);
for(Object targetLabel : cfg.getTargetLabels(stmt)) {
CAstNode target = cfg.getTarget(stmt, targetLabel);
int targetIndex = getCoveringChildIndex(block, start, target);
if(targetIndex >= end)
end = targetIndex + 1;
}
}
// special hack to handle "var p = ..., x = y[p];", where startNode = "y[p]"
if(block.getChild(0).getKind() == CAstNode.BLOCK_EXPR && start == 0) {
for(start_inner=0;start_inner<block.getChild(0).getChildCount();++start_inner)
if(NodePos.inSubtree(startNode.getChild(), block.getChild(0).getChild(start_inner)))
return Pair.make(block, new TwoLevelExtractionRegion(start, end, start_inner, end_inner, Collections.singletonList(parmName)));
}
return Pair.make(block, new ExtractionRegion(start, end, Collections.singletonList(parmName)));
}
private static int getCoveringChildIndex(CAstNode parent, int start, CAstNode child) {
for(int i=start;i<parent.getChildCount();++i)
if(NodePos.inSubtree(child, parent.getChild(i)))
return i;
return -1;
}
private static CorrelatedPairExtractionPolicy addCorrelations(CAstEntity entity, Map<Position, CorrelationSummary> summaries, CorrelatedPairExtractionPolicy policy) {
// add correlations for this entity
if(entity.getAST() != null && summaries.containsKey(entity.getPosition())) {
CorrelationSummary correlations = summaries.get(entity.getPosition());
for(Correlation corr : correlations.getCorrelations())
policy.addCorrelation(entity, corr, correlations);
}
// recursively add correlations for scoped entities
Map<CAstNode, Collection<CAstEntity>> allScopedEntities = entity.getAllScopedEntities();
for(Collection<CAstEntity> scopedEntities : allScopedEntities.values())
for(CAstEntity scopedEntity : scopedEntities)
if(addCorrelations(scopedEntity, summaries, policy) == null)
return null;
return policy;
}
public static CorrelatedPairExtractionPolicy make(CAstEntity entity, Map<IMethod, CorrelationSummary> summaries) {
CorrelatedPairExtractionPolicy policy = new CorrelatedPairExtractionPolicy();
Map<Position, CorrelationSummary> summary_map = HashMapFactory.make();
for(Map.Entry<IMethod, CorrelationSummary> e : summaries.entrySet()) {
if(e.getKey() instanceof AstMethod) {
Position pos = ((AstMethod)e.getKey()).getSourcePosition();
if(pos != null)
summary_map.put(pos, e.getValue());
}
}
return addCorrelations(entity, summary_map, policy);
}
@Override
public List<ExtractionRegion> extract(CAstNode node) {
return region_map.get(node);
}
}

View File

@ -0,0 +1,52 @@
/*******************************************************************************
* 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.io.IOException;
import java.net.URL;
import java.util.Map;
import com.ibm.wala.cast.js.ipa.callgraph.correlations.CorrelationFinder;
import com.ibm.wala.cast.js.ipa.callgraph.correlations.CorrelationSummary;
import com.ibm.wala.cast.js.translator.JavaScriptTranslatorFactory;
import com.ibm.wala.cast.tree.CAst;
import com.ibm.wala.cast.tree.CAstEntity;
import com.ibm.wala.cast.tree.impl.CAstBasicRewriter.NoKey;
import com.ibm.wala.cast.tree.impl.CAstRewriter;
import com.ibm.wala.cast.tree.impl.CAstRewriterFactory;
import com.ibm.wala.classLoader.IMethod;
import com.ibm.wala.ipa.cha.ClassHierarchyException;
public class CorrelatedPairExtractorFactory implements CAstRewriterFactory<NodePos, NoKey> {
private final Map<IMethod, CorrelationSummary> summaries;
public CorrelatedPairExtractorFactory(JavaScriptTranslatorFactory translatorFactory, URL entryPoint) throws ClassHierarchyException, IOException {
this(new CorrelationFinder(translatorFactory).findCorrelatedAccesses(entryPoint));
}
public CorrelatedPairExtractorFactory(Map<IMethod, CorrelationSummary> summaries) {
this.summaries = summaries;
}
@Override
public CAstRewriter<NodePos, NoKey> createCAstRewriter(CAst ast) {
ExtractionPolicyFactory policyFactory = new ExtractionPolicyFactory() {
@Override
public ExtractionPolicy createPolicy(CAstEntity entity) {
CorrelatedPairExtractionPolicy policy = CorrelatedPairExtractionPolicy.make(entity, summaries);
assert policy != null;
return policy;
}
};
return new ClosureExtractor(ast, policyFactory);
}
}

View File

@ -0,0 +1,183 @@
/*******************************************************************************
* 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.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
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.CAstQualifier;
import com.ibm.wala.cast.tree.CAstSourcePositionMap;
import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position;
import com.ibm.wala.cast.tree.CAstType;
import com.ibm.wala.cast.tree.impl.CAstRewriter.Rewrite;
import com.ibm.wala.util.collections.EmptyIterator;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.HashSetFactory;
/**
* A simple implementation of {@link CAstEntity} used by the {@link ClosureExtractor}.
*
* @author mschaefer
*
*/
class ExtractedFunction implements CAstEntity {
private final String name;
private String[] parms;
private final ExtractionPos pos;
private Rewrite r;
private CAstNode root;
private CAstControlFlowMap cfg;
private CAstSourcePositionMap posmap;
private CAstNodeTypeMap types;
private Map<CAstNode, Collection<CAstEntity>> scopedEntities;
public ExtractedFunction(String name, ExtractionPos pos) {
this.name = name;
this.pos = pos;
}
public void setRewrite(Rewrite r) {
assert this.r == null : "Rewrite shouldn't be set more than once.";
this.r = r;
this.root = r.newRoot();
this.cfg = r.newCfg();
this.posmap = r.newPos();
this.types = r.newTypes();
}
@Override
public int getKind() {
return CAstEntity.FUNCTION_ENTITY;
}
@Override
public String getName() {
return name;
}
@Override
public String getSignature() {
return null;
}
@Override
public String[] getArgumentNames() {
computeParms();
return parms;
}
@Override
public CAstNode[] getArgumentDefaults() {
return new CAstNode[0];
}
@Override
public int getArgumentCount() {
computeParms();
return parms.length;
}
private void computeParms() {
if(this.parms == null) {
ArrayList<String> parms = new ArrayList<String>();
parms.add(name);
parms.add("this");
parms.addAll(pos.getParameters());
if(pos.containsThis())
parms.add(pos.getThisParmName());
this.parms = parms.toArray(new String[0]);
}
}
@Override
public Map<CAstNode, Collection<CAstEntity>> getAllScopedEntities() {
if(scopedEntities == null) {
scopedEntities = HashMapFactory.make();
// first, add all existing entities inside the body of this for loop
for(Entry<CAstNode, Collection<CAstEntity>> e : r.newChildren().entrySet()) {
if(NodePos.inSubtree(e.getKey(), root)) {
Collection<CAstEntity> c = scopedEntities.get(e.getKey());
if(c == null)
scopedEntities.put(e.getKey(), c = HashSetFactory.make());
c.addAll(e.getValue());
}
}
// now, add all new entities which arise from extracted nested for-in loops
for(Iterator<ExtractionPos> iter=pos.getNestedLoops(); iter.hasNext();) {
ExtractionPos nested_loop = iter.next();
CAstNode callsite = nested_loop.getCallSite();
CAstEntity scoped_entity = nested_loop.getExtractedEntity();
Collection<CAstEntity> c = scopedEntities.get(callsite);
if(c == null)
scopedEntities.put(callsite, c = HashSetFactory.make());
c.add(scoped_entity);
}
}
return scopedEntities;
}
@Override
public Iterator<CAstEntity> getScopedEntities(CAstNode construct) {
if(getAllScopedEntities().containsKey(construct)) {
return getAllScopedEntities().get(construct).iterator();
} else {
return EmptyIterator.instance();
}
}
@Override
public CAstNode getAST() {
return root;
}
@Override
public CAstControlFlowMap getControlFlow() {
return cfg;
}
@Override
public CAstSourcePositionMap getSourceMap() {
return posmap;
}
@Override
public Position getPosition() {
return getSourceMap().getPosition(root);
}
@Override
public CAstNodeTypeMap getNodeTypeMap() {
return types;
}
@Override
public Collection<CAstQualifier> getQualifiers() {
return null;
}
@Override
public CAstType getType() {
return null;
}
@Override
public String toString() {
return "<JS function " + name + ">";
}
}

View File

@ -0,0 +1,26 @@
/*******************************************************************************
* 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.List;
import com.ibm.wala.cast.tree.CAstNode;
/**
* An extraction policy tells a {@link ClosureExtractor} which bits of code to extract into closures.
*
* @author mschaefer
*
*/
public abstract class ExtractionPolicy {
public abstract List<ExtractionRegion> extract(CAstNode node);
}

View File

@ -0,0 +1,18 @@
/*******************************************************************************
* 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.CAstEntity;
public abstract class ExtractionPolicyFactory {
public abstract ExtractionPolicy createPolicy(CAstEntity entity);
}

View File

@ -0,0 +1,216 @@
/*******************************************************************************
* 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.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import com.ibm.wala.cast.tree.CAstEntity;
import com.ibm.wala.cast.tree.CAstNode;
import com.ibm.wala.util.collections.HashSetFactory;
/**
* A special {@link ChildPos} representing the position of a node which is the body of a for-in loop.
*
* <p>This also stores some additional data obtained while rewriting the loop body, such as whether
* <code>return</code> statements were encountered.</p>
*
* @author mschaefer
*
*/
public class ExtractionPos extends NodePos {
private final CAstNode parent;
private final ExtractionRegion region;
private final NodePos parent_pos;
private boolean contains_return;
private boolean contains_this;
private final Set<CAstNode> goto_targets = HashSetFactory.make();
private boolean contains_outer_goto;
private final Set<ExtractionPos> nested_loops = HashSetFactory.make();
private CAstEntity extracted_entity;
private CAstNode callsite;
public ExtractionPos(CAstNode parent, ExtractionRegion region, NodePos parent_pos) {
this.parent = parent;
this.region = region;
this.parent_pos = parent_pos;
}
public CAstNode getParent() {
return parent;
}
public int getStart() {
return region.getStart();
}
public int getEnd() {
return region.getEnd();
}
public ExtractionRegion getRegion() {
return region;
}
public boolean contains(CAstNode node) {
for(int i=getStart();i<getEnd();++i)
if(NodePos.inSubtree(node, parent.getChild(i)))
return true;
return false;
}
public List<String> getParameters() {
return region.getParameters();
}
public void addGotoTarget(CAstNode node) {
// check whether this target lies beyond an enclosing for-in loop
ExtractionPos outer = getEnclosingExtractionPos(parent_pos);
if(outer != null && !outer.contains(node)) {
// the goto needs to be handled by the outer loop
outer.addGotoTarget(node);
// but we need to remember to pass it on
contains_outer_goto = true;
} else {
// this goto is our responsibility
goto_targets.add(node);
}
}
public boolean containsReturn() {
return contains_return;
}
public void addReturn() {
this.contains_return = true;
}
public Set<CAstNode> getGotoTargets() {
return Collections.unmodifiableSet(goto_targets);
}
public void addThis() {
contains_this = true;
}
public boolean containsThis() {
return contains_this;
}
public boolean containsGoto() {
return !getGotoTargets().isEmpty();
}
public boolean containsOuterGoto() {
return contains_outer_goto;
}
public boolean containsJump() {
return containsGoto() || containsReturn() || containsOuterGoto();
}
public String getThisParmName() {
return "thi$";
}
public void addNestedPos(ExtractionPos loop) {
nested_loops.add(loop);
}
public Iterator<ExtractionPos> getNestedLoops() {
return nested_loops.iterator();
}
public void setExtractedEntity(CAstEntity entity) {
assert this.extracted_entity == null : "Cannot reset extracted entity.";
extracted_entity = entity;
}
public CAstEntity getExtractedEntity() {
assert extracted_entity != null : "Extracted entity not set.";
return extracted_entity;
}
public void setCallSite(CAstNode callsite) {
assert this.callsite == null : "Cannot reset call site.";
this.callsite = callsite;
}
public CAstNode getCallSite() {
assert callsite != null : "Call site not set.";
return callsite;
}
@Override
public <A> A accept(PosSwitch<A> ps) {
return ps.caseForInLoopBodyPos(this);
}
// return the outermost enclosing extraction position around 'pos' within the same function; "null" if there is none
public static ExtractionPos getOutermostEnclosingExtractionPos(NodePos pos) {
return pos.accept(new PosSwitch<ExtractionPos>() {
@Override
public ExtractionPos caseRootPos(RootPos pos) {
return null;
}
@Override
public ExtractionPos caseChildPos(ChildPos pos) {
int kind = pos.getParent().getKind();
if(kind == CAstNode.FUNCTION_STMT || kind == CAstNode.FUNCTION_EXPR)
return null;
return getOutermostEnclosingExtractionPos(pos.getParentPos());
}
@Override
public ExtractionPos caseForInLoopBodyPos(ExtractionPos pos) {
ExtractionPos outer = getEnclosingExtractionPos(pos.getParentPos());
return outer == null ? pos : outer;
}
});
}
// return the innermost enclosing extraction position around 'pos' within the same function; "null" if there is none
public static ExtractionPos getEnclosingExtractionPos(NodePos pos) {
return pos.accept(new PosSwitch<ExtractionPos>() {
@Override
public ExtractionPos caseRootPos(RootPos pos) {
return null;
}
@Override
public ExtractionPos caseChildPos(ChildPos pos) {
int kind = pos.getParent().getKind();
if(kind == CAstNode.FUNCTION_STMT || kind == CAstNode.FUNCTION_EXPR)
return null;
return getEnclosingExtractionPos(pos.getParentPos());
}
@Override
public ExtractionPos caseForInLoopBodyPos(ExtractionPos pos) {
return pos;
}
});
}
// is this the outermost for-in loop within its enclosing function?
public boolean isOutermost() {
return getEnclosingExtractionPos(parent_pos) == null;
}
public NodePos getParentPos() {
return parent_pos;
}
}

View File

@ -0,0 +1,52 @@
/*******************************************************************************
* 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.List;
/**
* A region for the {@link ClosureExtractor} to extract.
*
* @author mschaefer
*
*/
public class ExtractionRegion {
private int start, end;
private final List<String> parameters;
public ExtractionRegion(int start, int end, List<String> parameters) {
super();
this.start = start;
this.end = end;
this.parameters = parameters;
}
public int getStart() {
return start;
}
public int getEnd() {
return end;
}
public List<String> getParameters() {
return parameters;
}
public void setStart(int start) {
this.start = start;
}
public void setEnd(int end) {
this.end = end;
}
}

View File

@ -0,0 +1,71 @@
/*******************************************************************************
* 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.BLOCK_STMT;
import static com.ibm.wala.cast.tree.CAstNode.EACH_ELEMENT_GET;
import static com.ibm.wala.cast.tree.CAstNode.VAR;
import java.util.Collections;
import java.util.List;
import com.ibm.wala.cast.tree.CAstEntity;
import com.ibm.wala.cast.tree.CAstNode;
import com.ibm.wala.cast.tree.pattern.*;
/**
* A policy telling a {@link ClosureExtractor} to extract the body of every for-in loop.
*
* TODO: This policy only works with Rhino (<1.7.2), since it specifically matches on the encoding
* of for-in loops generated by it.
*
* @author mschaefer
*
*/
public class ForInBodyExtractionPolicy extends ExtractionPolicy {
public static final ForInBodyExtractionPolicy INSTANCE = new ForInBodyExtractionPolicy();
public static final ExtractionPolicyFactory FACTORY = new ExtractionPolicyFactory() {
@Override
public ExtractionPolicy createPolicy(CAstEntity entity) {
return INSTANCE;
}
};
private ForInBodyExtractionPolicy() {}
@Override
public List<ExtractionRegion> extract(CAstNode node) {
SomeConstant loopVar = new SomeConstant();
/* matches the following pattern:
*
* BLOCK_STMT
* ASSIGN
* VAR <loopVar>
* EACH_ELEMENT_GET
* VAR <forin_tmp>
* <loopBody>
*
* TODO: this is too brittle; what if future versions of Rhino encode for-in loops differently?
*/
if(new NodeOfKind(BLOCK_STMT,
new NodeOfKind(ASSIGN,
new NodeOfKind(VAR, loopVar),
new SubtreeOfKind(EACH_ELEMENT_GET)),
new AnyNode()).matches(node)) {
return Collections.singletonList(new ExtractionRegion(1, 2, Collections.singletonList((String)loopVar.getLastMatch())));
}
return null;
}
}

View File

@ -0,0 +1,58 @@
/*******************************************************************************
* 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.ArrayList;
import com.ibm.wala.cast.tree.CAstNode;
/**
* A node labeller keeps a mapping from nodes to integers to allow consistent labelling of nodes.
*
* @author mschaefer
*
*/
public class NodeLabeller {
private ArrayList<CAstNode> nodes = new ArrayList<CAstNode>();
/**
* Adds a node to the mapping if it is not present yet.
*
* @param node the node to add
* @return the node's label
*/
public int addNode(CAstNode node) {
int label = getLabel(node);
if(label == -1) {
label = nodes.size();
nodes.add(node);
}
return label;
}
/**
* Determines the label of a node in the mapping.
*
* @param node the node whose label is to be determined
* @return if the node is mapped, returns its label; otherwise, returns -1
*/
public int getLabel(CAstNode node) {
return nodes.indexOf(node);
}
/**
* Determines the node associated with a given label.
*/
public CAstNode getNode(int label) {
return nodes.get(label);
}
}

View File

@ -0,0 +1,51 @@
/*******************************************************************************
* 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;
import com.ibm.wala.cast.tree.impl.CAstBasicRewriter;
import com.ibm.wala.cast.tree.impl.CAstRewriter;
import com.ibm.wala.cast.tree.impl.CAstBasicRewriter.NoKey;
/**
* Representation of a node's position in a CAst entity's syntax tree. The position is stored as a zipper
* data structure.
*
* @author mschaefer
*
*/
public abstract class NodePos implements CAstRewriter.RewriteContext<CAstBasicRewriter.NoKey> {
public abstract <A> A accept(PosSwitch<A> ps);
@Override
public NoKey key() {
return null;
}
/**
* Determines whether a node is inside the subtree rooted at some other node.
*
* @param node the node
* @param tree the subtree
* @return {@literal true} if {@code node} is a descendant of {@code tree}, {@literal false} otherwise
*/
public static boolean inSubtree(CAstNode node, CAstNode tree) {
if(node == tree)
return true;
if(tree == null)
return false;
for(int i=0;i<tree.getChildCount();++i)
if(inSubtree(node, tree.getChild(i)))
return true;
return false;
}
}

View File

@ -0,0 +1,18 @@
/*******************************************************************************
* 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;
public abstract class PosSwitch<A> {
public abstract A caseRootPos(RootPos pos);
public abstract A caseChildPos(ChildPos pos);
public abstract A caseForInLoopBodyPos(ExtractionPos pos);
}

View File

@ -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.ipa.callgraph.correlations.extraction;
public final class RootPos extends NodePos {
@Override
public <A> A accept(PosSwitch<A> ps) {
return ps.caseRootPos(this);
}
@Override
public int hashCode() {
return 3741;
}
@Override
public boolean equals(Object obj) {
return obj instanceof RootPos;
}
}

View File

@ -0,0 +1,32 @@
/*******************************************************************************
* 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.List;
public class TwoLevelExtractionRegion extends ExtractionRegion {
private final int start_inner, end_inner;
public TwoLevelExtractionRegion(int start, int end, int start_inner, int end_inner, List<String> parameters) {
super(start, end, parameters);
this.start_inner = start_inner;
this.end_inner = end_inner;
}
public int getStartInner() {
return this.start_inner;
}
public int getEndInner() {
return this.end_inner;
}
}

View File

@ -0,0 +1,41 @@
package com.ibm.wala.cast.js.util;
import java.util.Iterator;
import com.ibm.wala.cast.loader.CAstAbstractLoader;
import com.ibm.wala.classLoader.IClassLoader;
import com.ibm.wala.classLoader.ModuleEntry;
import com.ibm.wala.ipa.cha.IClassHierarchy;
import com.ibm.wala.util.warnings.Warning;
public class Util {
public static void checkForFrontEndErrors(IClassHierarchy cha) {
StringBuffer message = null;
for(IClassLoader loader : cha.getLoaders()) {
if (loader instanceof CAstAbstractLoader) {
Iterator<ModuleEntry> errors = ((CAstAbstractLoader)loader).getModulesWithParseErrors();
if (errors.hasNext()) {
if (message == null) {
message = new StringBuffer("front end errors:\n");
}
while (errors.hasNext()) {
ModuleEntry errorModule = errors.next();
for(Warning w : (((CAstAbstractLoader)loader).getMessages(errorModule))) {
message.append("error in ").append(errorModule.getName()).append(":\n");
message.append(w.toString()).append("\n");
}
}
}
// clear out the errors to free some memory
((CAstAbstractLoader)loader).clearMessages();
}
}
if (message != null) {
message.append("end of front end errors\n");
throw new AssertionError(String.valueOf(message));
}
}
}

View File

@ -21,6 +21,7 @@ Export-Package: com.ibm.wala.cast.analysis.typeInference,
com.ibm.wala.cast.plugin,
com.ibm.wala.cast.tree,
com.ibm.wala.cast.tree.impl,
com.ibm.wala.cast.tree.pattern,
com.ibm.wala.cast.tree.visit,
com.ibm.wala.cast.types,
com.ibm.wala.cast.util

View File

@ -0,0 +1,26 @@
/******************************************************************************
* 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.tree.pattern;
import com.ibm.wala.cast.tree.CAstNode;
/**
* A node pattern that matches any AST node.
*
* @author mschaefer
*
*/
public class AnyNode implements NodePattern {
public boolean matches(CAstNode node) {
return true;
}
}

View File

@ -0,0 +1,45 @@
/******************************************************************************
* 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.tree.pattern;
import com.ibm.wala.cast.tree.CAstNode;
/**
* A node pattern that matches an AST node of a certain kind; additionally, the node's children
* have to match the pattern's child patterns.
*
* @author mschaefer
*
*/
public class NodeOfKind implements NodePattern {
protected int kind;
protected NodePattern[] children;
public NodeOfKind(int kind, NodePattern... children) {
this.kind = kind;
this.children = new NodePattern[children.length];
for(int i=0;i<children.length;++i)
this.children[i] = children[i];
}
/* (non-Javadoc)
* @see pattern.NodePattern#matches(com.ibm.wala.cast.tree.CAstNode)
*/
public boolean matches(CAstNode node) {
if(node == null || node.getKind() != kind || node.getChildCount() != children.length)
return false;
for(int i=0;i<children.length;++i)
if(!children[i].matches(node.getChild(i)))
return false;
return true;
}
}

View File

@ -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.tree.pattern;
import com.ibm.wala.cast.tree.CAstNode;
/**
* Interface for lightweight AST patterns.
*
* @author mschaefer
*
*/
public interface NodePattern {
public abstract boolean matches(CAstNode node);
}

View File

@ -0,0 +1,40 @@
/******************************************************************************
* 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.tree.pattern;
import com.ibm.wala.cast.tree.CAstNode;
/**
* A node pattern matching any constant. A pattern of this class also stores its last match.
*
* @author mschaefer
*
*/
public class SomeConstant extends NodeOfKind {
private Object last_match;
public SomeConstant() {
super(CAstNode.CONSTANT);
}
@Override
public boolean matches(CAstNode node) {
boolean res = super.matches(node);
if(res)
this.last_match = node.getValue();
return res;
}
public Object getLastMatch() {
return last_match;
}
}

View File

@ -0,0 +1,31 @@
/******************************************************************************
* 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.tree.pattern;
import com.ibm.wala.cast.tree.CAstNode;
/**
* A node pattern matching a node of a given kind, without regard to its children.
*
* @author mschaefer
*
*/
public class SubtreeOfKind extends NodeOfKind {
public SubtreeOfKind(int kind) {
super(kind);
}
@Override
public boolean matches(CAstNode node) {
return node != null && node.getKind() == this.kind;
}
}

View File

@ -13,6 +13,7 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.MappedByteBuffer;
@ -182,4 +183,15 @@ public class FileUtil {
out.close();
return bb;
}
/**
* write string s into file f
*
* @param f
* @param content
* @throws IOException
*/
public static void writeFile(File f, String content) throws IOException {
new FileWriter(f).append(content).close();
}
}