Initial commit 'crosslanguage analysis'.

This commit is contained in:
Achim D. Brucker 2015-10-14 22:01:52 +02:00
parent ece503aaec
commit 28be30edec
16 changed files with 1671 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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 _ =>
}
}
}
}

View File

@ -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 = {
???
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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