486 lines
16 KiB
Java
486 lines
16 KiB
Java
/*******************************************************************************
|
|
* 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" +
|
|
"}");
|
|
}
|
|
}
|