NodeJS Support, including Ant build script to download Nodejs Core Library files

This commit is contained in:
Brian Pfretzschner 2016-06-16 11:31:30 +02:00
parent 8855f80c97
commit 9de3da19fb
37 changed files with 1068 additions and 0 deletions

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry combineaccessrules="false" kind="src" path="/com.ibm.wala.cast.js.nodejs"/>
<classpathentry combineaccessrules="false" kind="src" path="/com.ibm.wala.cast.js"/>
<classpathentry combineaccessrules="false" kind="src" path="/com.ibm.wala.core"/>
<classpathentry combineaccessrules="false" kind="src" path="/com.ibm.wala.util"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="src" path="testdata"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.ibm.wala.cast.js.nodejs.test</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -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 <brian.pfretzschner@gmail.com>
*/
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("?"));
}
}

View File

@ -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 <brian.pfretzschner@gmail.com>
*/
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"));
}
}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,3 @@
var empty = require('./empty.json');
var empty = require('./nested.json');
var empty = require('./package.json');

View File

@ -0,0 +1 @@
{ num: 7, obj: { val1: "hello", num: 77 } }

View File

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

View File

@ -0,0 +1,2 @@
var util = require('util');
var util = require('https');

View File

@ -0,0 +1,9 @@
var lib1 = myRequire('lib1', true);
lib1();
function myRequire(name, local) {
var prefix = '';
if (local) prefix = './';
return require(prefix+name);
}

View File

@ -0,0 +1,5 @@
var lib2 = require('./lib2');
module.exports = function lib1() {
lib2();
};

View File

@ -0,0 +1,5 @@
var lib1 = require('./lib1');
module.exports = function lib2() {
lib1();
};

View File

@ -0,0 +1,2 @@
require('./helper');
require('sublib');

View File

@ -0,0 +1,2 @@
module.exports = function lib2() {
};

View File

@ -0,0 +1,2 @@
var mod = require('./mod');
mod.exec();

View File

@ -0,0 +1,8 @@
function SomeClass() {
this.hello = function hello() {}
}
exports.exec = function exec() {
var c = new SomeClass();
c.hello();
};

View File

@ -0,0 +1,2 @@
var lib1 = require('./lib1');
lib1();

View File

@ -0,0 +1,5 @@
var lib2 = require('./lib2');
module.exports = function lib1() {
lib2();
};

View File

@ -0,0 +1,5 @@
var lib1 = require('./lib1');
module.exports = function lib2() {
lib1();
};

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="dat">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/com.ibm.wala.cast"/>
<classpathentry combineaccessrules="false" kind="src" path="/com.ibm.wala.cast.js"/>
<classpathentry combineaccessrules="false" kind="src" path="/com.ibm.wala.cast.js.rhino"/>
<classpathentry combineaccessrules="false" kind="src" path="/com.ibm.wala.core"/>
<classpathentry combineaccessrules="false" kind="src" path="/com.ibm.wala.util"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/com.ibm.wala.shrike"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.ibm.wala.cast.js.nodejs</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,39 @@
<project name="com.ibm.wala.cast.js.nodejs" default="download-core-libs" basedir=".">
<description>
WALA Nodejs Front-end
</description>
<property name="nodejs-release" location="nodejs.zip" />
<property name="core-modules" location="dat/core-modules" />
<target name="download-core-libs">
<!-- 0a604e92e258c5ee2752d763e50721e35053f135 is v0.12.4-release -->
<get src="https://api.github.com/repos/nodejs/node/zipball/0a604e92e258c5ee2752d763e50721e35053f135"
dest="${nodejs-release}" />
<tempfile property="unzip-dest" />
<unzip src="${nodejs-release}"
dest="${unzip-dest}">
<patternset>
<include name="*/lib/**" />
</patternset>
</unzip>
<mkdir dir="${core-modules}" />
<move todir="${core-modules}"
flatten="true">
<fileset dir="${unzip-dest}">
<include name="*/lib/*.js" />
</fileset>
</move>
<!-- Cleanup -->
<delete includeemptydirs="true">
<fileset dir="${unzip-dest}" includes="**" />
<fileset file="${nodejs-release}" />
</delete>
</target>
</project>

View File

@ -0,0 +1,4 @@
rules:
# Custom rules in tools/eslint-rules
require-buffer: 2
buffer-constructor: 2

View File

@ -0,0 +1 @@
*.js

View File

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

View File

@ -0,0 +1,3 @@
(function jsonModule() {
return /*/ WALA-INSERT-CODE-HERE /*/;
})();

View File

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

View File

@ -0,0 +1,34 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ibm.wala.cast.js.nodejs</groupId>
<artifactId>com.ibm.wala.cast.js.nodejs</artifactId>
<version>0.0.1-SNAPSHOT</version>
<build>
<sourceDirectory>src</sourceDirectory>
<resources>
<resource>
<directory>dat</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20160212</version>
</dependency>
</dependencies>
</project>

View File

@ -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 <brian.pfretzschner@gmail.com>
*/
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<Language> languages = Collections.singleton(language);
IRFactory<IMethod> 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<Entrypoint> 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;
}
}

View File

@ -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 <brian.pfretzschner@gmail.com>
*/
public class NodejsRequireTargetSelector implements MethodTargetSelector {
private File rootDir;
private MethodTargetSelector base;
private PropagationCallGraphBuilder builder;
private HashMap<String, IMethod> 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<InstanceKey> 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<String> 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<String> getRequireTargets(PointerAnalysis<InstanceKey> pointerAnalysis, CGNode caller,
JavaScriptInvoke callInstr) {
HashSet<String> set = HashSetFactory.make();
PointerKey pk = builder.getPointerKeyForLocal(caller, callInstr.getUse(2));
OrdinalSet<InstanceKey> 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<File> 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<File> nodeModulePaths(File rootDir, File d) throws IOException {
LinkedList<File> 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;
}
}

View File

@ -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 <brian.pfretzschner@gmail.com>
*/
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<String> 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);
}
}

View File

@ -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 <brian.pfretzschner@gmail.com>
*/
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);
}
}