diff --git a/com.ibm.wala.cast.js.nodejs.test/.classpath b/com.ibm.wala.cast.js.nodejs.test/.classpath
new file mode 100644
index 000000000..53a2c38d8
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/.classpath
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/com.ibm.wala.cast.js.nodejs.test/.project b/com.ibm.wala.cast.js.nodejs.test/.project
new file mode 100644
index 000000000..20888ae24
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/.project
@@ -0,0 +1,17 @@
+
+
+ com.ibm.wala.cast.js.nodejs.test
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/com.ibm.wala.cast.js.nodejs.test/src/com/ibm/wala/cast/js/nodejs/test/NodejsRequireJsonTest.java b/com.ibm.wala.cast.js.nodejs.test/src/com/ibm/wala/cast/js/nodejs/test/NodejsRequireJsonTest.java
new file mode 100644
index 000000000..58410888c
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/src/com/ibm/wala/cast/js/nodejs/test/NodejsRequireJsonTest.java
@@ -0,0 +1,43 @@
+/******************************************************************************
+ * Copyright (c) 2002 - 2016 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:
+ * Brian Pfretzschner - initial implementation
+ *****************************************************************************/
+package com.ibm.wala.cast.js.nodejs.test;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.net.URL;
+
+import org.junit.Test;
+
+import com.ibm.wala.cast.js.nodejs.NodejsCallGraphBuilderUtil;
+import com.ibm.wala.ipa.callgraph.CallGraph;
+import com.ibm.wala.ipa.callgraph.propagation.PropagationCallGraphBuilder;
+
+/**
+ * @author Brian Pfretzschner
+ */
+public class NodejsRequireJsonTest {
+
+ @Test
+ public void test() throws Exception {
+ URL fileUrl = getClass().getClassLoader().getResource("NodejsRequireJsonTest/index.js");
+ File file = new File(fileUrl.toURI());
+
+ PropagationCallGraphBuilder builder = NodejsCallGraphBuilderUtil.makeCGBuilder(file);
+ CallGraph CG = builder.makeCallGraph(builder.getOptions());
+ String cgString = CG.toString();
+
+ assertTrue(cgString.contains("Lempty/jsonModule>"));
+ assertTrue(cgString.contains("Lnested/jsonModule>"));
+ assertTrue(cgString.contains("Lpackage/jsonModule>"));
+ assertTrue(!cgString.contains("?"));
+ }
+}
diff --git a/com.ibm.wala.cast.js.nodejs.test/src/com/ibm/wala/cast/js/nodejs/test/NodejsRequireTargetSelectorResolveTest.java b/com.ibm.wala.cast.js.nodejs.test/src/com/ibm/wala/cast/js/nodejs/test/NodejsRequireTargetSelectorResolveTest.java
new file mode 100644
index 000000000..78db42e08
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/src/com/ibm/wala/cast/js/nodejs/test/NodejsRequireTargetSelectorResolveTest.java
@@ -0,0 +1,98 @@
+/******************************************************************************
+ * Copyright (c) 2002 - 2016 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:
+ * Brian Pfretzschner - initial implementation
+ *****************************************************************************/
+package com.ibm.wala.cast.js.nodejs.test;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.net.URL;
+
+import org.junit.Test;
+
+import com.ibm.wala.cast.js.nodejs.NodejsCallGraphBuilderUtil;
+import com.ibm.wala.ipa.callgraph.CallGraph;
+import com.ibm.wala.ipa.callgraph.propagation.PropagationCallGraphBuilder;
+
+/**
+ * @author Brian Pfretzschner
+ */
+public class NodejsRequireTargetSelectorResolveTest {
+
+ @Test
+ public void testRequireSimple() throws Exception {
+ URL fileUrl = getClass().getClassLoader().getResource("NodejsRequireTargetSelectorResolve/requireSimple/index.js");
+ File file = new File(fileUrl.toURI());
+
+ PropagationCallGraphBuilder builder = NodejsCallGraphBuilderUtil.makeCGBuilder(file);
+ CallGraph CG = builder.makeCallGraph(builder.getOptions());
+ String cgString = CG.toString();
+
+ assertTrue(cgString.contains("Lmod/nodejsModule/moduleSource/exec>"));
+ assertTrue(cgString.contains("Lmod/nodejsModule/moduleSource/SomeClass/hello>"));
+ assertTrue(!cgString.contains("?"));
+ }
+
+ @Test
+ public void testRequireStaticCircular() throws Exception {
+ URL fileUrl = getClass().getClassLoader().getResource("NodejsRequireTargetSelectorResolve/requireStaticCircular/index.js");
+ File file = new File(fileUrl.toURI());
+
+ PropagationCallGraphBuilder builder = NodejsCallGraphBuilderUtil.makeCGBuilder(file);
+ CallGraph CG = builder.makeCallGraph(builder.getOptions());
+ String cgString = CG.toString();
+
+ assertTrue(cgString.contains("Llib1/nodejsModule/moduleSource/lib1>"));
+ assertTrue(cgString.contains("Llib2/nodejsModule/moduleSource/lib2>"));
+ assertTrue(!cgString.contains("?"));
+ }
+
+ @Test
+ public void testRequireDynamic() throws Exception {
+ URL fileUrl = getClass().getClassLoader().getResource("NodejsRequireTargetSelectorResolve/requireDynamic/index.js");
+ File file = new File(fileUrl.toURI());
+
+ PropagationCallGraphBuilder builder = NodejsCallGraphBuilderUtil.makeCGBuilder(file);
+ CallGraph CG = builder.makeCallGraph(builder.getOptions());
+ String cgString = CG.toString();
+
+ assertTrue(cgString.contains("Llib1/nodejsModule/moduleSource/lib1>"));
+ assertTrue(cgString.contains("Llib2/nodejsModule/moduleSource/lib2>"));
+ assertTrue(!cgString.contains("?"));
+ }
+
+ @Test
+ public void testRequireNodeModules() throws Exception {
+ URL fileUrl = getClass().getClassLoader().getResource("NodejsRequireTargetSelectorResolve/requireNodeModules/index.js");
+ File file = new File(fileUrl.toURI());
+
+ PropagationCallGraphBuilder builder = NodejsCallGraphBuilderUtil.makeCGBuilder(file);
+ CallGraph CG = builder.makeCallGraph(builder.getOptions());
+ String cgString = CG.toString();
+
+ assertTrue(cgString.contains("Lnode_modules_lib_node_modules_sublib_sublib/nodejsModule/moduleSource"));
+ assertTrue(!cgString.contains("?"));
+ }
+
+ @Test
+ public void testRequireCoreModules() throws Exception {
+ URL fileUrl = getClass().getClassLoader().getResource("NodejsRequireTargetSelectorResolve/requireCoreModules.js");
+ File file = new File(fileUrl.toURI());
+
+ PropagationCallGraphBuilder builder = NodejsCallGraphBuilderUtil.makeCGBuilder(file);
+ CallGraph CG = builder.makeCallGraph(builder.getOptions());
+ String cgString = CG.toString();
+
+ assertTrue(cgString.contains("Lutil/nodejsModule/moduleSource/util"));
+ assertTrue(cgString.contains("Lhttps/nodejsModule/moduleSource/https"));
+ assertTrue(cgString.contains("Lhttp/nodejsModule/moduleSource/http"));
+ }
+
+}
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireJsonTest/empty.json b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireJsonTest/empty.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireJsonTest/empty.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireJsonTest/index.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireJsonTest/index.js
new file mode 100644
index 000000000..39126df24
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireJsonTest/index.js
@@ -0,0 +1,3 @@
+var empty = require('./empty.json');
+var empty = require('./nested.json');
+var empty = require('./package.json');
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireJsonTest/nested.json b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireJsonTest/nested.json
new file mode 100644
index 000000000..25ef26a10
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireJsonTest/nested.json
@@ -0,0 +1 @@
+{ num: 7, obj: { val1: "hello", num: 77 } }
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireJsonTest/package.json b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireJsonTest/package.json
new file mode 100644
index 000000000..bdc5b0b32
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireJsonTest/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "test2",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "poc-lettersonly-gm": "^1.0.1",
+ "validator": "^5.5.0"
+ }
+}
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireCoreModules.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireCoreModules.js
new file mode 100644
index 000000000..f52cc51f3
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireCoreModules.js
@@ -0,0 +1,2 @@
+var util = require('util');
+var util = require('https');
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireDynamic/index.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireDynamic/index.js
new file mode 100644
index 000000000..9c633263a
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireDynamic/index.js
@@ -0,0 +1,9 @@
+var lib1 = myRequire('lib1', true);
+lib1();
+
+function myRequire(name, local) {
+ var prefix = '';
+ if (local) prefix = './';
+
+ return require(prefix+name);
+}
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireDynamic/lib1.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireDynamic/lib1.js
new file mode 100644
index 000000000..f850bad98
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireDynamic/lib1.js
@@ -0,0 +1,5 @@
+var lib2 = require('./lib2');
+
+module.exports = function lib1() {
+ lib2();
+};
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireDynamic/lib2.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireDynamic/lib2.js
new file mode 100644
index 000000000..5239b7581
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireDynamic/lib2.js
@@ -0,0 +1,5 @@
+var lib1 = require('./lib1');
+
+module.exports = function lib2() {
+ lib1();
+};
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/index.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/index.js
new file mode 100644
index 000000000..3a91de887
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/index.js
@@ -0,0 +1 @@
+require('lib');
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/node_modules/lib/helper.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/node_modules/lib/helper.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/node_modules/lib/index.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/node_modules/lib/index.js
new file mode 100644
index 000000000..1783bb4d7
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/node_modules/lib/index.js
@@ -0,0 +1,2 @@
+require('./helper');
+require('sublib');
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/node_modules/lib/node_modules/sublib/package.json b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/node_modules/lib/node_modules/sublib/package.json
new file mode 100644
index 000000000..23de57254
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/node_modules/lib/node_modules/sublib/package.json
@@ -0,0 +1,3 @@
+{
+ "main": "sublib.js"
+}
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/node_modules/lib/node_modules/sublib/sublib.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/node_modules/lib/node_modules/sublib/sublib.js
new file mode 100644
index 000000000..fdd17f6a1
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/node_modules/lib/node_modules/sublib/sublib.js
@@ -0,0 +1 @@
+require('lib2');
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/node_modules/lib2/index.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/node_modules/lib2/index.js
new file mode 100644
index 000000000..ab015498b
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireNodeModules/node_modules/lib2/index.js
@@ -0,0 +1,2 @@
+module.exports = function lib2() {
+};
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireSimple/index.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireSimple/index.js
new file mode 100644
index 000000000..2c3e99142
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireSimple/index.js
@@ -0,0 +1,2 @@
+var mod = require('./mod');
+mod.exec();
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireSimple/mod.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireSimple/mod.js
new file mode 100644
index 000000000..b27726676
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireSimple/mod.js
@@ -0,0 +1,8 @@
+function SomeClass() {
+ this.hello = function hello() {}
+}
+
+exports.exec = function exec() {
+ var c = new SomeClass();
+ c.hello();
+};
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireStaticCircular/index.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireStaticCircular/index.js
new file mode 100644
index 000000000..430171fc2
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireStaticCircular/index.js
@@ -0,0 +1,2 @@
+var lib1 = require('./lib1');
+lib1();
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireStaticCircular/lib1.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireStaticCircular/lib1.js
new file mode 100644
index 000000000..f850bad98
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireStaticCircular/lib1.js
@@ -0,0 +1,5 @@
+var lib2 = require('./lib2');
+
+module.exports = function lib1() {
+ lib2();
+};
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireStaticCircular/lib2.js b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireStaticCircular/lib2.js
new file mode 100644
index 000000000..5239b7581
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs.test/testdata/NodejsRequireTargetSelectorResolve/requireStaticCircular/lib2.js
@@ -0,0 +1,5 @@
+var lib1 = require('./lib1');
+
+module.exports = function lib2() {
+ lib1();
+};
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs/.classpath b/com.ibm.wala.cast.js.nodejs/.classpath
new file mode 100644
index 000000000..c3f719862
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs/.classpath
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/com.ibm.wala.cast.js.nodejs/.project b/com.ibm.wala.cast.js.nodejs/.project
new file mode 100644
index 000000000..9f32287bd
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs/.project
@@ -0,0 +1,23 @@
+
+
+ com.ibm.wala.cast.js.nodejs
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.m2e.core.maven2Nature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/com.ibm.wala.cast.js.nodejs/build.xml b/com.ibm.wala.cast.js.nodejs/build.xml
new file mode 100644
index 000000000..34d20bdc2
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs/build.xml
@@ -0,0 +1,39 @@
+
+
+ WALA Nodejs Front-end
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs/dat/core-modules/.eslintrc b/com.ibm.wala.cast.js.nodejs/dat/core-modules/.eslintrc
new file mode 100644
index 000000000..8fb98b7c1
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs/dat/core-modules/.eslintrc
@@ -0,0 +1,4 @@
+rules:
+ # Custom rules in tools/eslint-rules
+ require-buffer: 2
+ buffer-constructor: 2
diff --git a/com.ibm.wala.cast.js.nodejs/dat/core-modules/.gitignore b/com.ibm.wala.cast.js.nodejs/dat/core-modules/.gitignore
new file mode 100644
index 000000000..4c43fe68f
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs/dat/core-modules/.gitignore
@@ -0,0 +1 @@
+*.js
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs/dat/core-modules/.gitkeep b/com.ibm.wala.cast.js.nodejs/dat/core-modules/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/com.ibm.wala.cast.js.nodejs/dat/extended-prologue.js b/com.ibm.wala.cast.js.nodejs/dat/extended-prologue.js
new file mode 100644
index 000000000..b808bdd4c
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs/dat/extended-prologue.js
@@ -0,0 +1,13 @@
+var timerId = 0;
+
+function setTimeout(cb, t) {
+ cb();
+ return timerId++;
+}
+
+function setInterval(cb, t) {
+ return setTimeout(function () {
+ cb();
+ setInterval(cb, t);
+ }, t);
+}
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs/dat/json-wrapper.js b/com.ibm.wala.cast.js.nodejs/dat/json-wrapper.js
new file mode 100644
index 000000000..1d3a55fb7
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs/dat/json-wrapper.js
@@ -0,0 +1,3 @@
+(function jsonModule() {
+ return /*/ WALA-INSERT-CODE-HERE /*/;
+})();
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs/dat/module-wrapper.js b/com.ibm.wala.cast.js.nodejs/dat/module-wrapper.js
new file mode 100644
index 000000000..8d866e384
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs/dat/module-wrapper.js
@@ -0,0 +1,12 @@
+(function nodejsModule() {
+ var module = {
+ require: function __WALA__require(id) {},
+ exports: {}
+ };
+
+ (function moduleSource(exports, require, module, __filename, __dirname) {
+ /*/ WALA-INSERT-CODE-HERE /*/
+ })(module.exports, module.require, module, '/*/ WALA-INSERT-FILENAME-HERE /*/', '/*/ WALA-INSERT-DIRNAME-HERE /*/');
+
+ return module.exports;
+})();
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs/pom.xml b/com.ibm.wala.cast.js.nodejs/pom.xml
new file mode 100644
index 000000000..33dd6fc40
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs/pom.xml
@@ -0,0 +1,34 @@
+
+ 4.0.0
+ com.ibm.wala.cast.js.nodejs
+ com.ibm.wala.cast.js.nodejs
+ 0.0.1-SNAPSHOT
+
+ src
+
+
+ dat
+
+ **/*.java
+
+
+
+
+
+ maven-compiler-plugin
+ 3.3
+
+
+ 1.8
+
+
+
+
+
+
+ org.json
+ json
+ 20160212
+
+
+
\ No newline at end of file
diff --git a/com.ibm.wala.cast.js.nodejs/src/com/ibm/wala/cast/js/nodejs/NodejsCallGraphBuilderUtil.java b/com.ibm.wala.cast.js.nodejs/src/com/ibm/wala/cast/js/nodejs/NodejsCallGraphBuilderUtil.java
new file mode 100644
index 000000000..a763ec263
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs/src/com/ibm/wala/cast/js/nodejs/NodejsCallGraphBuilderUtil.java
@@ -0,0 +1,114 @@
+/******************************************************************************
+ * Copyright (c) 2002 - 2016 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:
+ * Brian Pfretzschner - initial implementation
+ *****************************************************************************/
+package com.ibm.wala.cast.js.nodejs;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+
+import com.ibm.wala.cast.ipa.callgraph.CAstAnalysisScope;
+import com.ibm.wala.cast.ipa.callgraph.CAstCallGraphUtil;
+import com.ibm.wala.cast.ipa.callgraph.StandardFunctionTargetSelector;
+import com.ibm.wala.cast.ir.ssa.AstIRFactory;
+import com.ibm.wala.cast.js.ipa.callgraph.JSAnalysisOptions;
+import com.ibm.wala.cast.js.ipa.callgraph.JSCFABuilder;
+import com.ibm.wala.cast.js.ipa.callgraph.JSCallGraphUtil;
+import com.ibm.wala.cast.js.ipa.callgraph.JSZeroOrOneXCFABuilder;
+import com.ibm.wala.cast.js.ipa.callgraph.JavaScriptEntryPoints;
+import com.ibm.wala.cast.js.ipa.callgraph.PropertyNameContextSelector;
+import com.ibm.wala.cast.js.loader.JavaScriptLoader;
+import com.ibm.wala.cast.js.loader.JavaScriptLoaderFactory;
+import com.ibm.wala.cast.js.translator.CAstRhinoTranslatorFactory;
+import com.ibm.wala.cast.js.translator.JavaScriptTranslatorFactory;
+import com.ibm.wala.classLoader.IMethod;
+import com.ibm.wala.classLoader.Language;
+import com.ibm.wala.classLoader.SourceFileModule;
+import com.ibm.wala.classLoader.SourceModule;
+import com.ibm.wala.ipa.callgraph.AnalysisCache;
+import com.ibm.wala.ipa.callgraph.ContextSelector;
+import com.ibm.wala.ipa.callgraph.Entrypoint;
+import com.ibm.wala.ipa.callgraph.MethodTargetSelector;
+import com.ibm.wala.ipa.callgraph.propagation.PropagationCallGraphBuilder;
+import com.ibm.wala.ipa.callgraph.propagation.cfa.ZeroXInstanceKeys;
+import com.ibm.wala.ipa.cha.ClassHierarchy;
+import com.ibm.wala.ipa.cha.IClassHierarchy;
+import com.ibm.wala.ssa.IRFactory;
+import com.ibm.wala.util.CancelException;
+import com.ibm.wala.util.WalaException;
+
+/**
+ * @author Brian Pfretzschner
+ */
+public class NodejsCallGraphBuilderUtil extends JSCallGraphUtil {
+
+ public static PropagationCallGraphBuilder makeCGBuilder(File mainFile)
+ throws IOException, IllegalArgumentException, CancelException, WalaException {
+ return makeCGBuilder(mainFile.getParentFile(), mainFile);
+ }
+
+ public static PropagationCallGraphBuilder makeCGBuilder(File workingDir, File mainFile)
+ throws IOException, IllegalArgumentException, CancelException, WalaException {
+ JavaScriptTranslatorFactory translatorFactory = new CAstRhinoTranslatorFactory();
+ JSCallGraphUtil.setTranslatorFactory(translatorFactory);
+
+ Language language = JavaScriptLoader.JS;
+ Collection languages = Collections.singleton(language);
+
+ IRFactory irFactory = new AstIRFactory.AstDefaultIRFactory();
+ AnalysisCache cache = new AnalysisCache(irFactory);
+
+ JavaScriptLoaderFactory loaders = new JavaScriptLoaderFactory(translatorFactory, null);
+
+ SourceFileModule mainSourceModule = CAstCallGraphUtil.makeSourceModule(mainFile.toURI().toURL(),
+ mainFile.getName());
+ String mainFileClassName = NodejsRequiredSourceModule.convertFileToClassName(workingDir, mainFile);
+
+ SourceModule[] files = new SourceModule[] {
+ JSCallGraphUtil.getPrologueFile("prologue.js"),
+ JSCallGraphUtil.getPrologueFile("extended-prologue.js"),
+ new NodejsRequiredSourceModule(mainFileClassName, mainFile, mainSourceModule) };
+
+ CAstAnalysisScope scope = new CAstAnalysisScope(files, loaders, languages);
+
+ IClassHierarchy cha = ClassHierarchy.make(scope, loaders, language, null);
+ com.ibm.wala.cast.js.util.Util.checkForFrontEndErrors(cha);
+
+ // Make Script Roots
+ Iterable roots = new JavaScriptEntryPoints(cha, loaders.getTheLoader());
+
+ // Make Options
+ JSAnalysisOptions options = new JSAnalysisOptions(scope, roots);
+ options.setUseConstantSpecificKeys(true);
+ options.setUseStacksForLexicalScoping(true);
+ options.setHandleCallApply(true);
+ // Important to be able to identify what file are required
+ options.setTraceStringConstants(true);
+
+ com.ibm.wala.ipa.callgraph.impl.Util.addDefaultSelectors(options, cha);
+
+ MethodTargetSelector baseSelector = new StandardFunctionTargetSelector(cha, options.getMethodTargetSelector());
+ NodejsRequireTargetSelector requireTargetSelector = new NodejsRequireTargetSelector(workingDir, baseSelector);
+ options.setSelector(requireTargetSelector);
+
+ JSCFABuilder builder = new JSZeroOrOneXCFABuilder(cha, options, cache, null, null,
+ ZeroXInstanceKeys.ALLOCATIONS, true);
+
+ // A little hacky, but the instance of RequireTargetSelector is required to build the CallGraphBuilder
+ // and the RequireTargetSelector also needs the CallGraphBuilder instance.
+ requireTargetSelector.setCallGraphBuilder(builder);
+
+ ContextSelector contextSelector = new PropertyNameContextSelector(cache, 2, builder.getContextSelector());
+ builder.setContextSelector(contextSelector);
+
+ return builder;
+ }
+}
diff --git a/com.ibm.wala.cast.js.nodejs/src/com/ibm/wala/cast/js/nodejs/NodejsRequireTargetSelector.java b/com.ibm.wala.cast.js.nodejs/src/com/ibm/wala/cast/js/nodejs/NodejsRequireTargetSelector.java
new file mode 100644
index 000000000..06ba07051
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs/src/com/ibm/wala/cast/js/nodejs/NodejsRequireTargetSelector.java
@@ -0,0 +1,343 @@
+/******************************************************************************
+ * Copyright (c) 2002 - 2016 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:
+ * Brian Pfretzschner - initial implementation
+ *****************************************************************************/
+package com.ibm.wala.cast.js.nodejs;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.commons.io.FileUtils;
+import org.json.JSONObject;
+
+import com.ibm.wala.cast.js.ipa.callgraph.JSCallGraphUtil;
+import com.ibm.wala.cast.js.loader.JavaScriptLoader;
+import com.ibm.wala.cast.js.ssa.JavaScriptInvoke;
+import com.ibm.wala.cast.js.types.JavaScriptMethods;
+import com.ibm.wala.cast.js.types.JavaScriptTypes;
+import com.ibm.wala.classLoader.CallSiteReference;
+import com.ibm.wala.classLoader.IClass;
+import com.ibm.wala.classLoader.IMethod;
+import com.ibm.wala.classLoader.SourceFileModule;
+import com.ibm.wala.classLoader.SourceModule;
+import com.ibm.wala.ipa.callgraph.CGNode;
+import com.ibm.wala.ipa.callgraph.MethodTargetSelector;
+import com.ibm.wala.ipa.callgraph.propagation.ConcreteTypeKey;
+import com.ibm.wala.ipa.callgraph.propagation.ConstantKey;
+import com.ibm.wala.ipa.callgraph.propagation.InstanceKey;
+import com.ibm.wala.ipa.callgraph.propagation.PointerAnalysis;
+import com.ibm.wala.ipa.callgraph.propagation.PointerKey;
+import com.ibm.wala.ipa.callgraph.propagation.PropagationCallGraphBuilder;
+import com.ibm.wala.ssa.IR;
+import com.ibm.wala.ssa.SSAAbstractInvokeInstruction;
+import com.ibm.wala.types.TypeReference;
+import com.ibm.wala.util.collections.HashMapFactory;
+import com.ibm.wala.util.collections.HashSetFactory;
+import com.ibm.wala.util.intset.OrdinalSet;
+import com.ibm.wala.util.ssa.ClassLookupException;
+
+/**
+ * This class is used by WALA internals to resolve to what functions a call
+ * could potentially invoke.
+ *
+ * @author Brian Pfretzschner
+ */
+public class NodejsRequireTargetSelector implements MethodTargetSelector {
+
+ private File rootDir;
+ private MethodTargetSelector base;
+ private PropagationCallGraphBuilder builder;
+
+ private HashMap previouslyRequired = HashMapFactory.make();
+
+ public NodejsRequireTargetSelector(File rootDir, MethodTargetSelector base) {
+ this.rootDir = rootDir;
+ this.base = base;
+ }
+
+ public void setCallGraphBuilder(PropagationCallGraphBuilder builder) {
+ this.builder = builder;
+ }
+
+ /**
+ * Basic idea: If the called method is named "__WALA__require", it is most likely
+ * the require-function mock from the module-wrapper. To figure out what file
+ * shall be required, pointer analysis is used to identify strings that can
+ * flow into the require call. That file is than loaded, wrapped into the
+ * module wrapper and returned as method that will be invoked. Therefore,
+ * there will never be an call graph edge to the require function call,
+ * the require function is replaced by the file that is included through
+ * the require call.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public IMethod getCalleeTarget(CGNode caller, CallSiteReference site, IClass receiver) {
+ PointerAnalysis pointerAnalysis = builder.getPointerAnalysis();
+ JavaScriptLoader jsLoader = (JavaScriptLoader) builder.getClassHierarchy().getLoader(JavaScriptTypes.jsLoader);
+
+ IMethod calledMethod = base.getCalleeTarget(caller, site, receiver);
+
+ if (calledMethod != null && calledMethod.getDeclaringClass().toString().endsWith("/__WALA__require")) {
+ JavaScriptInvoke callInstr = getInvokeInstruction(caller, site);
+
+ Set targets = getRequireTargets(pointerAnalysis, caller, callInstr);
+ if (targets.size() == 0) {
+ // There is no possible call target
+ throw new RuntimeException("No require target found in method: "+caller.getMethod());
+ }
+
+ for (String target : targets) {
+ try {
+ File workingDir = new File(receiver.getSourceFileName()).getParentFile();
+ SourceModule sourceModule = resolve(rootDir, workingDir, target);
+ if (previouslyRequired.containsKey(sourceModule.getClassName())) {
+ return previouslyRequired.get(sourceModule.getClassName());
+ }
+
+ String className = "L" + sourceModule.getClassName() + "/nodejsModule";
+ if (sourceModule instanceof NodejsRequiredSourceModule
+ && ((NodejsRequiredSourceModule) sourceModule).getFile().toString().endsWith(".json")) {
+ className = "L" + sourceModule.getClassName() + "/jsonModule";
+ }
+
+ JSCallGraphUtil.loadAdditionalFile(builder.getClassHierarchy(), jsLoader, sourceModule);
+ IClass script = builder.getClassHierarchy()
+ .lookupClass(TypeReference.findOrCreate(jsLoader.getReference(), className));
+
+ IMethod method = script.getMethod(JavaScriptMethods.fnSelector);
+ previouslyRequired.put(sourceModule.getClassName(), method);
+
+ return method;
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ return calledMethod;
+ }
+
+ private JavaScriptInvoke getInvokeInstruction(CGNode caller, CallSiteReference site) {
+ IR callerIR = caller.getIR();
+ SSAAbstractInvokeInstruction callInstrs[] = callerIR.getCalls(site);
+ assert callInstrs.length == 1;
+ return (JavaScriptInvoke) callInstrs[0];
+ }
+
+ private Set getRequireTargets(PointerAnalysis pointerAnalysis, CGNode caller,
+ JavaScriptInvoke callInstr) {
+ HashSet set = HashSetFactory.make();
+
+ PointerKey pk = builder.getPointerKeyForLocal(caller, callInstr.getUse(2));
+ OrdinalSet instanceKeys = pointerAnalysis.getPointsToSet(pk);
+
+ for (InstanceKey instanceKey : instanceKeys) {
+ if (instanceKey instanceof ConstantKey>) {
+ Object value = ((ConstantKey>) instanceKey).getValue();
+ if (value instanceof String) {
+ set.add((String) value);
+ }
+ else {
+ System.err.println("NodejsRequireTargetSelector: Unexpected value: " + value);
+ return HashSetFactory.make();
+ }
+ }
+ else if (instanceKey instanceof ConcreteTypeKey) {
+ // Cannot do anything with this information...
+ }
+ else {
+ System.err.println("NodejsRequireTargetSelector: Unexpected instanceKey: " + instanceKey.getClass() + " -- " + instanceKey);
+ return HashSetFactory.make();
+ }
+ }
+
+ return set;
+ }
+
+ /**
+ * Implements the Nodejs require.resolve algorithm,
+ * see https://nodejs.org/api/modules.html#modules_all_together
+ *
+ * require(X) from module at path Y
+ * 1. If X is a core module,
+ * a. return the core module
+ * b. STOP
+ * 2. If X begins with './' or '/' or '../'
+ * a. LOAD_AS_FILE(Y + X)
+ * b. LOAD_AS_DIRECTORY(Y + X)
+ * 3. LOAD_NODE_MODULES(X, dirname(Y))
+ * 4. THROW "not found"
+ *
+ * @param dir Y in the pseudo algorithm
+ * @param target X in the pseudo algorithm
+ * @return
+ * @throws IOException
+ */
+ public static SourceFileModule resolve(File rootDir, File dir, String target) throws IOException {
+ if (NodejsRequiredCoreModule.isCoreModule(target))
+ return NodejsRequiredCoreModule.make(target);
+
+ if (target.startsWith("./") || target.startsWith("/") || target.startsWith("../")) {
+ SourceFileModule module = loadAsFile(rootDir, new File(dir, target));
+ if (module != null) return module;
+
+ module = loadAsDirectory(rootDir, new File(dir, target));
+ if (module != null) return module;
+ }
+
+ SourceFileModule module = loadNodeModules(rootDir, dir, target);
+ if (module != null) return module;
+
+ throw new ClassLookupException("Required module not found: "+target+" in "+dir);
+ }
+
+ /**
+ * LOAD_AS_FILE(X)
+ * 1. If X is a file, load X as JavaScript text. STOP
+ * 2. If X.js is a file, load X.js as JavaScript text. STOP
+ * 3. If X.json is a file, parse X.json to a JavaScript Object. STOP
+ * 4. If X.node is a file, load X.node as binary addon. STOP
+ *
+ * @param f
+ * @return
+ * @throws IOException
+ */
+ private static SourceFileModule loadAsFile(File rootDir, File f) throws IOException {
+ // 1.
+ if (f.isFile())
+ return NodejsRequiredSourceModule.make(rootDir, f);
+
+ // 2.
+ File jsFile = new File(f+".js");
+ if (jsFile.isFile())
+ return NodejsRequiredSourceModule.make(rootDir, jsFile);
+
+ // 3.
+ File jsonFile = new File(f+".json");
+ if (jsonFile.isFile())
+ return NodejsRequiredSourceModule.make(rootDir, jsonFile);
+
+ // Skip 4. step
+
+ return null;
+ }
+
+ /**
+ * LOAD_AS_DIRECTORY(X)
+ * 1. If X/package.json is a file,
+ * a. Parse X/package.json, and look for "main" field.
+ * b. let M = X + (json main field)
+ * c. LOAD_AS_FILE(M)
+ * 2. If X/index.js is a file, load X/index.js as JavaScript text. STOP
+ * 3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
+ * 4. If X/index.node is a file, load X/index.node as binary addon. STOP
+ *
+ * @param d
+ * @return
+ * @throws IOException
+ */
+ private static SourceFileModule loadAsDirectory(File rootDir, File d) throws IOException {
+ // 1.
+ File packageJsonFile = new File(d, "package.json");
+ if (packageJsonFile.isFile()) {
+ // 1.a.
+ String packageJsonContent = FileUtils.readFileToString(packageJsonFile);
+ JSONObject packageJson = new JSONObject(packageJsonContent);
+ if (packageJson.has("main")) {
+ String mainFileName = packageJson.getString("main");
+
+ // 1.b.
+ File mainFile = new File(d, mainFileName);
+
+ // 1.c.
+ return loadAsFile(rootDir, mainFile);
+ }
+ }
+
+ // 2.
+ File jsFile = new File(d, "index.js");
+ if (jsFile.isFile())
+ return NodejsRequiredSourceModule.make(rootDir, jsFile);
+
+ // 3.
+ File jsonFile = new File(d, "index.json");
+ if (jsonFile.isFile())
+ return NodejsRequiredSourceModule.make(rootDir, jsonFile);
+
+ // Skip 4. step
+
+ return null;
+ }
+
+ /**
+ * LOAD_NODE_MODULES(X, START)
+ * 1. let DIRS=NODE_MODULES_PATHS(START)
+ * 2. for each DIR in DIRS:
+ * a. LOAD_AS_FILE(DIR/X)
+ * b. LOAD_AS_DIRECTORY(DIR/X)
+ *
+ * @param dir
+ * @param target
+ * @return
+ * @throws IOException
+ */
+ private static SourceFileModule loadNodeModules(File rootDir, File d, String target) throws IOException {
+ List dirs = nodeModulePaths(rootDir, d);
+ for (File dir : dirs) {
+ SourceFileModule module = loadAsFile(rootDir, new File(dir, target));
+ if (module != null) return module;
+
+ module = loadAsDirectory(rootDir, new File(dir, target));
+ if (module != null) return module;
+ }
+
+ return null;
+ }
+
+ /**
+ * NODE_MODULES_PATHS(START)
+ * 1. let PARTS = path split(START)
+ * 2. let I = count of PARTS - 1
+ * 3. let DIRS = []
+ * 4. while I >= 0,
+ * a. if PARTS[I] = "node_modules" CONTINUE
+ * b. DIR = path join(PARTS[0 .. I] + "node_modules")
+ * c. DIRS = DIRS + DIR
+ * d. let I = I - 1
+ * 5. return DIRS
+ *
+ * @param d
+ * @return
+ * @throws IOException
+ */
+ private static List nodeModulePaths(File rootDir, File d) throws IOException {
+ LinkedList dirs = new LinkedList<>();
+
+ while (d.getCanonicalPath().startsWith(rootDir.getCanonicalPath()) && d.toPath().getNameCount() > 0) {
+ // 4.a.
+ if (!d.getName().equals("node_modules")) {
+ // 4.b. and 4.c.
+ dirs.add(new File(d, "node_modules"));
+ }
+
+ // 4.d.
+ d = d.getParentFile();
+ }
+
+ return dirs;
+ }
+
+}
diff --git a/com.ibm.wala.cast.js.nodejs/src/com/ibm/wala/cast/js/nodejs/NodejsRequiredCoreModule.java b/com.ibm.wala.cast.js.nodejs/src/com/ibm/wala/cast/js/nodejs/NodejsRequiredCoreModule.java
new file mode 100644
index 000000000..779fcdaab
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs/src/com/ibm/wala/cast/js/nodejs/NodejsRequiredCoreModule.java
@@ -0,0 +1,60 @@
+/******************************************************************************
+ * Copyright (c) 2002 - 2016 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:
+ * Brian Pfretzschner - initial implementation
+ *****************************************************************************/
+package com.ibm.wala.cast.js.nodejs;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Set;
+
+import org.apache.commons.io.FilenameUtils;
+
+import com.ibm.wala.cast.ipa.callgraph.CAstCallGraphUtil;
+import com.ibm.wala.classLoader.SourceFileModule;
+import com.ibm.wala.util.collections.HashSetFactory;
+
+/**
+ * @author Brian Pfretzschner
+ */
+public class NodejsRequiredCoreModule extends NodejsRequiredSourceModule {
+
+ /**
+ * Core modules list for Nodejs v6.2.2
+ * https://github.com/nodejs/node/blob/v6.2.2/lib/internal/module.js
+ */
+ private static final Set CORE_MODULES = HashSetFactory.make(Arrays.asList(
+ "assert", "buffer", "child_process", "cluster", "crypto", "dgram", "dns", "domain", "events", "fs", "http",
+ "https", "net", "os", "path", "punycode", "querystring", "readline", "repl", "stream", "string_decoder",
+ "tls", "tty", "url", "util", "v8", "vm", "zlib",
+
+ // Non-public files
+ "timers", "constants", "freelist", "smalloc",
+ "_debugger", "_http_agent", "_http_client", "_http_common", "_http_incoming", "_http_outgoing",
+ "_http_server", "_linklist", "_stream_duplex", "_stream_passthrough", "_stream_readable",
+ "_stream_transform", "_stream_writable", "_tls_common", "_tls_legacy", "_tls_wrap"));
+
+ private final static File CORE_MODULES_FOLDER = new File(NodejsRequiredCoreModule.class.getClassLoader().getResource("core-modules").getPath());
+
+ protected NodejsRequiredCoreModule(File f, SourceFileModule clonedFrom) throws IOException {
+ super(FilenameUtils.getBaseName(f.getName()), f, clonedFrom);
+ }
+
+ public static NodejsRequiredCoreModule make(String name) throws IOException {
+ File file = new File(CORE_MODULES_FOLDER, name+".js");
+ SourceFileModule sourceFileModule = CAstCallGraphUtil.makeSourceModule(file.toURI().toURL(), file.getName());
+ return new NodejsRequiredCoreModule(file, sourceFileModule);
+ }
+
+ public static boolean isCoreModule(String name) {
+ return CORE_MODULES.contains(name);
+ }
+
+}
diff --git a/com.ibm.wala.cast.js.nodejs/src/com/ibm/wala/cast/js/nodejs/NodejsRequiredSourceModule.java b/com.ibm.wala.cast.js.nodejs/src/com/ibm/wala/cast/js/nodejs/NodejsRequiredSourceModule.java
new file mode 100644
index 000000000..9d08f6e1b
--- /dev/null
+++ b/com.ibm.wala.cast.js.nodejs/src/com/ibm/wala/cast/js/nodejs/NodejsRequiredSourceModule.java
@@ -0,0 +1,149 @@
+/******************************************************************************
+ * Copyright (c) 2002 - 2016 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:
+ * Brian Pfretzschner - initial implementation
+ *****************************************************************************/
+package com.ibm.wala.cast.js.nodejs;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+
+import com.ibm.wala.cast.ipa.callgraph.CAstCallGraphUtil;
+import com.ibm.wala.classLoader.SourceFileModule;
+import com.ibm.wala.util.debug.Assertions;
+
+/**
+ * This class is intended to be used whenever a JavaScript module is dynamically
+ * required by JavaScript (CommonJS). The required file will be loaded and
+ * wrapped in a function call to simulate the real behavior of a CommonJS
+ * environment. The resulting function will be named GLOBAL_PREFIX + relative
+ * file-name. To retrieve the final function name, use getFunctioName().
+ *
+ * @author Brian Pfretzschner
+ */
+public class NodejsRequiredSourceModule extends SourceFileModule {
+
+ private final static String MODULE_WRAPPER_FILENAME = "module-wrapper.js";
+ private final static String JSON_WRAPPER_FILENAME = "json-wrapper.js";
+
+ private final static String FILENAME_PLACEHOLDER = "/*/ WALA-INSERT-FILENAME-HERE /*/";
+ private final static String DIRNAME_PLACEHOLDER = "/*/ WALA-INSERT-DIRNAME-HERE /*/";
+ private final static String CODE_PLACEHOLDER = "/*/ WALA-INSERT-CODE-HERE /*/";
+
+ private static String MODULE_WRAPPER_SOURCE = null;
+ private static String JSON_WRAPPER_SOURCE = null;
+
+ private final String className;
+
+ /**
+ * @param workingDir
+ * Must be a direct or indirect parent folder of file f.
+ * @param f
+ * Must be a file located below folder workingDir.
+ * @param clonedFrom
+ * @throws IOException
+ */
+ protected NodejsRequiredSourceModule(String className, File f, SourceFileModule clonedFrom) throws IOException {
+ super(f, clonedFrom);
+
+ // Generate className based on the given file name
+ this.className = className;
+ assert className.matches("[a-zA-Z_$][0-9a-zA-Z_$]*") : "Invalid className: " + className;
+
+ if (MODULE_WRAPPER_SOURCE == null || JSON_WRAPPER_SOURCE == null) {
+ // Populate the cache that hold the module wrapper source code
+ loadWrapperSources();
+ }
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ InputStream inputStream = super.getInputStream();
+ String moduleSource = null;
+ try {
+ moduleSource = IOUtils.toString(inputStream);
+ } catch (IOException e) {
+ Assertions.UNREACHABLE(e.getMessage());
+ }
+
+ String wrapperSource = null;
+ String ext = FilenameUtils.getExtension(getFile().toString()).toLowerCase();
+ if (ext.equals("js")) {
+ // JS file -> use module wrapper
+ wrapperSource = MODULE_WRAPPER_SOURCE;
+ }
+ else if (ext.equals("json")) {
+ // JSON file -> use JSON wrapper
+ wrapperSource = JSON_WRAPPER_SOURCE;
+ }
+ else {
+ // No clue -> try module wrapper
+ System.err.println("NodejsRequiredSourceModule: Unsupported file type ("+ext+"), continue anyway.");
+ wrapperSource = MODULE_WRAPPER_SOURCE;
+ }
+
+ String wrappedModuleSource = wrapperSource
+ .replace(FILENAME_PLACEHOLDER, getFile().getName())
+ .replace(DIRNAME_PLACEHOLDER, getFile().getParent().toString())
+ .replace(CODE_PLACEHOLDER, moduleSource);
+
+ return IOUtils.toInputStream(wrappedModuleSource);
+ }
+
+ @Override
+ public String getClassName() {
+ return className;
+ }
+
+ private void loadWrapperSources() throws IOException {
+ MODULE_WRAPPER_SOURCE = loadWrapperSource(MODULE_WRAPPER_FILENAME);
+ JSON_WRAPPER_SOURCE = loadWrapperSource(JSON_WRAPPER_FILENAME);
+ }
+
+ private String loadWrapperSource(String filename) throws IOException {
+ URL url = NodejsRequiredSourceModule.class.getClassLoader().getResource(filename);
+ Path wrapperPath = Paths.get(url.getPath());
+ byte[] wrapperSourceBin = Files.readAllBytes(wrapperPath);
+ return new String(wrapperSourceBin, Charset.defaultCharset());
+ }
+
+ /**
+ * Generate a className based on the file name and path of the module file.
+ * The path should be encoded in the className since the file name is not unique.
+ *
+ * @param rootDir
+ * @param file
+ * @return
+ */
+ public static String convertFileToClassName(File rootDir, File file) {
+ URI normalizedWorkingDirURI = rootDir.getAbsoluteFile().toURI().normalize();
+ URI normalizedFileURI = file.getAbsoluteFile().toURI().normalize();
+ String relativePath = normalizedWorkingDirURI.relativize(normalizedFileURI).getPath();
+
+ return FilenameUtils.removeExtension(relativePath)
+ .replace("/", "_")
+ .replace("-", "__")
+ .replace(".", "__");
+ }
+
+ public static NodejsRequiredSourceModule make(File rootDir, File file) throws IOException {
+ String className = convertFileToClassName(rootDir, file);
+ SourceFileModule sourceFileModule = CAstCallGraphUtil.makeSourceModule(file.toURI().toURL(), file.getName());
+ return new NodejsRequiredSourceModule(className, file, sourceFileModule);
+ }
+}