Initial commit 'crosslanguage analysis'.
This commit is contained in:
parent
ece503aaec
commit
28be30edec
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage
|
||||
|
||||
import java.io.File
|
||||
import eu.aniketos.dasca.crosslanguage.builder._
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import java.lang.management.ManagementFactory
|
||||
import eu.aniketos.dasca.crosslanguage.util.Util
|
||||
import org.slf4j.LoggerFactory
|
||||
import com.typesafe.scalalogging.Logger
|
||||
|
||||
object Main {
|
||||
def main(args: Array[String]): Unit = {
|
||||
if (args.length < 1 || (args.length < 2 && args(0).charAt(0) == '-')) {
|
||||
println("You must at least provide the path to the apk!")
|
||||
return
|
||||
}
|
||||
|
||||
val apk = if (args(0).charAt(0) == '-') new File(args(1)) else new File(args(0))
|
||||
|
||||
if (!apk.exists() || !apk.getCanonicalPath.endsWith(".apk")) {
|
||||
println("Please provide a valid apk file!")
|
||||
return
|
||||
}
|
||||
|
||||
val options = if (args(0).charAt(0) == '-') Util.argsToOptions(args(0)) else List()
|
||||
implicit val logger = Logger(LoggerFactory.getLogger(getClass.toString))
|
||||
Util.time("Creation of the Cordova unified call graph", {
|
||||
val builder = CordovaCGBuilder(apk)
|
||||
builder.setOptions(options: _*)
|
||||
builder.createCallGraph
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.builder
|
||||
|
||||
import com.ibm.wala.cast.js.html.DefaultSourceExtractor
|
||||
import com.ibm.wala.cast.js.html.DefaultSourceExtractor.HtmlCallBack
|
||||
import com.ibm.wala.cast.js.html.IUrlResolver
|
||||
import java.net.URL
|
||||
import com.ibm.wala.cast.js.html.ITag
|
||||
import org.apache.commons.io.input.BOMInputStream
|
||||
import org.apache.commons.io.ByteOrderMark
|
||||
import java.io.InputStreamReader
|
||||
import scala.io.Source
|
||||
import java.io.File
|
||||
import org.apache.commons.io.IOUtils
|
||||
import java.io.BufferedInputStream
|
||||
import scala.collection.JavaConverters._
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import java.io.IOException
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.apache.commons.io.FileUtils
|
||||
import scala.collection.mutable.MutableList
|
||||
|
||||
class AllScriptsExtractor(apkUnzipDir: File) extends DefaultSourceExtractor {
|
||||
val logger = Logger(LoggerFactory.getLogger(getClass.toString))
|
||||
val extractedScripts = MutableList[URL]()
|
||||
|
||||
override def createHtmlCallback(entrypoint: URL, urlResolver: IUrlResolver) = new HtmlCallBack(entrypoint, urlResolver)
|
||||
|
||||
class HtmlCallBack(entrypoint: URL, urlResolver: IUrlResolver) extends DefaultSourceExtractor.HtmlCallBack(entrypoint, urlResolver) {
|
||||
|
||||
val AbsolutePathRegex = """.+android_asset/(.+)""".r
|
||||
val RemotePathRegex = """(https?://.+)""".r
|
||||
|
||||
override def getScriptFromUrl(urlString: String, scriptTag: ITag) = {
|
||||
try {
|
||||
// For some reason, some people use absolute script paths in their Cordova apps...
|
||||
val scriptSrc = urlString match {
|
||||
case AbsolutePathRegex(rel) => new File(new File(apkUnzipDir, "assets"), rel).toURI().toURL()
|
||||
case RemotePathRegex(urlString) => {
|
||||
val url = new URL(urlString)
|
||||
logger.debug(s"Downloading $urlString...")
|
||||
val dest = new File(new File(apkUnzipDir, "downloaded"), url.getFile)
|
||||
dest.getParentFile.mkdirs()
|
||||
val stream = url.openStream()
|
||||
FileUtils.copyInputStreamToFile(stream, dest)
|
||||
IOUtils.closeQuietly(stream)
|
||||
dest.toURI().toURL()
|
||||
}
|
||||
case _ => new URL(entrypoint, urlString)
|
||||
}
|
||||
if (!new File(scriptSrc.getFile).isFile()) {
|
||||
// Try again with all lower-case letter, otherwise files may be missed on case-sensitive file systems
|
||||
if (!new File(scriptSrc.getFile.toLowerCase()).isFile()) {
|
||||
throw new IOException(s"$scriptSrc does not exist on the file system!")
|
||||
}
|
||||
}
|
||||
extractedScripts += scriptSrc
|
||||
} catch {
|
||||
case ioe: IOException => logger.warn(s"Could not fetch script $urlString!", ioe)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,407 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.builder
|
||||
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.lang.ref.ReferenceQueue.Null
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.immutable.StringOps
|
||||
import scala.xml.XML
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.apache.commons.io.filefilter.IOFileFilter
|
||||
import org.apache.commons.io.filefilter.TrueFileFilter
|
||||
import com.ibm.wala.classLoader.IClass
|
||||
import com.ibm.wala.classLoader.IMethod
|
||||
import com.ibm.wala.dalvik.classLoader.DexIRFactory
|
||||
import com.ibm.wala.dalvik.ipa.callgraph.impl.AndroidEntryPoint
|
||||
import com.ibm.wala.dalvik.ipa.callgraph.impl.DexEntryPoint
|
||||
import com.ibm.wala.dalvik.util.AndroidEntryPointLocator
|
||||
import com.ibm.wala.dalvik.util.AndroidEntryPointLocator.LocatorFlags
|
||||
import com.ibm.wala.ipa.callgraph.AnalysisCache
|
||||
import com.ibm.wala.ipa.callgraph.AnalysisOptions
|
||||
import com.ibm.wala.ipa.callgraph.AnalysisOptions.ReflectionOptions
|
||||
import com.ibm.wala.ipa.callgraph.AnalysisScope
|
||||
import com.ibm.wala.ipa.callgraph.CallGraph
|
||||
import com.ibm.wala.ipa.callgraph.Entrypoint
|
||||
import eu.aniketos.dasca.crosslanguage.util.Util
|
||||
import com.ibm.wala.ipa.cha.ClassHierarchy
|
||||
import brut.androlib.ApkDecoder
|
||||
import spray.json._
|
||||
import spray.json.DefaultJsonProtocol._
|
||||
import com.ibm.wala.cast.js.html.WebPageLoaderFactory
|
||||
import com.ibm.wala.cast.js.translator.CAstRhinoTranslatorFactory
|
||||
import com.ibm.wala.cast.js.loader.JavaScriptLoaderFactory
|
||||
import java.net.URL
|
||||
import com.ibm.wala.cast.js.loader.JavaScriptLoader
|
||||
import com.ibm.wala.cast.js.html.WebUtil
|
||||
import com.ibm.wala.classLoader.SourceURLModule
|
||||
import com.ibm.wala.classLoader.SourceModule
|
||||
import scala.concurrent._
|
||||
import ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration._
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import scala.util.Random
|
||||
import eu.aniketos.dasca.crosslanguage.util.Util
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import com.ibm.wala.cast.js.html.jericho.JerichoHtmlParser
|
||||
import com.ibm.wala.cast.js.html.IdentityUrlResolver
|
||||
import com.ibm.wala.cast.js.html.DefaultSourceExtractor
|
||||
import scala.xml.Elem
|
||||
import org.apache.commons.io.input.BOMInputStream
|
||||
import org.apache.commons.io.ByteOrderMark
|
||||
import scala.util.matching.Regex
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import eu.aniketos.dasca.crosslanguage.builder.CordovaCGBuilder._
|
||||
import com.ibm.wala.ipa.callgraph.impl.{ Util => WalaUtil }
|
||||
import eu.aniketos.dasca.crosslanguage.util.FileMappingStore
|
||||
import eu.aniketos.dasca.crosslanguage.util.FileMapRecorder
|
||||
import com.ibm.wala.util.io.TemporaryFile
|
||||
import org.apache.commons.io.IOUtils
|
||||
import com.ibm.wala.util.io.FileUtil
|
||||
import java.nio.file.Files
|
||||
import com.ibm.wala.cast.ir.ssa.AstIRFactory
|
||||
import com.ibm.wala.cast.ipa.callgraph.CAstAnalysisScope
|
||||
import com.ibm.wala.ipa.callgraph.propagation.cfa.ZeroXInstanceKeys
|
||||
import com.ibm.wala.cast.js.ipa.callgraph.JSCallGraphUtil
|
||||
import com.ibm.wala.cast.js.ipa.callgraph.JSZeroOrOneXCFABuilder
|
||||
import java.util.Collections
|
||||
import com.ibm.wala.cast.js.callgraph.fieldbased.OptimisticCallgraphBuilder
|
||||
import com.ibm.wala.util.WalaException
|
||||
import com.ibm.wala.dalvik.util.AndroidAnalysisScope
|
||||
import java.util.jar.JarFile
|
||||
import com.ibm.wala.types.ClassLoaderReference
|
||||
import com.ibm.wala.classLoader.JarFileModule
|
||||
|
||||
object CordovaCGBuilder {
|
||||
val ExecuteSuffix = "walaexec"
|
||||
val ExclusionFile = new File("jsexclusions.txt")
|
||||
def apply(apk: File): CordovaCGBuilder = {
|
||||
val tmpApkDir = new File(System.getProperty("java.io.tmpdir"), s"${apk.getName}-${Random.alphanumeric.take(16).mkString}")
|
||||
tmpApkDir.deleteOnExit()
|
||||
CordovaCGBuilder(apk, tmpApkDir)
|
||||
}
|
||||
|
||||
def apply(apk: File, tmpApkDir: File): CordovaCGBuilder = new CordovaCGBuilder(apk, tmpApkDir)
|
||||
}
|
||||
|
||||
class CordovaCGBuilder(val apk: File, val apkUnzipDir: File) {
|
||||
val PluginsRegex = """(?s).*\.exports\s*=\s*(.+]).*""".r
|
||||
implicit val logger = Logger(LoggerFactory.getLogger(getClass.toString))
|
||||
|
||||
var mockCordovaExec = false
|
||||
var filterJavaCallSites = false
|
||||
var replacePluginDefinesAndRequires = false
|
||||
var filterJSFrameworks = false
|
||||
var preciseJS = false
|
||||
var runBuildersInParallel = false
|
||||
|
||||
def waitForFutures(fut1: Future[Any], fut2: Future[Any]) = {
|
||||
while (fut1.value.isEmpty || fut2.value.isEmpty) {
|
||||
for (value <- fut1.value if value.isFailure) {
|
||||
value.get
|
||||
}
|
||||
for (value <- fut2.value if value.isFailure) {
|
||||
value.get
|
||||
}
|
||||
Thread.sleep(500)
|
||||
}
|
||||
}
|
||||
|
||||
def createCallGraph: MergedCallGraph = {
|
||||
val mcg = if (runBuildersInParallel) {
|
||||
val javaFuture = Future { Util.time("Creation of Java Call Graph", { createJavaCallGraph }) }
|
||||
val jsFuture = Future { Util.time("Creation of JavaScript Call Graph", { createJavaScriptCallGraph }) }
|
||||
waitForFutures(javaFuture, jsFuture)
|
||||
val javaCG = javaFuture.value.get.get
|
||||
val (jsCG, xml) = jsFuture.value.get.get
|
||||
new MergedCallGraph(javaCG, jsCG, xml)
|
||||
val mcgFuture = for {
|
||||
javaCG <- javaFuture
|
||||
jsCG <- jsFuture
|
||||
} yield new MergedCallGraph(javaCG, jsCG._1, jsCG._2)
|
||||
Await.result(mcgFuture, Duration.Inf)
|
||||
} else {
|
||||
val javaCG = Util.time("Creation of Java Call Graph", { createJavaCallGraph })
|
||||
val (jsCG, xml) = Util.time("Creation of JavaScript Call Graph", { createJavaScriptCallGraph })
|
||||
new MergedCallGraph(javaCG, jsCG, xml)
|
||||
}
|
||||
|
||||
logger.info(s"The Java Call Graph has ${mcg.javaCG.getNumberOfNodes} nodes")
|
||||
logger.info(s"The JavaScript Call Graph has ${mcg.jsCG.getNumberOfNodes} nodes")
|
||||
|
||||
DalvikLineNumberCalculator.setLineNumbers(apkUnzipDir, mcg.javaCG)
|
||||
|
||||
logger.info("Connecting cross language calls...")
|
||||
mcg.connect(filterJavaCallSites, filterJSFrameworks)
|
||||
Util.time("Connecting the cross calls", { mcg.connect })(logger)
|
||||
|
||||
logger.info("The following calls have been found:")
|
||||
for (line <- Util.prettyPrintCrossTargets(mcg.getAllCrossTargets)) logger.info(line)
|
||||
logger.info("End of cross calls")
|
||||
mcg
|
||||
}
|
||||
|
||||
private def createJavaCallGraph = {
|
||||
val tmpAndroidJar = File.createTempFile("android", "jar")
|
||||
tmpAndroidJar.deleteOnExit()
|
||||
TemporaryFile.urlToFile(tmpAndroidJar, getClass.getClassLoader.getResource("android19.jar"))
|
||||
val scope = AndroidAnalysisScope.setUpAndroidAnalysisScope(apk.toURI(), getClass.getClassLoader.getResource("javaexclusions.txt").getFile, CordovaCGBuilder.getClass.getClassLoader())
|
||||
scope.addToScope(ClassLoaderReference.Primordial, new JarFileModule(new JarFile(tmpAndroidJar)))
|
||||
val cha = ClassHierarchy.make(scope)
|
||||
val cache = new AnalysisCache(new DexIRFactory())
|
||||
val eps = new AndroidEntryPointLocator(Set(
|
||||
LocatorFlags.INCLUDE_CALLBACKS, LocatorFlags.EP_HEURISTIC, LocatorFlags.CB_HEURISTIC).asJava)
|
||||
val pluginEntryPoints = getPluginEntryPoints(cha)
|
||||
for (ep <- pluginEntryPoints) logger.info(s"Found cordova plugin entry point: $ep")
|
||||
val entryPoints = eps.getEntryPoints(cha).asScala ++ pluginEntryPoints
|
||||
val options = new AnalysisOptions(scope, entryPoints.asJava);
|
||||
options.setReflectionOptions(ReflectionOptions.FULL);
|
||||
val cgb = WalaUtil.makeZeroCFABuilder(options, cache, cha, scope);
|
||||
cgb.makeCallGraph(options, null);
|
||||
}
|
||||
|
||||
private def getPluginEntryPoints(cha: ClassHierarchy): List[Entrypoint] = for (
|
||||
cls <- cha.iterator.asScala.toList;
|
||||
m <- cls.getDeclaredMethods.asScala if !cls.isInterface() &&
|
||||
cls.getClassLoader.getName == AnalysisScope.APPLICATION && Util.derivesFromCordovaPlugin(cls.getSuperclass);
|
||||
if m.getName.toString.equals("execute")
|
||||
) yield new DexEntryPoint(m, cha)
|
||||
|
||||
private def createJavaScriptCallGraph: (CallGraph, Elem) = {
|
||||
decodeApk(apk);
|
||||
val configXml = XML.loadFile(new File(apkUnzipDir, "/res/xml/config.xml"))
|
||||
val entrypoint = getEntryPoint(configXml)
|
||||
if (!entrypoint.exists()) {
|
||||
logger.error(s"Could not find entrypoint $entrypoint, using an empty call graph ...")
|
||||
return (new EmptyCallGraph(), configXml)
|
||||
}
|
||||
val pluginInfos = getPluginInfos
|
||||
val loaders = new WebPageLoaderFactory(new CAstRhinoTranslatorFactory())
|
||||
val scripts = makeHtmlScope(entrypoint.toURI().toURL(), loaders, pluginInfos);
|
||||
preprocessApkDir(scripts, pluginInfos)
|
||||
val cache = new AnalysisCache(AstIRFactory.makeDefaultFactory())
|
||||
val scope = new CAstAnalysisScope(scripts.toArray, loaders, Collections.singleton(JavaScriptLoader.JS))
|
||||
val cha = ClassHierarchy.make(scope, loaders, JavaScriptLoader.JS)
|
||||
val roots = JSCallGraphUtil.makeScriptRoots(cha)
|
||||
val options = JSCallGraphUtil.makeOptions(scope, cha, roots)
|
||||
try {
|
||||
com.ibm.wala.cast.js.util.Util.checkForFrontEndErrors(cha);
|
||||
} catch {
|
||||
case e: WalaException => logger.warn("JavaScript front end error:", e)
|
||||
}
|
||||
val cg = if (preciseJS) {
|
||||
val builder = new JSZeroOrOneXCFABuilder(cha, options, cache, null, null, ZeroXInstanceKeys.ALLOCATIONS,
|
||||
true)
|
||||
builder.makeCallGraph(options)
|
||||
} else {
|
||||
val builder = new OptimisticCallgraphBuilder(cha, JSCallGraphUtil.makeOptions(scope, cha, roots), cache, false)
|
||||
builder.buildCallGraph(roots, null).fst
|
||||
}
|
||||
(cg, configXml)
|
||||
}
|
||||
|
||||
private def makeHtmlScope(url: URL, loaders: JavaScriptLoaderFactory, pluginInfos: List[PluginInfo]): List[SourceModule] = {
|
||||
JavaScriptLoader.addBootstrapFile(WebUtil.preamble);
|
||||
val lb = new ListBuffer[SourceModule]()
|
||||
lb += new SourceURLModule(CordovaCGBuilder.getClass.getClassLoader.getResource("prologue.js"))
|
||||
lb += new SourceURLModule(CordovaCGBuilder.getClass.getClassLoader.getResource("preamble.js"))
|
||||
for (info <- pluginInfos) lb += new SourceURLModule(info.file.toURI().toURL())
|
||||
|
||||
val extractor = new AllScriptsExtractor(apkUnzipDir)
|
||||
|
||||
lb ++= extractor.extractSources(url, new JerichoHtmlParser, new IdentityUrlResolver).asScala
|
||||
|
||||
lb ++= extractor.extractedScripts filter { !excludeJS(_) } map { toSourceModule }
|
||||
|
||||
logger.debug("The following scripts are in the JavaScript scope:")
|
||||
for (scriptUrl <- lb.map { _.getURL })
|
||||
logger.debug(scriptUrl.toString())
|
||||
|
||||
lb.toList
|
||||
}
|
||||
|
||||
private def toSourceModule(src: URL) = new SourceURLModule(src) {
|
||||
override def getInputStream = new BOMInputStream(src.openConnection().getInputStream(), false,
|
||||
ByteOrderMark.UTF_8,
|
||||
ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE,
|
||||
ByteOrderMark.UTF_32LE, ByteOrderMark.UTF_32BE)
|
||||
}
|
||||
|
||||
private def decodeApk(apk: File) = {
|
||||
val decoder = new ApkDecoder();
|
||||
decoder.setFrameworkDir(new File(System.getProperty("java.io.tmpdir"), s"apktoolframework${Random.alphanumeric.take(16).mkString}").getCanonicalPath)
|
||||
decoder.setApkFile(apk);
|
||||
if (apkUnzipDir.exists()) FileUtils.deleteDirectory(apkUnzipDir);
|
||||
decoder.setOutDir(apkUnzipDir);
|
||||
decoder.decode();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private def getPluginInfos: List[PluginInfo] = {
|
||||
val lb = new ListBuffer[PluginInfo]()
|
||||
|
||||
val files = Util.walkTree(apkUnzipDir).filter({ file => file.getName == "cordova_plugins.js" }).toList
|
||||
if (files.isEmpty) {
|
||||
logger.warn("Expected a cordova_plugins.js file, but found none. Assuming there are no plugins.")
|
||||
return lb.toList
|
||||
}
|
||||
|
||||
val cordovaPluginJs = if (files.size > 1) {
|
||||
logger.warn(s"Found ${files.size} cordova_plugin.js files! Trying to take one in a folder called 'www'.")
|
||||
files.find { Util.hasSomeParentWithName("www", _) } getOrElse (files(0))
|
||||
} else {
|
||||
files(0)
|
||||
}
|
||||
|
||||
val jsonArray = FileUtils.readFileToString(cordovaPluginJs) match {
|
||||
case PluginsRegex(jsonArray) => jsonArray
|
||||
case _ => throw new IllegalArgumentException("Could not parse plugin section of cordova_plugins.js!")
|
||||
}
|
||||
val list = jsonArray.parseJson.convertTo[List[Map[String, JsValue]]]
|
||||
for (info <- list) {
|
||||
val fileName = info.get("file").get.convertTo[String]
|
||||
val file = new File(cordovaPluginJs.getParentFile, fileName)
|
||||
val id = info.get("id").get.convertTo[String]
|
||||
val clobbers = if (info.contains("clobbers")) {
|
||||
info.get("clobbers").get.convertTo[Vector[JsValue]].map({ _.convertTo[String] }).toList
|
||||
} else {
|
||||
List[String]()
|
||||
}
|
||||
lb += new PluginInfo(fileName, id, clobbers, file)
|
||||
}
|
||||
|
||||
lb.toList
|
||||
}
|
||||
|
||||
private def preprocessApkDir(scripts: List[SourceModule], plugins: List[PluginInfo]) = {
|
||||
var counter = 0
|
||||
for (
|
||||
script <- scripts;
|
||||
file = new File(script.getURL.getFile) if script.getURL.getProtocol == "file"
|
||||
) {
|
||||
val recorder = new FileMapRecorder(file)
|
||||
|
||||
// Fixes WALA bug when encountering "new Function('');" statements
|
||||
for (m <- """new Function\(""\)""".r.findAllMatchIn(recorder.content)) {
|
||||
recorder.replace(m.start, m.end, """new Function("wala123")""")
|
||||
}
|
||||
for (m <- """new Function\(''\)""".r.findAllMatchIn(recorder.content)) {
|
||||
recorder.replace(m.start, m.end, """new Function('wala123')""")
|
||||
}
|
||||
|
||||
for (plugin <- plugins.find { _.file == file } if replacePluginDefinesAndRequires) {
|
||||
for (m <- """cordova\.define\(".+?",\s*function\(.*?\)\s*\{""".r.findFirstMatchIn(recorder.content)) {
|
||||
recorder.replace(m.start, m.end, s"(function() {\n${plugin.getGlobalVar} = function() {};\n")
|
||||
}
|
||||
|
||||
val lastSemi = recorder.content.lastIndexOf(";")
|
||||
recorder.replace(lastSemi, lastSemi, "()")
|
||||
|
||||
for (m <- """module\.exports""".r.findAllMatchIn(recorder.content)) {
|
||||
recorder.replace(m.start, m.end, plugin.getGlobalVar)
|
||||
}
|
||||
|
||||
// for (m <- """exports\s*=""".r.findAllMatchIn(recorder.content)) {
|
||||
// recorder.replace(m.start, m.end, plugin.getGlobalVar + " =")
|
||||
// }
|
||||
|
||||
for (clobber <- plugin.clobbers if !clobber.contains("-")) {
|
||||
recorder.replace(recorder.content.size, recorder.content.size, s"\n$clobber = ${plugin.getGlobalVar};")
|
||||
}
|
||||
|
||||
for (m <- """require\((?:'|")(.+?)(?:'|")\)""".r.findAllMatchIn(recorder.content)) {
|
||||
recorder.replace(m.start, m.end, replaceRequire(plugin, plugins, m))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (mockCordovaExec) {
|
||||
var counterAtBeginning = counter
|
||||
|
||||
for (m <- """cordova\s*\.\s*exec\s*\(""".r.findAllMatchIn(recorder.content)) {
|
||||
counter += 1
|
||||
recorder.replace(recorder.content.size, recorder.content.size,
|
||||
s"\nfunction fun${counter}$ExecuteSuffix(s, f){s();f();};")
|
||||
|
||||
recorder.replace(m.start, m.end, s"fun${counter}$ExecuteSuffix(")
|
||||
}
|
||||
|
||||
val VarRegex =
|
||||
for (m <- """([a-zA-Z0-9_]+)\s*=\s*[a-zA-Z0-9_]+\s*\('cordova/exec'\)""".r.unanchored.findFirstMatchIn(recorder.content)) {
|
||||
val name = m.group(1)
|
||||
for (m2 <- ("""([\s\};])""" + name + """\(""").r.findAllMatchIn(recorder.content)) {
|
||||
counter += 1
|
||||
recorder.replace(recorder.content.size, recorder.content.size,
|
||||
s"\nfunction fun${counter}$ExecuteSuffix(s, f){s();f();};")
|
||||
recorder.replace(m2.start, m2.end, m2.group(1) + s"fun${counter}$ExecuteSuffix(")
|
||||
}
|
||||
}
|
||||
logger.debug(s"Replaced ${counter - counterAtBeginning} cordova exec calls in file $file.")
|
||||
}
|
||||
recorder.writeToFile
|
||||
FileMappingStore.map += ((file, recorder))
|
||||
}
|
||||
}
|
||||
|
||||
val LocalRequireRegex = """\./([a-zA-Z]+)""".r
|
||||
|
||||
private def replaceRequire(plugin: PluginInfo, plugins: List[PluginInfo], m: Regex.Match): String = m.group(1) match {
|
||||
case LocalRequireRegex(name) => plugins.find({ p =>
|
||||
p.fileName.substring(0, p.fileName.lastIndexOf('/')) ==
|
||||
plugin.fileName.substring(0, plugin.fileName.lastIndexOf('/')) && p.fileName.endsWith(s"/$name.js")
|
||||
}).get.getGlobalVar
|
||||
case _ => m.group(0)
|
||||
}
|
||||
|
||||
def setOptions(options: CrossBuilderOption*) = for (option <- options) option match {
|
||||
case MockCordovaExec => mockCordovaExec = true
|
||||
case ReplacePluginDefinesAndRequires => replacePluginDefinesAndRequires = true
|
||||
case FilterJavaCallSites => filterJavaCallSites = true
|
||||
case FilterJSFrameworks => filterJSFrameworks = true
|
||||
case PreciseJS => preciseJS = true
|
||||
case RunBuildersInParallel => runBuildersInParallel = true
|
||||
}
|
||||
|
||||
def getEntryPoint(configXml: Elem) = {
|
||||
val RemotePathRegex = """(https?://.+)""".r
|
||||
val entryPoint = configXml \ "content" \ "@src" text match {
|
||||
case RemotePathRegex(urlString) => {
|
||||
// val url = new URL(urlString)
|
||||
// val dest = new File(new File(apkUnzipDir, "downloaded"), "entrypoint.html")
|
||||
// logger.debug(s"Downloading $urlString to $dest")
|
||||
// dest.getParentFile.mkdirs()
|
||||
// val stream = url.openStream()
|
||||
// FileUtils.copyInputStreamToFile(stream, dest)
|
||||
// IOUtils.closeQuietly(stream)
|
||||
// dest
|
||||
new File(apkUnzipDir, "assets/www/index.html")
|
||||
}
|
||||
case "" => new File(apkUnzipDir, "assets/www/index.html")
|
||||
case text => new File(new File(apkUnzipDir, "assets/www"), text)
|
||||
}
|
||||
if (!entryPoint.getCanonicalPath.startsWith(apkUnzipDir.getCanonicalPath))
|
||||
throw new RuntimeException("Entrypoint seems to be outside of the unzip dir")
|
||||
entryPoint
|
||||
}
|
||||
|
||||
lazy val jsExclusions = if (ExclusionFile.isFile() && ExclusionFile.canRead()) {
|
||||
FileUtils.readLines(ExclusionFile).asScala.map { _.r.unanchored }.toList
|
||||
} else {
|
||||
List.empty
|
||||
}
|
||||
|
||||
def excludeJS(url: URL) = jsExclusions.exists { _.unapplySeq(url.toString()).isDefined }
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.builder
|
||||
|
||||
sealed trait CrossBuilderOption {}
|
||||
|
||||
case object MockCordovaExec extends CrossBuilderOption
|
||||
case object ReplacePluginDefinesAndRequires extends CrossBuilderOption
|
||||
case object FilterJavaCallSites extends CrossBuilderOption
|
||||
case object FilterJSFrameworks extends CrossBuilderOption
|
||||
case object PreciseJS extends CrossBuilderOption
|
||||
case object RunBuildersInParallel extends CrossBuilderOption
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.builder
|
||||
|
||||
import java.io.File
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.io.Source
|
||||
import scala.{ Option => ? }
|
||||
import com.ibm.wala.dalvik.classLoader.DexIMethod
|
||||
import com.ibm.wala.ipa.callgraph.CallGraph
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
/**
|
||||
* @author D062824
|
||||
*/
|
||||
object DalvikLineNumberCalculator {
|
||||
val MethodRegex = """\s*\.method (.+)""".r
|
||||
val LineRegex = """\s*\.line ([0-9]+)\s*""".r
|
||||
val AnnotationRegex = """\s*\.annotation .+""".r
|
||||
val EndAnnotationRegex = """\s*\.end annotation\s*""".r
|
||||
val InstructionRegex = """\s*[a-zA-Z].*""".r
|
||||
|
||||
def setLineNumbers(apkUnzipDir: File, cg: CallGraph) = {
|
||||
for (
|
||||
node <- cg.iterator().asScala;
|
||||
method <- ?(node.getMethod).collect({ case m: DexIMethod => m })
|
||||
) {
|
||||
val path = method.getDeclaringClass.getName.toString().substring(1) + ".smali"
|
||||
val smaliFile = new File(new File(apkUnzipDir, "smali"), path)
|
||||
val methodString = method.getReference().getName().toString() + node.getMethod().getReference().getDescriptor()
|
||||
var lastLine = -1
|
||||
var inCorrectMethod = false
|
||||
var correctMethodFound = false
|
||||
var inAnnotation = false
|
||||
var instCounter = 0
|
||||
for (line <- FileUtils.readLines(smaliFile).asScala.takeWhile(_ => !correctMethodFound || inCorrectMethod)) line match {
|
||||
case MethodRegex(rest) => {
|
||||
inCorrectMethod = rest.endsWith(methodString)
|
||||
if (inCorrectMethod) correctMethodFound = true
|
||||
}
|
||||
case LineRegex(line) if inCorrectMethod => lastLine = line.toInt
|
||||
case AnnotationRegex() => inAnnotation = true
|
||||
case EndAnnotationRegex() => inAnnotation = false
|
||||
case InstructionRegex() if (inCorrectMethod && !inAnnotation) => {
|
||||
method.setLineNumber(instCounter, lastLine)
|
||||
instCounter += 1
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.builder
|
||||
|
||||
import com.ibm.wala.ipa.callgraph.CallGraph
|
||||
import com.ibm.wala.ipa.callgraph.CGNode
|
||||
import com.ibm.wala.classLoader.IMethod
|
||||
import com.ibm.wala.ipa.callgraph.Context
|
||||
import com.ibm.wala.types.MethodReference
|
||||
import com.ibm.wala.classLoader.CallSiteReference
|
||||
import com.ibm.wala.util.intset.IntSet
|
||||
import com.ibm.wala.ipa.cha.IClassHierarchy
|
||||
import java.util.Collection
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
class EmptyCallGraph extends CallGraph {
|
||||
def addEdge(x$1: CGNode, x$2: CGNode): Unit = {}
|
||||
|
||||
def addNode(x$1: CGNode): Unit = {}
|
||||
|
||||
def containsNode(x$1: CGNode): Boolean = false
|
||||
|
||||
def getClassHierarchy(): IClassHierarchy = null
|
||||
|
||||
def getEntrypointNodes(): Collection[CGNode] = List().asJava
|
||||
|
||||
def getFakeRootNode(): CGNode = null
|
||||
|
||||
def getMaxNumber(): Int = 0
|
||||
|
||||
def getNode(x$1: IMethod, x$2: Context): CGNode = null
|
||||
|
||||
def getNode(x$1: Int): CGNode = null
|
||||
|
||||
def getNodes(x$1: MethodReference): java.util.Set[CGNode] = Set().asJava
|
||||
|
||||
def getNumber(x$1: CGNode): Int = 0
|
||||
|
||||
def getNumberOfNodes(): Int = 0
|
||||
|
||||
def getNumberOfTargets(x$1: CGNode, x$2: CallSiteReference): Int = 0
|
||||
|
||||
def getPossibleSites(x$1: CGNode, x$2: CGNode): java.util.Iterator[CallSiteReference] = Iterator.empty.asJava
|
||||
|
||||
def getPossibleTargets(x$1: CGNode, x$2: CallSiteReference): java.util.Set[CGNode] = Set().asJava
|
||||
|
||||
def getPredNodeCount(x$1: CGNode): Int = 0
|
||||
|
||||
def getPredNodeNumbers(x$1: CGNode): IntSet = null
|
||||
|
||||
def getPredNodes(x$1: CGNode): java.util.Iterator[CGNode] = Iterator.empty.asJava
|
||||
|
||||
def getSuccNodeCount(x$1: CGNode): Int = 0
|
||||
|
||||
def getSuccNodeNumbers(x$1: CGNode): IntSet = null
|
||||
|
||||
def getSuccNodes(x$1: CGNode): java.util.Iterator[CGNode] = Iterator.empty.asJava
|
||||
|
||||
def hasEdge(x$1: CGNode, x$2: CGNode): Boolean = false
|
||||
|
||||
def iterateNodes(x$1: IntSet): java.util.Iterator[CGNode] = Iterator.empty.asJava
|
||||
|
||||
def iterator(): java.util.Iterator[CGNode] = Iterator.empty.asJava
|
||||
|
||||
def removeAllIncidentEdges(x$1: CGNode): Unit = {}
|
||||
|
||||
def removeEdge(x$1: CGNode, x$2: CGNode): Unit = {}
|
||||
|
||||
def removeIncomingEdges(x$1: CGNode): Unit = {}
|
||||
|
||||
def removeNode(x$1: CGNode): Unit = {}
|
||||
|
||||
def removeNodeAndEdges(x$1: CGNode): Unit = {}
|
||||
|
||||
def removeOutgoingEdges(x$1: CGNode): Unit = {}
|
||||
|
||||
def getFakeWorldClinitNode(): CGNode = {
|
||||
???
|
||||
}
|
||||
}
|
|
@ -0,0 +1,405 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.builder
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import com.ibm.wala.ipa.callgraph.CGNode
|
||||
import com.ibm.wala.ipa.callgraph.CallGraph
|
||||
import com.ibm.wala.classLoader.CallSiteReference
|
||||
import com.ibm.wala.classLoader.Language
|
||||
import scala.xml.XML
|
||||
import scala.xml.Elem
|
||||
import scala.collection.JavaConverters._
|
||||
import com.ibm.wala.classLoader.CallSiteReference
|
||||
import com.ibm.wala.ipa.callgraph.CGNode
|
||||
import com.ibm.wala.cast.js.loader.JavaScriptLoader
|
||||
import com.ibm.wala.ssa.SSAAbstractInvokeInstruction
|
||||
import com.ibm.wala.ssa.SymbolTable
|
||||
import scala.xml.Elem
|
||||
import scala.xml.Node
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import com.ibm.wala.cast.js.loader.JavaScriptLoader
|
||||
import com.ibm.wala.ssa.SSAInstruction
|
||||
import com.ibm.wala.cast.js.ssa.JavaScriptInvoke
|
||||
import com.ibm.wala.cast.js.types.JavaScriptMethods
|
||||
import com.ibm.wala.ssa.SSAReturnInstruction
|
||||
import com.ibm.wala.ssa.SSAPhiInstruction
|
||||
import com.ibm.wala.cast.ir.ssa.AstLexicalRead
|
||||
import com.ibm.wala.cast.ir.ssa.AstLexicalWrite
|
||||
import scala.{ Option => ? }
|
||||
import com.ibm.wala.cast.ir.ssa.AstLexicalAccess.Access
|
||||
import scala.collection.immutable.HashMap
|
||||
import com.ibm.wala.ssa.SSAGetInstruction
|
||||
import eu.aniketos.dasca.crosslanguage.builder.algorithms.ExecuteActionBasedChecker
|
||||
import com.ibm.wala.ipa.cfg.BasicBlockInContext
|
||||
import com.ibm.wala.ssa.analysis.IExplodedBasicBlock
|
||||
import eu.aniketos.dasca.crosslanguage.builder.algorithms.ExecuteActionBasedChecker
|
||||
import com.ibm.wala.ssa.analysis.ExplodedControlFlowGraph
|
||||
import eu.aniketos.dasca.crosslanguage.util.Util
|
||||
import scala.collection.mutable.Queue
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import com.ibm.wala.cast.ir.ssa.AstIRFactory
|
||||
import scala.collection.mutable.LinkedHashSet
|
||||
|
||||
class MergedCallGraph(val javaCG: CallGraph, val jsCG: CallGraph, val configXml: Elem) extends Iterable[CGNode] {
|
||||
val logger = Logger(LoggerFactory.getLogger(getClass.toString))
|
||||
var targets = Map[(CGNode, CallSiteReference), LinkedHashSet[CGNode]]()
|
||||
var origins = Map[CGNode, LinkedHashSet[(CGNode, CallSiteReference)]]()
|
||||
var paramInfo = Map[(CGNode, CallSiteReference), String]()
|
||||
var paramMap = Map[(CGNode, CallSiteReference, CGNode), Map[Int, Set[Int]]]()
|
||||
|
||||
var filterJavaCallSites = false
|
||||
var filterJSFrameworks = false
|
||||
|
||||
lazy val classNames = {
|
||||
for (
|
||||
feature <- configXml \\ "feature";
|
||||
param <- feature \ "param" if param \@ "name" == "android-package"
|
||||
) yield (feature \@ "name", param \@ "value")
|
||||
}.groupBy(_._1).mapValues(_.map("L" + _._2.replace('.', '/')))
|
||||
|
||||
lazy val lexicalWrites = {
|
||||
for (
|
||||
node <- jsCG.iterator().asScala;
|
||||
write <- node.getIR().getInstructions.collect { case inst: AstLexicalWrite => inst };
|
||||
access <- write.getAccesses();
|
||||
use <- 0.to(write.getNumberOfUses()).find(use => write.getUse(use) == access.valueNumber)
|
||||
) yield ((access.variableDefiner, access.variableName), (node, write, use))
|
||||
}.toSeq.groupBy(_._1).mapValues(_.map(_._2))
|
||||
|
||||
lazy val executeNodesForClassName = {
|
||||
for (
|
||||
node <- javaCG.iterator.asScala;
|
||||
method = node.getMethod if method.getName.toString() == "execute";
|
||||
declaringClass = method.getDeclaringClass if Util.derivesFromCordovaPlugin(declaringClass.getSuperclass)
|
||||
) yield (declaringClass.getName.toString(), node)
|
||||
}.toSeq.groupBy(_._1).mapValues(_.map(_._2))
|
||||
|
||||
lazy val (successCalls, failCalls) = {
|
||||
val successLb = ListBuffer[(CGNode, SSAAbstractInvokeInstruction)]()
|
||||
val failLb = ListBuffer[(CGNode, SSAAbstractInvokeInstruction)]()
|
||||
|
||||
for (
|
||||
node <- javaCG.iterator().asScala;
|
||||
ir <- ?(node.getIR());
|
||||
invoke <- ir.getInstructions().collect({ case invoke: SSAAbstractInvokeInstruction => invoke });
|
||||
csr = invoke.getCallSite()
|
||||
) {
|
||||
if (isSuccessCall(node, csr)) successLb += ((node, invoke))
|
||||
if (isFailCall(node, csr)) failLb += ((node, invoke))
|
||||
}
|
||||
|
||||
(successLb.toList, failLb.toList)
|
||||
}
|
||||
|
||||
override def iterator(): Iterator[CGNode] = {
|
||||
javaCG.iterator.asScala ++ jsCG.iterator.asScala
|
||||
}
|
||||
|
||||
def getPossibleTargets(node: CGNode, csr: CallSiteReference) = {
|
||||
if (Util.isJavaNode(node)) {
|
||||
javaCG.getPossibleTargets(node, csr).asScala
|
||||
} else {
|
||||
jsCG.getPossibleTargets(node, csr).asScala
|
||||
}
|
||||
}
|
||||
|
||||
def connect(filterJavaCallSites: Boolean, filterJSFrameworks: Boolean): Unit = {
|
||||
this.filterJavaCallSites = filterJavaCallSites
|
||||
this.filterJSFrameworks = filterJSFrameworks
|
||||
connect()
|
||||
}
|
||||
|
||||
def connect(): Unit = for (
|
||||
node <- jsCG.iterator.asScala;
|
||||
ir = node.getIR if ir != null;
|
||||
csr <- ir.iterateCallSites.asScala;
|
||||
jsExecuteNode <- findJsExecuteNode(node, csr);
|
||||
invoke <- ir.getCalls(csr);
|
||||
st = ir.getSymbolTable;
|
||||
featureName <- findFeatureString(st, node, invoke);
|
||||
classNames <- classNames.get(featureName);
|
||||
className <- classNames;
|
||||
executeNodes <- executeNodesForClassName.get(className);
|
||||
executeNode <- executeNodes;
|
||||
action <- determinePossibleActions(node, invoke)
|
||||
) {
|
||||
val paramMap = Map(2 -> Set(3), 3 -> Set(3), 6 -> Set(2))
|
||||
addCrossCall(node, csr, executeNode, paramMap, action)
|
||||
|
||||
val preds = jsCG.getPredNodes(node).asScala.toSeq
|
||||
logger.debug(s"There are ${preds.size} predecessors of node ${Util.prettyPrintInstruction(node, node.getIR.getInstructions.find { _ != null } get)}")
|
||||
for (
|
||||
pred <- preds;
|
||||
site <- jsCG.getPossibleSites(pred, node).asScala;
|
||||
invoke <- pred.getIR.getCalls(site)
|
||||
) logger.debug("\t" + Util.prettyPrintInstruction(pred, invoke))
|
||||
|
||||
def getPackagePrefix(pkg: String) = {
|
||||
val firstIndex = pkg.indexOf('/')
|
||||
val secondIndex = pkg.indexOf('/', firstIndex + 1)
|
||||
if (secondIndex == -1) {
|
||||
pkg
|
||||
} else {
|
||||
pkg.substring(0, secondIndex)
|
||||
}
|
||||
}
|
||||
|
||||
val packageFilter = { n: CGNode =>
|
||||
getPackagePrefix(n.getMethod.getDeclaringClass.getName.getPackage.toString()) ==
|
||||
getPackagePrefix(executeNode.getMethod.getDeclaringClass.getName.getPackage.toString()) &&
|
||||
!("org/apache/cordova".equals(n.getMethod.getDeclaringClass.getName.getPackage.toString()))
|
||||
}
|
||||
|
||||
val reachabilityChecker = new ExecuteActionBasedChecker(javaCG, packageFilter, action, executeNode)
|
||||
|
||||
val successNodes = findFunctionNodes(2, node, invoke, jsExecuteNode)
|
||||
logger.info(s"Found ${successNodes.size} success nodes for action $action, ${Util.prettyPrintInstruction(node, invoke)}");
|
||||
val reachableSuccessCalls = if (filterJavaCallSites) {
|
||||
successCalls.filter({ case (successNode, successInvoke) => reachabilityChecker.isReachable(executeNode, successNode, successInvoke) })
|
||||
} else {
|
||||
successCalls.filter({ case (n, _) => packageFilter(n) })
|
||||
}
|
||||
for (
|
||||
successTarget <- successNodes;
|
||||
_ = logger.debug(s"\t-> ${Util.prettyPrintInstruction(successTarget, successTarget.getIR.getInstructions.find { _ != null } get)}");
|
||||
(successFrom, successInvoke) <- reachableSuccessCalls
|
||||
) {
|
||||
addCrossCall(successFrom, successInvoke.getCallSite, successTarget, Map(1 -> Set(3)))
|
||||
}
|
||||
|
||||
val failNodes = findFunctionNodes(3, node, invoke, jsExecuteNode)
|
||||
logger.info(s"Found ${failNodes.size} fail nodes for action $action, ${Util.prettyPrintInstruction(node, invoke)}")
|
||||
val reachableFailCalls = if (filterJavaCallSites) {
|
||||
failCalls.filter({ case (failNode, failInvoke) => reachabilityChecker.isReachable(executeNode, failNode, failInvoke) })
|
||||
} else {
|
||||
failCalls.filter({ case (n, _) => packageFilter(n) })
|
||||
}
|
||||
for (
|
||||
failTarget <- failNodes;
|
||||
_ = logger.debug(s"\t-> ${Util.prettyPrintInstruction(failTarget, failTarget.getIR.getInstructions.find { _ != null } get)}");
|
||||
(failFrom, failInvoke) <- reachableFailCalls
|
||||
) {
|
||||
addCrossCall(failFrom, failInvoke.getCallSite, failTarget, Map(1 -> Set(3)))
|
||||
}
|
||||
}
|
||||
|
||||
def findFeatureString(st: SymbolTable, node: CGNode, invoke: SSAAbstractInvokeInstruction) = {
|
||||
val featureUse = invoke.getUse(4)
|
||||
if (st.isStringConstant(featureUse)) {
|
||||
Option(st.getStringValue(featureUse))
|
||||
} else {
|
||||
logger.warn(s"Feature parameter was not found in symbol table for statement ${Util.prettyPrintInstruction(node, invoke)}")
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def determinePossibleActions(node: CGNode, invoke: SSAAbstractInvokeInstruction) = {
|
||||
val lb = ListBuffer[String]()
|
||||
val st = node.getIR.getSymbolTable
|
||||
if (st.isStringConstant(invoke.getUse(5))) {
|
||||
lb += st.getStringValue(invoke.getUse(5));
|
||||
} else {
|
||||
node.getDU.getDef(invoke.getUse(5)) match {
|
||||
case phi: SSAPhiInstruction => {
|
||||
if (0.until(phi.getNumberOfUses).map({ i => st.isStringConstant(phi.getUse(i)) }).forall { _ == true }) {
|
||||
lb ++= 0.until(phi.getNumberOfUses).map { i => st.getStringValue(phi.getUse(i)) }
|
||||
}
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
lb.toList
|
||||
}
|
||||
|
||||
def isSuccessCall(node: CGNode, csr: CallSiteReference): Boolean = {
|
||||
val declaredTarget = csr.getDeclaredTarget
|
||||
if (declaredTarget.getDeclaringClass().getName().toString() != "Lorg/apache/cordova/CallbackContext")
|
||||
return false
|
||||
|
||||
if (declaredTarget.getName().toString() == "success")
|
||||
return true
|
||||
|
||||
if (declaredTarget.getName().toString() == "sendPluginResult" && isPossibleStatus(node, csr, "OK"))
|
||||
return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
def findJsExecuteNode(node: CGNode, csr: CallSiteReference) = getPossibleTargets(node, csr).collectFirst({
|
||||
n =>
|
||||
n.getMethod match {
|
||||
case m: JavaScriptLoader#JavaScriptMethodObject if (m.getEntity.getName.endsWith(CordovaCGBuilder.ExecuteSuffix)) => n
|
||||
}
|
||||
})
|
||||
|
||||
private def addCrossCall(from: CGNode, csr: CallSiteReference, to: CGNode, params: Map[Int, Set[Int]]): Unit = {
|
||||
if (filterJSFrameworks && skipCrossCall(from, csr, to)) {
|
||||
logger.info(s"Skipped cross call from ${Util.prettyPrintInstruction(from, from.getIR.getCalls(csr)(0))} to ${Util.prettyPrintInstruction(to, to.getIR.getInstructions.find({ _ != null }).get)}")
|
||||
return
|
||||
}
|
||||
|
||||
targets += (((from, csr), LinkedHashSet(to) ++ targets.getOrElse((from, csr), Set())))
|
||||
origins += ((to, LinkedHashSet((from, csr)) ++ origins.getOrElse(to, Set())))
|
||||
paramMap += (((from, csr, to), params))
|
||||
}
|
||||
|
||||
private def skipCrossCall(from: CGNode, csr: CallSiteReference, to: CGNode): Boolean = {
|
||||
if (Util.isJavaNode(from)) {
|
||||
to.getMethod match {
|
||||
case m: JavaScriptLoader#JavaScriptMethodObject if (m.getEntity.getName.startsWith("make_")) => return true
|
||||
case _ =>
|
||||
}
|
||||
to.getIR match {
|
||||
case ir: AstIRFactory#AstIR => {
|
||||
val (_, _, _, _, relPath) = Util.getJavaScriptSourceInfo(ir, ir.getInstructions.find(_ != null).get)
|
||||
val lc = relPath.toLowerCase()
|
||||
val filename = lc.substring(lc.lastIndexOf('/'))
|
||||
if (filename.contains("jquery") || filename.contains("energize") || filename.contains("jqm") || filename.contains("prologue.js") ||
|
||||
filename.contains("preamble.js") || filename.contains("autonumeric") || filename.contains("modernizr") || filename.contains("cordova")) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
case _ => true
|
||||
}
|
||||
} else {
|
||||
from.getIR match {
|
||||
case ir: AstIRFactory#AstIR => {
|
||||
val (_, _, _, _, relPath) = Util.getJavaScriptSourceInfo(ir, ir.getInstructions.find(_ != null).get)
|
||||
val lc = relPath.toLowerCase()
|
||||
val filename = lc.substring(lc.lastIndexOf('/'))
|
||||
if (filename.contains("cordova")) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def addCrossCall(from: CGNode, csr: CallSiteReference, to: CGNode, params: Map[Int, Set[Int]], param: String): Unit = {
|
||||
addCrossCall(from, csr, to, params)
|
||||
paramInfo += (((from, csr), param))
|
||||
}
|
||||
|
||||
def getAllCrossTargets = targets
|
||||
|
||||
def getCrossOrigins(node: CGNode) = origins.get(node) match {
|
||||
case Some(x) => x
|
||||
case None => LinkedHashSet[(CGNode, CallSiteReference)]()
|
||||
}
|
||||
|
||||
def getCrossTargets(node: CGNode, csr: CallSiteReference): LinkedHashSet[CGNode] = targets.get((node, csr)) match {
|
||||
case Some(x) => x
|
||||
case None => LinkedHashSet[CGNode]()
|
||||
}
|
||||
|
||||
def getParameterMapping(from: CGNode, csr: CallSiteReference, to: CGNode) = paramMap.get((from, csr, to))
|
||||
|
||||
def findFunctionNodes(arg: Int, node: CGNode, inst: SSAInstruction, jsExecuteNode: CGNode) = {
|
||||
for (
|
||||
invoke <- jsExecuteNode.getDU.getUses(arg + 1).asScala.collectFirst({ case s: SSAAbstractInvokeInstruction => s });
|
||||
csr = invoke.getCallSite
|
||||
) yield jsCG.getPossibleTargets(jsExecuteNode, csr).asScala
|
||||
}.getOrElse(Set[CGNode]())
|
||||
// val queue = Queue((arg, node, inst))
|
||||
// val visited = scala.collection.mutable.Set[(Int, CGNode, SSAInstruction)]()
|
||||
// val result = ListBuffer[CGNode]()
|
||||
// while (!queue.isEmpty) {
|
||||
// val cur = queue.dequeue()
|
||||
// if (!visited.contains(cur)) {
|
||||
// visited += cur
|
||||
// val (curArg, curNode, curInst) = cur
|
||||
//
|
||||
// val use = curInst.getUse(curArg)
|
||||
// val symbolTable = curNode.getIR().getSymbolTable()
|
||||
//
|
||||
// if (use <= curNode.getMethod().getNumberOfParameters()) {
|
||||
// for (
|
||||
// pred <- jsCG.getPredNodes(curNode).asScala;
|
||||
// csr <- jsCG.getPossibleSites(pred, curNode).asScala;
|
||||
// invoke <- pred.getIR().getCalls(csr) if invoke.getNumberOfParameters() == curNode.getMethod().getNumberOfParameters()
|
||||
// ) queue += ((use - 1, pred, invoke))
|
||||
// } else {
|
||||
// curNode.getDU().getDef(use) match {
|
||||
// case invoke: JavaScriptInvoke if isMethodConstructor(invoke, symbolTable) => result ++= findCallbackNode(jsExecuteNode, invoke, symbolTable).toList
|
||||
// case invoke: JavaScriptInvoke => for (
|
||||
// target <- jsCG.getPossibleTargets(curNode, invoke.getCallSite()).asScala;
|
||||
// ret <- target.getIR().getInstructions().collect({ case r: SSAReturnInstruction => r });
|
||||
// if (ret.getNumberOfUses == 1)
|
||||
// ) queue += ((0, target, ret))
|
||||
// case phi: SSAPhiInstruction => for (i <- 0.until(phi.getNumberOfUses)) queue += ((i, curNode, phi))
|
||||
// case read: AstLexicalRead => {
|
||||
// for (
|
||||
// node <- jsCG.iterator().asScala;
|
||||
// method <- ?(node.getMethod()).collect({ case method: JavaScriptLoader#JavaScriptMethodObject => method }).toSeq;
|
||||
// access <- read.getAccesses() if access.variableName == method.getEntity().getName();
|
||||
// pkg <- ?(method.getReference().getDeclaringClass().getName().getPackage()) if pkg.toString() == access.variableDefiner.substring(1)
|
||||
// ) yield result += node
|
||||
// for (
|
||||
// accessNo <- 0.until(read.getAccessCount);
|
||||
// access = read.getAccesses().apply(accessNo);
|
||||
// writes <- lexicalWrites.get((access.variableDefiner, access.variableName));
|
||||
// write <- writes
|
||||
// ) queue += ((accessNo, write._1, write._2))
|
||||
// }
|
||||
// case _ =>
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// result.toList
|
||||
|
||||
def findCallbackNode(jsExecNode: CGNode, inst: SSAInstruction, symbolTable: SymbolTable) = jsCG.getSuccNodes(jsExecNode).asScala.find {
|
||||
_.getMethod.toString().contains(symbolTable.getStringValue(inst.getUse(1)))
|
||||
}
|
||||
|
||||
def isMethodConstructor(invoke: JavaScriptInvoke, symbolTable: SymbolTable) =
|
||||
invoke.getCallSite().getDeclaredTarget() == JavaScriptMethods.ctorReference &&
|
||||
invoke.getNumberOfUses > 1 && symbolTable.isStringConstant(invoke.getUse(1))
|
||||
|
||||
def isPossibleStatus(node: CGNode, csr: CallSiteReference, args: String*): Boolean = {
|
||||
for (
|
||||
invoke <- node.getIR().getCalls(csr);
|
||||
inv <- node.getDU().getUses(invoke.getUse(1)).asScala.collect({ case i: SSAAbstractInvokeInstruction => i }) if inv != invoke;
|
||||
if inv.getCallSite().getDeclaredTarget().getName().toString() == "<init>";
|
||||
statusUse = inv.getUse(1)
|
||||
) {
|
||||
if (statusUse <= node.getMethod().getNumberOfParameters())
|
||||
return true
|
||||
for (
|
||||
get <- ?(node.getDU().getDef(statusUse)).collect({ case i: SSAGetInstruction => i })
|
||||
) {
|
||||
if (args.contains(get.getDeclaredField().getName().toString()))
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
def isFailCall(node: CGNode, csr: CallSiteReference): Boolean = {
|
||||
val declaredTarget = csr.getDeclaredTarget
|
||||
if (declaredTarget.getDeclaringClass().getName().toString() != "Lorg/apache/cordova/CallbackContext")
|
||||
return false
|
||||
if (declaredTarget.getName().toString() == "error")
|
||||
return true
|
||||
|
||||
if (declaredTarget.getName().toString() == "sendPluginResult" && isPossibleStatus(node, csr, "INVALID_ACTION", "ERROR", "JSON_EXCEPTION", "IO_EXCEPTION"))
|
||||
return true
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.builder
|
||||
|
||||
import java.io.File
|
||||
|
||||
class PluginInfo(val fileName: String, val id: String, val clobbers: List[String], val file: File) {
|
||||
|
||||
def getGlobalVar = s"window.crosswala${id.replace(".", "").replace("-", "")}"
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.builder.algorithms
|
||||
|
||||
import scala.collection.JavaConverters.asScalaIteratorConverter
|
||||
import scala.collection.JavaConverters.collectionAsScalaIterableConverter
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import com.ibm.wala.ipa.callgraph.CGNode
|
||||
import com.ibm.wala.ipa.callgraph.CallGraph
|
||||
import com.ibm.wala.ipa.cfg.BasicBlockInContext
|
||||
import com.ibm.wala.shrikeBT.IConditionalBranchInstruction
|
||||
import com.ibm.wala.ssa.SSAAbstractInvokeInstruction
|
||||
import com.ibm.wala.ssa.SSAConditionalBranchInstruction
|
||||
import com.ibm.wala.ssa.analysis.ExplodedControlFlowGraph
|
||||
import com.ibm.wala.ssa.analysis.IExplodedBasicBlock
|
||||
import com.typesafe.scalalogging.Logger
|
||||
|
||||
import scala.{ Option => ? }
|
||||
|
||||
class ExecuteActionBasedChecker(val cg: CallGraph,
|
||||
val keep: CGNode => Boolean,
|
||||
val action: String,
|
||||
val execNode: CGNode) extends ReachabilityChecker(cg, keep) {
|
||||
val symbolTable = execNode.getIR.getSymbolTable
|
||||
override val logger = Logger(LoggerFactory.getLogger(getClass.toString))
|
||||
|
||||
override def canPassThrough(toBB: IExplodedBasicBlock,
|
||||
predBB: IExplodedBasicBlock): Boolean = {
|
||||
// Asuming both basic blocks are from the execNode
|
||||
predBB.getLastInstruction match {
|
||||
case cond: SSAConditionalBranchInstruction if cond.getOperator == IConditionalBranchInstruction.Operator.EQ => {
|
||||
val vl = cond.getUse(0)
|
||||
val vr = cond.getUse(1)
|
||||
// The right side (vr) is apparently always either one or zero in conditional eq instructions
|
||||
|
||||
// We are only looking for equal calls on the second method parameter of the cordova execute method,
|
||||
// which is "action"
|
||||
val nonActionUseInEquals = isEqualsAndDependsOnActionParam(vl)
|
||||
|
||||
if (nonActionUseInEquals != -1 && symbolTable.isConstant(nonActionUseInEquals) &&
|
||||
symbolTable.isStringConstant(nonActionUseInEquals)) {
|
||||
val actionString = symbolTable.getStringValue(nonActionUseInEquals)
|
||||
if (actionString.equals(action) && !isTrueBranch(vr, toBB, predBB)) {
|
||||
return false
|
||||
} else if (!actionString.equals(action) && isTrueBranch(vr, toBB, predBB)) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case _ => true
|
||||
}
|
||||
}
|
||||
|
||||
def isEqualsAndDependsOnActionParam(v: Int): Int = {
|
||||
for (
|
||||
invoke <- ?(execNode.getDU.getDef(v)).collect({ case i: SSAAbstractInvokeInstruction => i });
|
||||
if (invoke.getDeclaredTarget.toString().contains("equal"))
|
||||
) {
|
||||
if (invoke.getUse(0) == 2) {
|
||||
return invoke.getUse(1)
|
||||
} else if (invoke.getUse(1) == 2) {
|
||||
return invoke.getUse(0)
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
def isTrueBranch(v: Int,
|
||||
toBB: IExplodedBasicBlock,
|
||||
predBB: IExplodedBasicBlock): Boolean = {
|
||||
if (symbolTable.isOneOrTrue(v) && predBB.getLastInstructionIndex + 1 != toBB.getLastInstructionIndex) {
|
||||
return true
|
||||
} else if (symbolTable.isZeroOrFalse(v) && predBB.getLastInstructionIndex + 1 == toBB.getLastInstructionIndex) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
def extraPredNodes(node: CGNode): List[CGNode] = {
|
||||
if (node.getMethod.getName.toString().equals("run")) {
|
||||
for (
|
||||
interf <- node.getMethod.getDeclaringClass.getAllImplementedInterfaces.asScala;
|
||||
if (interf.getName().toString().equals("Ljava/lang/Runnable"));
|
||||
n <- cg.iterator().asScala;
|
||||
if (n.getMethod.getDeclaringClass == node.getMethod.getDeclaringClass);
|
||||
if (n.getMethod.getName.toString() == "<init>")
|
||||
) {
|
||||
return List(n)
|
||||
}
|
||||
}
|
||||
return List.empty
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.builder.algorithms
|
||||
|
||||
import scala.collection.JavaConverters.asScalaIteratorConverter
|
||||
import scala.collection.mutable.Queue
|
||||
import scala.collection.JavaConverters._
|
||||
import org.slf4j.LoggerFactory
|
||||
import com.ibm.wala.ipa.callgraph.CGNode
|
||||
import com.ibm.wala.ipa.callgraph.CallGraph
|
||||
import com.ibm.wala.ipa.cfg.BasicBlockInContext
|
||||
import com.ibm.wala.ipa.cfg.ExplodedInterproceduralCFG
|
||||
import com.ibm.wala.ssa.SSAInstruction
|
||||
import com.ibm.wala.ssa.analysis.ExplodedControlFlowGraph
|
||||
import com.ibm.wala.ssa.analysis.IExplodedBasicBlock
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import com.ibm.wala.ssa.SSAAbstractInvokeInstruction
|
||||
|
||||
abstract class ReachabilityChecker(private val cg: CallGraph, private val keep: CGNode => Boolean) {
|
||||
val logger = Logger(LoggerFactory.getLogger(getClass.toString))
|
||||
val cache = scala.collection.mutable.Map[(CGNode, CGNode, SSAInstruction), Boolean]()
|
||||
|
||||
def isReachable(fromNode: CGNode, toNode: CGNode, inst: SSAInstruction): Boolean = {
|
||||
if (cache.contains((fromNode, toNode, inst))) return cache((fromNode, toNode, inst))
|
||||
|
||||
val instructions = if (fromNode == toNode) {
|
||||
List(inst)
|
||||
} else {
|
||||
getReachableInstructions(fromNode, toNode)
|
||||
}
|
||||
|
||||
val ecfg = ExplodedControlFlowGraph.make(fromNode.getIR)
|
||||
|
||||
val res = instructions.exists { inst => isReachable(ecfg, inst) }
|
||||
cache((fromNode, toNode, inst)) = res
|
||||
res
|
||||
}
|
||||
|
||||
def getReachableInstructions(fromNode: CGNode, toNode: CGNode): List[SSAInstruction] = {
|
||||
val lb = ListBuffer[SSAInstruction]()
|
||||
|
||||
if (!keep(fromNode) || !keep(toNode)) return List()
|
||||
|
||||
val queue = Queue[CGNode](toNode)
|
||||
val visited = scala.collection.mutable.Set[CGNode]()
|
||||
|
||||
while (!queue.isEmpty) {
|
||||
val cur = queue.dequeue()
|
||||
|
||||
if (!visited.contains(cur)) {
|
||||
visited += cur
|
||||
|
||||
for (pred <- cg.getPredNodes(cur).asScala ++ extraPredNodes(cur) if keep(pred)) {
|
||||
if (pred == fromNode) {
|
||||
for (csr <- cg.getPossibleSites(pred, cur).asScala;
|
||||
invoke <- pred.getIR.getCalls(csr)) {
|
||||
lb += invoke
|
||||
}
|
||||
} else {
|
||||
queue += pred
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lb.toList
|
||||
}
|
||||
|
||||
private def isReachable(ecfg: ExplodedControlFlowGraph, inst: SSAInstruction): Boolean = {
|
||||
ecfg.getBlockForInstruction(inst.iindex)
|
||||
|
||||
val queue = Queue[IExplodedBasicBlock](ecfg.getBlockForInstruction(inst.iindex))
|
||||
val visited = scala.collection.mutable.Set[IExplodedBasicBlock]()
|
||||
|
||||
while (!queue.isEmpty) {
|
||||
val cur = queue.dequeue()
|
||||
|
||||
if (!visited.contains(cur)) {
|
||||
visited += cur
|
||||
|
||||
if (cur == ecfg.entry())
|
||||
return true
|
||||
|
||||
for (pred <- ecfg.getPredNodes(cur).asScala if canPassThrough(cur, pred)) {
|
||||
queue += pred
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
def extraPredNodes(node: CGNode): List[CGNode]
|
||||
|
||||
def canPassThrough(toBB: IExplodedBasicBlock, predBB: IExplodedBasicBlock): Boolean
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.util
|
||||
|
||||
import java.io.File
|
||||
import scala.collection.mutable.TreeSet
|
||||
import org.apache.commons.io.FileUtils
|
||||
import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position
|
||||
import com.ibm.wala.cast.js.translator.RangePosition
|
||||
|
||||
class FileMapRecorder(file: File) {
|
||||
|
||||
val replacements = TreeSet[(Int, Int, String)]()
|
||||
val content = FileUtils.readFileToString(file)
|
||||
|
||||
var alreadyWritten = false
|
||||
|
||||
def replace(start: Int, end: Int, replacement: String) = {
|
||||
replacements += ((start, end, replacement))
|
||||
}
|
||||
|
||||
def writeToFile = {
|
||||
if (alreadyWritten) {
|
||||
throw new IllegalStateException("Already written to file!")
|
||||
}
|
||||
|
||||
val replArray = replacements.toArray
|
||||
for (i <- 0.until(replArray.length - 1)) {
|
||||
val end = replArray(i)._2
|
||||
val nextStart = replArray(i+1)._1
|
||||
if (end > nextStart) {
|
||||
throw new IllegalStateException("Must not overlap!")
|
||||
}
|
||||
}
|
||||
|
||||
var newContent = content
|
||||
|
||||
for ((start, end, replacement) <- replArray.reverse) {
|
||||
newContent = newContent.substring(0, start) + replacement + newContent.substring(end)
|
||||
}
|
||||
|
||||
alreadyWritten = true
|
||||
|
||||
FileUtils.write(file, newContent)
|
||||
}
|
||||
|
||||
def translate(pos: Position) = {
|
||||
if (pos.getURL != file.toURI().toURL()) {
|
||||
throw new IllegalArgumentException("The passed position's file does not match this one!")
|
||||
}
|
||||
|
||||
val startOffset = pos.getFirstOffset
|
||||
val endOffset = pos.getLastOffset
|
||||
|
||||
val translatedStartOffset = translateOffset(startOffset)
|
||||
|
||||
val translatedEndOffset = translateOffset(endOffset)
|
||||
|
||||
val line = 1 + content.substring(0, translatedStartOffset).count { _ == '\n' }
|
||||
|
||||
val col = RangePosition.getCol(content, line, translatedStartOffset)
|
||||
|
||||
(line, col, translatedStartOffset, translatedEndOffset)
|
||||
}
|
||||
|
||||
private def translateOffset(offset: Int) = {
|
||||
var newOffset = offset
|
||||
val replArray = replacements.toArray
|
||||
for (i <- 0.until(replArray.size)) {
|
||||
val (start, end, repl) = replArray(i)
|
||||
if (end < newOffset) {
|
||||
newOffset += (end - start) - repl.size
|
||||
}
|
||||
}
|
||||
newOffset
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.util
|
||||
|
||||
import java.io.File
|
||||
|
||||
object FileMappingStore {
|
||||
val map = scala.collection.mutable.Map[File, FileMapRecorder]()
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.util
|
||||
|
||||
import com.ibm.wala.cast.ir.ssa.AstIRFactory
|
||||
import com.ibm.wala.classLoader.CallSiteReference
|
||||
import com.ibm.wala.ipa.callgraph.CGNode
|
||||
import com.ibm.wala.ssa.SSAInstruction
|
||||
import com.ibm.wala.cast.js.html.IncludedPosition
|
||||
|
||||
class JavaScriptSourceLocation(val line: Int, val column: Int, val filePath: String) extends SourceLocation with Equals {
|
||||
|
||||
override def toString = s"$filePath:$line:$column"
|
||||
|
||||
override def equals(other: Any) = other match {
|
||||
case o: JavaScriptSourceLocation => line == o.line && column == o.column && filePath == o.filePath
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def canEqual(other: Any) = {
|
||||
other.isInstanceOf[eu.aniketos.dasca.crosslanguage.util.JavaScriptSourceLocation]
|
||||
}
|
||||
|
||||
override def hashCode() = {
|
||||
val prime = 41
|
||||
prime * (prime * (prime + line.hashCode) + column.hashCode) + filePath.hashCode
|
||||
}
|
||||
}
|
||||
|
||||
object JavaScriptSourceLocation {
|
||||
val JavaScriptPathRegex = """.+/assets/(.+)""".r
|
||||
|
||||
def apply(ir: AstIRFactory#AstIR, inst: SSAInstruction): JavaScriptSourceLocation = {
|
||||
val (line, col, _, _, relPath) = Util.getJavaScriptSourceInfo(ir, inst)
|
||||
new JavaScriptSourceLocation(line, col, relPath)
|
||||
}
|
||||
|
||||
def apply(ir: AstIRFactory#AstIR, csr: CallSiteReference): JavaScriptSourceLocation = apply(ir, ir.getCalls(csr)(0))
|
||||
|
||||
def apply(ir: AstIRFactory#AstIR): JavaScriptSourceLocation = apply(ir, ir.getInstructions.find({ i => i != null }).get)
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.util
|
||||
|
||||
import com.ibm.wala.classLoader.CallSiteReference
|
||||
import com.ibm.wala.ipa.callgraph.CGNode
|
||||
import com.ibm.wala.ssa.SSAInstruction
|
||||
|
||||
class JavaSourceLocation(val line: Int, val filePath: String) extends SourceLocation with Equals {
|
||||
|
||||
override def toString = s"$filePath:$line"
|
||||
|
||||
override def equals(other: Any) = other match {
|
||||
case o: JavaSourceLocation => line == o.line && filePath == o.filePath
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def canEqual(other: Any) = {
|
||||
other.isInstanceOf[eu.aniketos.dasca.crosslanguage.util.JavaSourceLocation]
|
||||
}
|
||||
|
||||
override def hashCode() = {
|
||||
val prime = 41
|
||||
prime * (prime + line.hashCode) + filePath.hashCode
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object JavaSourceLocation {
|
||||
def apply(node: CGNode, inst: SSAInstruction): JavaSourceLocation = {
|
||||
val (line, path, className, method) = Util.getJavaSourceInfo(node, inst)
|
||||
new JavaSourceLocation(line, path)
|
||||
}
|
||||
|
||||
def apply(node: CGNode, csr: CallSiteReference): JavaSourceLocation = apply(node, node.getIR.getCalls(csr)(0))
|
||||
|
||||
def apply(node: CGNode): JavaSourceLocation = apply(node, node.getIR.getInstructions.find({ i => i != null }).get)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.util
|
||||
|
||||
trait SourceLocation {
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* (C) Copyright 2010-2015 SAP SE.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
package eu.aniketos.dasca.crosslanguage.util
|
||||
|
||||
import com.ibm.wala.cast.ir.ssa.AstIRFactory
|
||||
import com.ibm.wala.classLoader.Language
|
||||
import com.ibm.wala.ipa.callgraph.CGNode
|
||||
import com.ibm.wala.ssa.SSAInstruction
|
||||
import com.ibm.wala.classLoader.CallSiteReference
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import java.io.File
|
||||
import com.ibm.wala.cast.js.html.IncludedPosition
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import java.util.concurrent.TimeUnit
|
||||
import com.ibm.wala.classLoader.IClass
|
||||
import scala.collection.mutable.LinkedHashSet
|
||||
import eu.aniketos.dasca.crosslanguage.builder.CrossBuilderOption
|
||||
import eu.aniketos.dasca.crosslanguage.builder.FilterJavaCallSites
|
||||
import eu.aniketos.dasca.crosslanguage.builder.MockCordovaExec
|
||||
import eu.aniketos.dasca.crosslanguage.builder.ReplacePluginDefinesAndRequires
|
||||
import eu.aniketos.dasca.crosslanguage.builder.FilterJSFrameworks
|
||||
import eu.aniketos.dasca.crosslanguage.builder.PreciseJS
|
||||
import eu.aniketos.dasca.crosslanguage.builder.RunBuildersInParallel
|
||||
|
||||
object Util {
|
||||
val cachedDalvikLines = Map[(CGNode, SSAInstruction), Int]()
|
||||
|
||||
def derivesFromCordovaPlugin(cls: IClass): Boolean = cls != null &&
|
||||
(cls.getName.toString == "Lorg/apache/cordova/CordovaPlugin" || derivesFromCordovaPlugin(cls.getSuperclass))
|
||||
|
||||
def getJavaSourceInfo(node: CGNode, inst: SSAInstruction) = {
|
||||
val path = node.getMethod.getReference.getDeclaringClass.getName.toString().substring(1)
|
||||
val className = path.substring(path.lastIndexOf('/') + 1)
|
||||
val method = node.getMethod.getName.toString()
|
||||
val line = node.getMethod.getLineNumber(inst.iindex)
|
||||
(line, path, className, method)
|
||||
}
|
||||
|
||||
def hasSomeParentWithName(name: String, file: File): Boolean = {
|
||||
val parent = file.getParentFile
|
||||
parent != null && (parent.getName == name || hasSomeParentWithName(name, parent))
|
||||
}
|
||||
|
||||
val JavaScriptPathRegex = """.+/assets/(.+)""".r
|
||||
def getJavaScriptSourceInfo(ir: AstIRFactory#AstIR, inst: SSAInstruction) = {
|
||||
val sourcePos = ir.getMethod.getSourcePosition(inst.iindex) match {
|
||||
case iPos: IncludedPosition => iPos.getIncludePosition
|
||||
case sp => sp
|
||||
}
|
||||
val relPath = sourcePos.getURL.getFile match {
|
||||
case JavaScriptPathRegex(p) => p
|
||||
case _ => sourcePos.getURL.toString()
|
||||
}
|
||||
val (line, col, start, end) = FileMappingStore.map.find(_._1.toURI().toURL() == sourcePos.getURL).map(_._2) match {
|
||||
case Some(rec) => rec.translate(sourcePos)
|
||||
case None => (sourcePos.getFirstLine, sourcePos.getFirstCol, sourcePos.getFirstOffset, sourcePos.getLastOffset)
|
||||
}
|
||||
(line, col, start, end, relPath)
|
||||
}
|
||||
|
||||
def prettyPrintInstruction(node: CGNode, inst: SSAInstruction): String = {
|
||||
if (isJavaNode(node)) {
|
||||
val (line, path, className, method) = getJavaSourceInfo(node, inst)
|
||||
s"$className:$line, Method: $method, Path: $path"
|
||||
} else {
|
||||
node.getIR match {
|
||||
case ir: AstIRFactory#AstIR => {
|
||||
val (line, col, start, end, relPath) = getJavaScriptSourceInfo(ir, inst)
|
||||
val fileName = relPath.substring(relPath.lastIndexOf('/') + 1)
|
||||
s"$fileName:$line:$col ($start -> $end), Path: $relPath"
|
||||
}
|
||||
case _ => s"$inst, $node"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def argsToOptions(arg: String) = if (arg.charAt(0) == '-') {
|
||||
val lb = new ListBuffer[CrossBuilderOption]
|
||||
if (arg.contains('j')) lb += FilterJavaCallSites
|
||||
if (arg.contains('m')) lb += MockCordovaExec
|
||||
if (arg.contains('r')) lb += ReplacePluginDefinesAndRequires
|
||||
if (arg.contains('f')) lb += FilterJSFrameworks
|
||||
if (arg.contains('p')) lb += PreciseJS
|
||||
if (arg.contains('x')) lb += RunBuildersInParallel
|
||||
lb.toList
|
||||
} else List()
|
||||
|
||||
def prettyPrintCrossTargets(targets: Map[(CGNode, CallSiteReference), LinkedHashSet[CGNode]]): List[String] = {
|
||||
val (javaJsCalls, jsJavaCalls) = targets.partition(p => Util.isJavaNode(p._1._1))
|
||||
|
||||
val lb = new ListBuffer[String]()
|
||||
lb += s"Java -> JavaScript (${javaJsCalls.values.map({ set => set.size }).sum} calls)"
|
||||
lb ++= prettyPrintCrossCalls(javaJsCalls)
|
||||
lb += s"JavaScript -> Java (${jsJavaCalls.values.map({ set => set.size }).sum} calls)"
|
||||
lb ++= prettyPrintCrossCalls(jsJavaCalls)
|
||||
lb.toList
|
||||
}
|
||||
|
||||
private def prettyPrintCrossCalls(targets: Map[(CGNode, CallSiteReference), LinkedHashSet[CGNode]]): List[String] = {
|
||||
val lb = new ListBuffer[String]()
|
||||
for ((node, csr) <- targets.keys) {
|
||||
lb += "\t" + prettyPrintInstruction(node, node.getIR.getCalls(csr)(0))
|
||||
for (target <- targets.get((node, csr)).get) {
|
||||
lb += "\t\t-> " + prettyPrintInstruction(target, target.getIR.getInstructions.find({ i => i != null }).get)
|
||||
}
|
||||
}
|
||||
lb.toList
|
||||
}
|
||||
|
||||
def walkTree(file: File): Iterable[File] = {
|
||||
val children = new Iterable[File] {
|
||||
def iterator = if (file.isDirectory) file.listFiles.iterator else Iterator.empty
|
||||
}
|
||||
Seq(file) ++: children.flatMap(walkTree(_))
|
||||
}
|
||||
|
||||
def isJavaNode(node: CGNode): Boolean = node.getMethod.getDeclaringClass.getClassLoader.getLanguage == Language.JAVA
|
||||
|
||||
def time[R](label: String, block: => R)(implicit logger: Logger): R = {
|
||||
val start = System.nanoTime()
|
||||
val result = block
|
||||
logger.info(s"$label took %.3fs".format(TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS) / 1000.0))
|
||||
result
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue