WALA/com.ibm.wala.cast.js.nodejs/src/com/ibm/wala/cast/js/nodejs/NodejsRequiredSourceModule....

146 lines
5.2 KiB
Java

/******************************************************************************
* 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 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;
import com.ibm.wala.util.io.Streams;
/**
* 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 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() {
String moduleSource = null;
try (final InputStream inputStream = super.getInputStream()) {
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;
}
@Override
public String getName() {
return className;
}
private static void loadWrapperSources() throws IOException {
MODULE_WRAPPER_SOURCE = loadWrapperSource(MODULE_WRAPPER_FILENAME);
JSON_WRAPPER_SOURCE = loadWrapperSource(JSON_WRAPPER_FILENAME);
}
private static String loadWrapperSource(String filename) throws IOException {
try (final InputStream url = NodejsRequiredSourceModule.class.getClassLoader().getResourceAsStream(filename)) {
return new String(Streams.inputStream2ByteArray(url));
}
}
/**
* 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
*/
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);
}
}