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 + 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); + } +}