diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/fpcf/analyses/PRATaintAnalysis.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/fpcf/analyses/PRATaintAnalysis.scala new file mode 100644 index 0000000000..9a521b7a3b --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/fpcf/analyses/PRATaintAnalysis.scala @@ -0,0 +1,318 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +/*package org.opalj +package fpcf +package analyses + +import java.io.File + +import scala.collection.immutable.ListSet +import scala.io.Source + +import org.opalj.log.LogContext +import org.opalj.fpcf.seq.PKESequentialPropertyStore +import org.opalj.br.fpcf.FPCFAnalysesManagerKey +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.br.DeclaredMethod +import org.opalj.br.Method +import org.opalj.br.ObjectType +import org.opalj.br.MethodDescriptor +import org.opalj.br.analyses.Project +import org.opalj.br.analyses.SomeProject +import org.opalj.br.analyses.Project.JavaClassFileReader +import org.opalj.ai.domain.l1 +import org.opalj.ai.fpcf.analyses.LazyL0BaseAIAnalysis +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey +import org.opalj.tac.Assignment +import org.opalj.tac.Expr +import org.opalj.tac.Var +import org.opalj.tac.ReturnValue +import org.opalj.tac.Call +import org.opalj.tac.fpcf.properties.IFDSProperty +import org.opalj.tac.fpcf.properties.IFDSPropertyMetaInformation +import org.opalj.tac.fpcf.analyses.AbstractIFDSAnalysis.V +import org.opalj.tac.fpcf.analyses.AbstractIFDSAnalysis +import org.opalj.tac.fpcf.analyses.Statement +import org.opalj.tac.fpcf.analyses.IFDSAnalysis +import org.opalj.tac.fpcf.analyses.TACAITransformer + +trait Fact +case object NullFact extends Fact +case class Variable(index: Int) extends Fact +case class FlowFact(flow: ListSet[Method]) extends Fact { + override val hashCode: Int = { + // HERE, a foldLeft introduces a lot of overhead due to (un)boxing. + var r = 1 + flow.foreach(f => r = (r + f.hashCode()) * 31) + r + } +} +class FinalFact(flow: ListSet[Method]) extends FlowFact(flow) +object FinalFact { + def unapply(arg: FinalFact): Some[ListSet[Method]] = Some(arg.flow) +} + +/** + * A simple IFDS taint analysis. + * + * @author Dominik Helm + */ +class PRATaintAnalysis private ( + implicit + val project: SomeProject +) extends AbstractIFDSAnalysis[Fact] { + + override val property: IFDSPropertyMetaInformation[Fact] = Taint + + override def createProperty(result: Map[Statement, Set[Fact]]): IFDSProperty[Fact] = { + new Taint(result) + } + + override def normalFlow(stmt: Statement, succ: Statement, in: Set[Fact]): Set[Fact] = + stmt.stmt.astID match { + case Assignment.ASTID => + handleAssignment(stmt, stmt.stmt.asAssignment.expr, in) + case _ => in + } + + /** + * Returns true if the expression contains a taint. + */ + def isTainted(expr: Expr[V], in: Set[Fact]): Boolean = { + expr.isVar && in.exists { + case Variable(index) => expr.asVar.definedBy.contains(index) + case _ => false + } + } + + def handleAssignment(stmt: Statement, expr: Expr[V], in: Set[Fact]): Set[Fact] = + expr.astID match { + case Var.ASTID => + val newTaint = in.collect { + case Variable(index) if expr.asVar.definedBy.contains(index) => + Some(Variable(stmt.index)) + case _ => None + }.flatten + in ++ newTaint + case _ => in + } + + override def callFlow( + stmt: Statement, + callee: DeclaredMethod, + in: Set[Fact] + ): Set[Fact] = { + val call = asCall(stmt.stmt) + val allParams = call.receiverOption ++ asCall(stmt.stmt).params + if (isSink(call)) { + Set.empty + } else { + in.collect { + case Variable(index) => // Taint formal parameter if actual parameter is tainted + allParams.zipWithIndex.collect { + case (param, pIndex) if param.asVar.definedBy.contains(index) => + Variable(paramToIndex(pIndex, !callee.definedMethod.isStatic)) + } + }.flatten + } + } + + override def returnFlow( + stmt: Statement, + callee: DeclaredMethod, + exit: Statement, + succ: Statement, + in: Set[Fact] + ): Set[Fact] = { + + /** + * Checks whether the formal parameter is of a reference type, as primitive types are + * call-by-value. + */ + def isRefTypeParam(index: Int): Boolean = + if (index == -1) true + else { + callee.descriptor.parameterType( + paramToIndex(index, includeThis = false) + ).isReferenceType + } + + val call = asCall(stmt.stmt) + if (isSource(call) && stmt.stmt.astID == Assignment.ASTID) + Set(Variable(stmt.index)) + else { + val allParams = (asCall(stmt.stmt).receiverOption ++ asCall(stmt.stmt).params).toSeq + var flows: Set[Fact] = Set.empty + for (fact <- in) { + fact match { + case Variable(index) if index < 0 && index > -100 && isRefTypeParam(index) => + // Taint actual parameter if formal parameter is tainted + val param = + allParams(paramToIndex(index, !callee.definedMethod.isStatic)) + flows ++= param.asVar.definedBy.iterator.map(Variable) + + case FlowFact(flow) => + val newFlow = flow + stmt.method + flows += FlowFact(newFlow) + case _ => + } + } + + // Propagate taints of the return value + if (exit.stmt.astID == ReturnValue.ASTID && stmt.stmt.astID == Assignment.ASTID) { + val returnValue = exit.stmt.asReturnValue.expr.asVar + flows ++= in.collect { + case Variable(index) if returnValue.definedBy.contains(index) => + Variable(stmt.index) + } + } + + flows + } + } + + /** + * Converts a parameter origin to the index in the parameter seq (and vice-versa). + */ + def paramToIndex(param: Int, includeThis: Boolean): Int = + (if (includeThis) -1 else -2) - param + + override def callToReturnFlow(stmt: Statement, succ: Statement, in: Set[Fact]): Set[Fact] = { + val call = asCall(stmt.stmt) + if (isSink(call)) { + if (in.exists { + case Variable(index) => + asCall(stmt.stmt).params.exists(p => p.asVar.definedBy.contains(index)) + case _ => false + }) { + in ++ Set(FlowFact(ListSet(stmt.method))) + } else { + in + } + } else { + in + } + } + + def isSource(call: Call[V]): Boolean = { + PRATaintAnalysis.sources.get((call.name, call.descriptor)).exists(p.classHierarchy.isSubtypeOf(_, call.declaringClass)) + } + + def isSink(call: Call[V]): Boolean = { + PRATaintAnalysis.sinks.get((call.name, call.descriptor)).exists(p.classHierarchy.isSubtypeOf(_, call.declaringClass)) + } + + val entryPoints: Map[DeclaredMethod, Fact] = (for { + m <- p.allMethodsWithBody + } yield declaredMethods(m) -> NullFact).toMap + +} + +object PRATaintAnalysis extends IFDSAnalysis[Fact] { + override def init(p: SomeProject, ps: PropertyStore) = new PRATaintAnalysis()(p) + + override def property: IFDSPropertyMetaInformation[Fact] = Taint + + var sources: Map[(String, MethodDescriptor), ObjectType] = _ + var sinks: Map[(String, MethodDescriptor), ObjectType] = _ +} + +class Taint(val flows: Map[Statement, Set[Fact]]) extends IFDSProperty[Fact] { + + override type Self = Taint + + def key: PropertyKey[Taint] = Taint.key +} + +object Taint extends IFDSPropertyMetaInformation[Fact] { + override type Self = Taint + + val key: PropertyKey[Taint] = PropertyKey.create( + "PRATaint", + new Taint(Map.empty) + ) +} + +object PRATaintAnalysisRunner { + + def main(args: Array[String]): Unit = { + + val cp = new File(args(0)+"S") + val sources = new File(args(1)) + val sinks = new File(args(2)) + + val p = Project( + JavaClassFileReader().ClassFiles(cp), + JavaClassFileReader().ClassFiles(new File("/home/dominik/Desktop/android.jar")), + libraryClassFilesAreInterfacesOnly = false, + Iterable.empty + ) + p.getOrCreateProjectInformationKeyInitializationData( + PropertyStoreKey, + (context: List[PropertyStoreContext[AnyRef]]) => { + implicit val lg: LogContext = p.logContext + val ps = PKESequentialPropertyStore.apply(context: _*) + PropertyStore.updateDebug(false) + ps + } + ) + p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey)( + (i: Option[Set[Class[_ <: AnyRef]]]) => (i match { + case None => Set(classOf[l1.DefaultDomainWithCFGAndDefUse[_]]) + case Some(requirements) => requirements + classOf[l1.DefaultDomainWithCFGAndDefUse[_]] + }): Set[Class[_ <: AnyRef]] + ) + + PRATaintAnalysis.sources = getList(sources) + PRATaintAnalysis.sinks = getList(sinks) + + val ps = p.get(PropertyStoreKey) + val manager = p.get(FPCFAnalysesManagerKey) + val (_, analyses) = + manager.runAll(LazyL0BaseAIAnalysis, TACAITransformer, PRATaintAnalysis) + + val entryPoints = analyses.collect { case (_, a: PRATaintAnalysis) => a.entryPoints }.head + for { + e <- entryPoints + flows = ps(e, PRATaintAnalysis.property.key) + fact <- flows.ub.asInstanceOf[IFDSProperty[Fact]].flows.values.flatten.toSet[Fact] + } { + fact match { + case FlowFact(flow) => println(s"flow: "+flow.map(_.toJava).mkString(", ")) + case _ => + } + } + + } + + def getList(file: File): Map[(String, MethodDescriptor), ObjectType] = { + ( + for { + line <- Source.fromFile(file).getLines() + Array(declClass, returnType, signature, _) = line.split(' ') + index = signature.indexOf("(") + name = signature.substring(0, index) + parameters = signature.substring(index + 1, signature.length - 1) + jvmSignature = parameters.split(',').map(toJVMType).mkString("(", "", ")"+toJVMType(returnType)) + descriptor = MethodDescriptor(jvmSignature) + } yield (name, descriptor) -> ObjectType(declClass.replace('.', '/')) + ).toMap + } + + def toJVMType(javaType: String): String = { + val trimmedType = javaType.trim + if (trimmedType.endsWith("[]")) "["+toJVMType(trimmedType.substring(0, trimmedType.length - 2)) + else trimmedType match { + case "void" => "V" + case "byte" => "B" + case "char" => "C" + case "double" => "D" + case "float" => "F" + case "int" => "I" + case "long" => "J" + case "short" => "S" + case "boolean" => "Z" + case _ => "L"+trimmedType.replace('.', '/')+";" + } + } +} +*/ \ No newline at end of file diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/fpcf/analyses/Playground.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/fpcf/analyses/Playground.scala new file mode 100644 index 0000000000..0fae2292a8 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/fpcf/analyses/Playground.scala @@ -0,0 +1,90 @@ + +package org.opalj.fpcf.analyses + +import org.opalj.br.analyses.{BasicReport, Project, ProjectAnalysisApplication} +//import org.opalj.tac.LazyDetachedTACAIKey +//import org.opalj.tac.cg.CFA_1_0_CallGraphKey +//import org.opalj.tac.fpcf.analyses.sql.SqlStringTaintAnalyzer + +import java.net.URL + +object Playground extends ProjectAnalysisApplication{ + override def doAnalyze(project: Project[URL], parameters: Seq[String], isInterrupted: () => Boolean): BasicReport ={ + val result = analyze(project) + BasicReport(result) + } + + def analyze(project: Project[URL]): String = { + + + //val computedCallGraph = project.get(CFA_1_0_CallGraphKey) + //val callGraph = computedCallGraph.numEdges + //val tacProvider = project.get(LazyDetachedTACAIKey) + + for{ + cf <- project.allProjectClassFiles + m <- cf.methods + if m.body.isDefined + if m.name == "main" + } { + //val tac = tacProvider(m) + //println(tac.cfg) + + val insertStatement = " INSERT INTO students (name,course, studID )VALUES ('Freddy' , 'Informatik', 'tainted') ;" + + val x = a.normalizeSQLString(insertStatement) + analyzeInsert(x) + + } + + "" + } + + def analyzeInsert(insertStatement: String): Boolean = { + val pattern = raw"(INSERT).*(INTO)\s+(\w+)\s+(([\w\s,]+))\s+(VALUES)\s+([(\w\s,)]+);".r + val pattern(command, _, table, columns, _, values) = a.normalizeSQLString(insertStatement) + + println(command) + println(table) + println(columns) + println(values) + + true + } + + + object a { + val WORD = raw"(\w+|'\w+'|`\w+`)" + val SPECIAL_WORD = raw"($WORD|(\w+(-|\.|\w+)+\w+)|('\w+(-|\.|\w+)+\w+')|(`\w+(-|\.|\w+)+\w+`))" + val WORD_LIST = raw"$SPECIAL_WORD(\s*,\s*$SPECIAL_WORD)*" + val INSERT_COLUMN = raw"\(\s*$WORD_LIST\s*\)" + val VALUES = raw"\(\s*$WORD_LIST\s*\)(\s*,\s*\(\s*$WORD_LIST\s*\))*" + val INSERT_PATTERN = raw"((INSERT|insert)\s+((IGNORE|ignore)\s+)?(INTO|into)\s+)($SPECIAL_WORD)\s+$INSERT_COLUMN\s+(VALUES|values)\s+($VALUES)\s*;\s*".r + val SELECT_PATTERN = raw"(SELECT|select)\s+($WORD_LIST|\*)\s+(FROM|from)\s+($SPECIAL_WORD)\s*(\s+(WHERE|where)\s+.+)?;".r + val UPDATE_PATTERN = raw"(UPDATE|update)\s+($SPECIAL_WORD)\s+(SET|set)\s+($SPECIAL_WORD\s+=\s+$SPECIAL_WORD)(\s*,\s*$SPECIAL_WORD\s+=\s+$SPECIAL_WORD)*(\s+(WHERE|where) .+)?\s*;\s*".r + + + def checkSQLStringSyntax(string: String) = { + string match { + case INSERT_PATTERN(_*) => true + case SELECT_PATTERN(_*) => true + case UPDATE_PATTERN(_*) => true + case _ => false + } + } + + def normalizeSQLString(sqlString: String): String = { + + val pattern1 = raw"(\s+)" + val pattern2 = raw"([,;=()])" + val replacement1 = " " + val replacement2 = " $1 " + + val sb = new StringBuilder(sqlString) + sb.replace(0, sb.length(), sb.toString().replaceAll(pattern1, replacement1)) + sb.replace(0, sb.length(), sb.toString().replaceAll(pattern2, replacement2)) + sb.toString().toUpperCase().trim() + } + } + +} diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/fpcf/analyses/TaintDemo.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/fpcf/analyses/TaintDemo.scala new file mode 100644 index 0000000000..c6a16430bf --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/fpcf/analyses/TaintDemo.scala @@ -0,0 +1,49 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.analyses + +import org.opalj.ai.domain +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey +import org.opalj.br.analyses.{BasicReport, Project, ProjectAnalysisApplication} +import org.opalj.br.fpcf.FPCFAnalysesManagerKey +import org.opalj.fpcf.PropertyStore +import org.opalj.tac.cg.{AllocationSiteBasedPointsToCallGraphKey} +import org.opalj.tac.fpcf.properties.Taint +//import org.opalj.js.IFDSAnalysisJSFixtureScheduler +import org.opalj.tac.fpcf.analyses.sql.IFDSSqlAnalysisScheduler + +import java.net.URL + +object TaintDemo extends ProjectAnalysisApplication { + + override def title: String = "..." + + override def description: String = "..." + + override def doAnalyze( + project: Project[URL], + parameters: Seq[String], + isInterrupted: () => Boolean + ): BasicReport = { + val result = analyze(project) + BasicReport(result) + } + + def analyze(project: Project[URL]): String = { + + var propertyStore: PropertyStore = null + val analysesManager = project.get(FPCFAnalysesManagerKey) + project.get(AllocationSiteBasedPointsToCallGraphKey) + + project.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { _ => + Set[Class[_ <: AnyRef]](classOf[domain.l2.DefaultPerformInvocationsDomainWithCFG[URL]]) + } + + propertyStore = analysesManager.runAll( + IFDSSqlAnalysisScheduler + )._1 + propertyStore.waitOnPhaseCompletion(); + //Result: + propertyStore.entities(Taint.key).mkString("\n") + + } +} \ No newline at end of file diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/BasicIFDSTaintAnalysis.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/BasicIFDSTaintAnalysis.scala deleted file mode 100644 index 2d4bbf2f09..0000000000 --- a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/BasicIFDSTaintAnalysis.scala +++ /dev/null @@ -1,555 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses - -import java.io.File - -import scala.collection.immutable.ArraySeq -import scala.collection.immutable.ListSet - -import com.typesafe.config.ConfigValueFactory - -import org.opalj.log.LogContext -import org.opalj.util.PerformanceEvaluation -import org.opalj.util.PerformanceEvaluation.time -import org.opalj.util.Seconds -import org.opalj.fpcf.PropertyKey -import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.seq.PKESequentialPropertyStore -import org.opalj.fpcf.PropertyStoreContext -import org.opalj.bytecode.JRELibraryFolder -import org.opalj.br.fpcf.FPCFAnalysesManagerKey -import org.opalj.br.fpcf.PropertyStoreKey -import org.opalj.br.DeclaredMethod -import org.opalj.br.ObjectType -import org.opalj.br.analyses.Project -import org.opalj.br.analyses.SomeProject -import org.opalj.br.fpcf.properties.Context -import org.opalj.ai.domain.l0.PrimitiveTACAIDomain -import org.opalj.ai.domain.l1 -import org.opalj.ai.domain.l2 -import org.opalj.ai.fpcf.properties.AIDomainFactoryKey -import org.opalj.tac.cg.RTACallGraphKey -import org.opalj.tac.fpcf.analyses.AbstractIFDSAnalysis.V -import org.opalj.tac.fpcf.analyses.cg.CallGraphDeserializerScheduler -import org.opalj.tac.fpcf.properties.IFDSProperty -import org.opalj.tac.fpcf.properties.IFDSPropertyMetaInformation - -sealed trait Fact extends AbstractIFDSFact -case class Variable(index: Int) extends Fact -//case class ArrayElement(index: Int, element: Int) extends Fact -case class StaticField(classType: ObjectType, fieldName: String) extends Fact -case class InstanceField(index: Int, classType: ObjectType, fieldName: String) extends Fact -case class FlowFact(flow: ListSet[Context]) extends Fact { - // ListSet is only meant for VERY SMALL sets, but this seems to be ok here! - - override val hashCode: Int = { - // HERE, a foldLeft introduces a lot of overhead due to (un)boxing. - var r = 1 - flow.foreach(f => r = (r + f.hashCode()) * 31) - r - } -} -case object NullFact extends Fact with AbstractIFDSNullFact - -/** - * A simple IFDS taint analysis. - * - * @author Dominik Helm - * @author Mario Trageser - * @author Michael Eichberg - */ -class BasicIFDSTaintAnalysis private ( - implicit - val project: SomeProject -) extends AbstractIFDSAnalysis[Fact] { - - override val propertyKey: IFDSPropertyMetaInformation[Fact] = Taint - - override def createPropertyValue(result: Map[Statement, Set[Fact]]): IFDSProperty[Fact] = { - new Taint(result) - } - - override def normalFlow(stmt: Statement, succ: Statement, in: Set[Fact]): Set[Fact] = - stmt.stmt.astID match { - case Assignment.ASTID => - handleAssignment(stmt, stmt.stmt.asAssignment.expr, in) - - /*case ArrayStore.ASTID => - val store = stmt.stmt.asArrayStore - val definedBy = store.arrayRef.asVar.definedBy - val index = getConstValue(store.index, stmt.code) - if (isTainted(store.value, in)) - if (index.isDefined) // Taint known array index - // Instead of using an iterator, we are going to use internal iteration - // in ++ definedBy.iterator.map(ArrayElement(_, index.get)) - definedBy.foldLeft(in) { (c, n) => c + ArrayElement(n, index.get) } - else // Taint whole array if index is unknown - // Instead of using an iterator, we are going to use internal iteration: - // in ++ definedBy.iterator.map(Variable) - definedBy.foldLeft(in) { (c, n) => c + Variable(n) } - else in*/ - - case PutStatic.ASTID => - val put = stmt.stmt.asPutStatic - if (isTainted(put.value, in)) - in + StaticField(put.declaringClass, put.name) - else - in - - /*case PutField.ASTID => - val put = stmt.stmt.asPutField - if (isTainted(put.value, in)) in + StaticField(put.declaringClass, put.name) - else in*/ - case PutField.ASTID => - val put = stmt.stmt.asPutField - val definedBy = put.objRef.asVar.definedBy - if (isTainted(put.value, in)) - definedBy.foldLeft(in) { (in, defSite) => - in + InstanceField(defSite, put.declaringClass, put.name) - } - else - in - - case _ => in - } - - /** - * Returns true if the expression contains a taint. - */ - def isTainted(expr: Expr[V], in: Set[Fact]): Boolean = { - expr.isVar && in.exists { - case Variable(index) => expr.asVar.definedBy.contains(index) - //case ArrayElement(index, _) => expr.asVar.definedBy.contains(index) - case InstanceField(index, _, _) => expr.asVar.definedBy.contains(index) - case _ => false - } - } - - /** - * Returns the constant int value of an expression if it exists, None otherwise. - */ - /*def getConstValue(expr: Expr[V], code: Array[Stmt[V]]): Option[Int] = { - if (expr.isIntConst) Some(expr.asIntConst.value) - else if (expr.isVar) { - // TODO The following looks optimizable! - val constVals = expr.asVar.definedBy.iterator.map[Option[Int]] { idx => - if (idx >= 0) { - val stmt = code(idx) - if (stmt.astID == Assignment.ASTID && stmt.asAssignment.expr.isIntConst) - Some(stmt.asAssignment.expr.asIntConst.value) - else - None - } else None - }.toIterable - if (constVals.forall(option => option.isDefined && option.get == constVals.head.get)) - constVals.head - else None - } else None - }*/ - - def handleAssignment(stmt: Statement, expr: Expr[V], in: Set[Fact]): Set[Fact] = - expr.astID match { - case Var.ASTID => - // This path is not used if the representation is in standard SSA-like form. - // It is NOT optimized! - val newTaint = in.collect { - case Variable(index) if expr.asVar.definedBy.contains(index) => - Some(Variable(stmt.index)) - /*case ArrayElement(index, taintIndex) if expr.asVar.definedBy.contains(index) => - Some(ArrayElement(stmt.index, taintIndex))*/ - case _ => None - }.flatten - in ++ newTaint - - /*case ArrayLoad.ASTID => - val load = expr.asArrayLoad - if (in.exists { - // The specific array element may be tainted - case ArrayElement(index, taintedIndex) => - val element = getConstValue(load.index, stmt.code) - load.arrayRef.asVar.definedBy.contains(index) && - (element.isEmpty || taintedIndex == element.get) - // Or the whole array - case Variable(index) => load.arrayRef.asVar.definedBy.contains(index) - case _ => false - }) - in + Variable(stmt.index) - else - in*/ - - case GetStatic.ASTID => - val get = expr.asGetStatic - if (in.contains(StaticField(get.declaringClass, get.name))) - in + Variable(stmt.index) - else - in - - /*case GetField.ASTID => - val get = expr.asGetField - if (in.contains(StaticField(get.declaringClass, get.name))) - in + Variable(stmt.index) - else in*/ - case GetField.ASTID => - val get = expr.asGetField - if (in.exists { - // The specific field may be tainted - case InstanceField(index, _, taintedField) => - taintedField == get.name && get.objRef.asVar.definedBy.contains(index) - // Or the whole object - case Variable(index) => get.objRef.asVar.definedBy.contains(index) - case _ => false - }) - in + Variable(stmt.index) - else - in - - case _ => in - } - - override def callFlow( - stmt: Statement, - calleeContext: Context, - in: Set[Fact] - ): Set[Fact] = { - val callee = calleeContext.method - val call = asCall(stmt.stmt) - val allParams = call.allParams - if (callee.name == "sink") { - if (in.exists { - case Variable(index) => allParams.exists(p => p.asVar.definedBy.contains(index)) - case _ => false - }) { - println(s"Found flow: $stmt") - } - } else if (callee.name == "forName" && (callee.declaringClassType eq ObjectType.Class) && - callee.descriptor.parameterTypes == ArraySeq(ObjectType.String)) { - if (in.exists { - case Variable(index) => call.params.exists(p => p.asVar.definedBy.contains(index)) - case _ => false - }) { - println(s"Found flow: $stmt") - } - } - if (true || (callee.descriptor.returnType eq ObjectType.Class) || - (callee.descriptor.returnType eq ObjectType.Object) || - (callee.descriptor.returnType eq ObjectType.String)) { - var facts = Set.empty[Fact] - in.foreach { - case Variable(index) => // Taint formal parameter if actual parameter is tainted - allParams.iterator.zipWithIndex.foreach { - case (param, pIndex) if param.asVar.definedBy.contains(index) => - facts += Variable(paramToIndex(pIndex, !callee.definedMethod.isStatic)) - case _ => // Nothing to do - } - - /*case ArrayElement(index, taintedIndex) => - // Taint element of formal parameter if element of actual parameter is tainted - allParams.zipWithIndex.collect { - case (param, pIndex) if param.asVar.definedBy.contains(index) => - ArrayElement(paramToIndex(pIndex, !callee.definedMethod.isStatic), taintedIndex) - }*/ - - case InstanceField(index, declClass, taintedField) => - // Taint field of formal parameter if field of actual parameter is tainted - // Only if the formal parameter is of a type that may have that field! - allParams.iterator.zipWithIndex.foreach { - case (param, pIndex) if param.asVar.definedBy.contains(index) && - (paramToIndex(pIndex, !callee.definedMethod.isStatic) != -1 || - classHierarchy.isSubtypeOf(declClass, callee.declaringClassType)) => - facts += InstanceField(paramToIndex(pIndex, !callee.definedMethod.isStatic), declClass, taintedField) - case _ => // Nothing to do - } - - case sf: StaticField => - facts += sf - - case _ => // Nothing to do - } - facts - } else Set.empty - } - - override def returnFlow( - stmt: Statement, - calleeContext: Context, - exit: Statement, - succ: Statement, - in: Set[Fact] - ): Set[Fact] = { - val callee = calleeContext.method - if (callee.name == "source" && stmt.stmt.astID == Assignment.ASTID) - Set(Variable(stmt.index)) - else if (callee.name == "sanitize") - Set.empty - else { - val call = asCall(stmt.stmt) - val allParams = call.allParams - var flows: Set[Fact] = Set.empty - in.foreach { - /*case ArrayElement(index, taintedIndex) if index < 0 && index > -100 => - // Taint element of actual parameter if element of formal parameter is tainted - val param = - allParams(paramToIndex(index, !callee.definedMethod.isStatic)) - flows ++= param.asVar.definedBy.iterator.map(ArrayElement(_, taintedIndex))*/ - - case InstanceField(index, declClass, taintedField) if index < 0 && index > -255 => - // Taint field of actual parameter if field of formal parameter is tainted - val param = allParams(paramToIndex(index, !callee.definedMethod.isStatic)) - param.asVar.definedBy.foreach { defSite => - flows += InstanceField(defSite, declClass, taintedField) - } - - case sf: StaticField => - flows += sf - - case FlowFact(flow) => - val newFlow = flow + stmt.context - if (entryPoints.contains(exit.context.method)) { - //println(s"flow: "+newFlow.map(_.toJava).mkString(", ")) - } else { - flows += FlowFact(newFlow) - } - - case _ => - } - - // Propagate taints of the return value - if (exit.stmt.astID == ReturnValue.ASTID && stmt.stmt.astID == Assignment.ASTID) { - val returnValue = exit.stmt.asReturnValue.expr.asVar - in.foreach { - case Variable(index) if returnValue.definedBy.contains(index) => - flows += Variable(stmt.index) - /*case ArrayElement(index, taintedIndex) if returnValue.definedBy.contains(index) => - ArrayElement(stmt.index, taintedIndex)*/ - case InstanceField(index, declClass, taintedField) if returnValue.definedBy.contains(index) => - flows += InstanceField(stmt.index, declClass, taintedField) - - case _ => // nothing to do - } - } - - flows - } - } - - /** - * Converts a parameter origin to the index in the parameter seq (and vice-versa). - */ - def paramToIndex(param: Int, includeThis: Boolean): Int = (if (includeThis) -1 else -2) - param - - override def callToReturnFlow(stmt: Statement, succ: Statement, in: Set[Fact]): Set[Fact] = { - val call = asCall(stmt.stmt) - if (call.name == "sanitize") { - // This branch is only used by the test cases and therefore NOT optimized! - in.filter { - case Variable(index) => - !(call.params ++ call.receiverOption).exists { p => - val definedBy = p.asVar.definedBy - definedBy.size == 1 && definedBy.contains(index) - } - case _ => true - } - } else if (call.name == "forName" && - (call.declaringClass eq ObjectType.Class) && - call.descriptor.parameterTypes == ArraySeq(ObjectType.String)) { - if (in.exists { - case Variable(index) => call.params.exists(p => p.asVar.definedBy.contains(index)) - case _ => false - }) { - /*if (entryPoints.contains(declaredMethods(stmt.method))) { - println(s"flow: "+stmt.method.toJava) - in - } else*/ - in + FlowFact(ListSet(stmt.context)) - } else { - in - } - } else { - in - } - } - - /** - * If forName is called, we add a FlowFact. - */ - override def nativeCall( - statement: Statement, calleeContext: Context, successor: Statement, in: Set[Fact] - ): Set[Fact] = { - /* val allParams = asCall(statement.stmt).allParams - if (statement.stmt.astID == Assignment.ASTID && in.exists { - case Variable(index) => - allParams.zipWithIndex.exists { - case (param, _) if param.asVar.definedBy.contains(index) => true - case _ => false - } - /*case ArrayElement(index, _) => - allParams.zipWithIndex.exists { - case (param, _) if param.asVar.definedBy.contains(index) => true - case _ => false - }*/ - case _ => false - }) Set(Variable(statement.index)) - else*/ Set.empty - } - - val entryPoints: Map[DeclaredMethod, Fact] = (for { - m <- p.allMethodsWithBody - if (m.isPublic || m.isProtected) && (m.descriptor.returnType == ObjectType.Object || m.descriptor.returnType == ObjectType.Class) - index <- m.descriptor.parameterTypes.zipWithIndex.collect { case (pType, index) if pType == ObjectType.String => index } - } //yield (declaredMethods(m), null) - yield declaredMethods(m) -> Variable(-2 - index)).toMap - -} - -object BasicIFDSTaintAnalysis extends IFDSAnalysis[Fact] { - override def init(p: SomeProject, ps: PropertyStore) = new BasicIFDSTaintAnalysis()(p) - - override def property: IFDSPropertyMetaInformation[Fact] = Taint -} - -class Taint(val flows: Map[Statement, Set[Fact]]) extends IFDSProperty[Fact] { - - override type Self = Taint - - def key: PropertyKey[Taint] = Taint.key -} - -object Taint extends IFDSPropertyMetaInformation[Fact] { - override type Self = Taint - - val key: PropertyKey[Taint] = PropertyKey.create( - "TestTaint", - new Taint(Map.empty) - ) -} - -object BasicIFDSTaintAnalysisRunner { - - def main(args: Array[String]): Unit = { - if (args.contains("--help")) { - println("Potential parameters:") - println(" -cp=/Path/To/Project (to analyze the given project instead of the JDK)") - println(" -cg=/Path/To/CallGraph (to use a serialized call graph instead of a new RTA)") - println(" -seq (to use the SequentialPropertyStore)") - println(" -l2 (to use the l2 domain instead of the default l1 domain)") - println(" -primitive (to use the primitive l0 domain instead of the default l1 domain)") - println(" -delay (for a three seconds delay before the taint flow analysis is started)") - println(" -debug (to set the PropertyStore's debug flag)") - println(" -evalSchedulingStrategies (evaluates all available scheduling strategies)") - } - - if (args.contains("-debug")) { - PropertyStore.updateDebug(true) - } - - val cpArg = args.find(_.startsWith("-cp=")) - val p = - if (cpArg.isDefined) - Project(new File(cpArg.get.substring(4))) - else - Project(JRELibraryFolder) - - def evalProject(p: SomeProject): Seconds = { - if (args.contains("-l2")) { - p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { - case None => - Set(classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[_]]) - case Some(requirements) => - requirements + classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] - } - } else if (args.contains("-primitive")) { - p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { - case None => Set(classOf[PrimitiveTACAIDomain]) - case Some(requirements) => requirements + classOf[PrimitiveTACAIDomain] - } - } else { - p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { - case None => - Set(classOf[l1.DefaultDomainWithCFGAndDefUse[_]]) - case Some(requirements) => - requirements + classOf[l1.DefaultDomainWithCFGAndDefUse[_]] - } - } - - val ps = p.get(PropertyStoreKey) - val manager = p.get(FPCFAnalysesManagerKey) - var analysisTime: Seconds = Seconds.None - - val cgArg = args.find(_.startsWith("-cg=")) - PerformanceEvaluation.time { - if (cgArg.isDefined) - manager.runAll( - new CallGraphDeserializerScheduler(new File(cgArg.get.substring(4))) - ) - else - p.get(RTACallGraphKey) - } { t => println(s"CG took ${t.toSeconds}s.") } - - println("Start: "+new java.util.Date) - val analyses = time { - manager.runAll(BasicIFDSTaintAnalysis) - }(t => analysisTime = t.toSeconds)._2 - - val entryPoints = - analyses.collect { case (_, a: BasicIFDSTaintAnalysis) => a.entryPoints }.head - for { - e <- entryPoints - flows = ps(e, BasicIFDSTaintAnalysis.property.key) - fact <- flows.ub.asInstanceOf[IFDSProperty[Fact]].flows.values.flatten.toSet[Fact] - } { - fact match { - case FlowFact(flow) => - println(s"flow: "+flow.map(_.method.toJava).mkString(", ")) - case _ => - } - } - println(s"The analysis took $analysisTime.") - println( - ps.statistics.iterator.map(_.toString()).toList - .sorted - .mkString("PropertyStore Statistics:\n\t", "\n\t", "\n") - ) - - analysisTime - } - - p.getOrCreateProjectInformationKeyInitializationData( - PropertyStoreKey, - (context: List[PropertyStoreContext[AnyRef]]) => { - implicit val lg: LogContext = p.logContext - val ps = - if (args.contains("-seq")) - PKESequentialPropertyStore.apply(context: _*) - else - PKESequentialPropertyStore.apply(context: _*) // TODO exchange for a concurrent one once one exists - ps - } - ) - - if (args.contains("-delay")) { - println("Sleeping for three seconds.") - Thread.sleep(3000) - } - - if (args.contains("-evalSchedulingStrategies")) { - val results = for { - i <- 1 to 2 - strategy <- PKESequentialPropertyStore.Strategies - } yield { - println(s"Round: $i - $strategy") - val strategyValue = ConfigValueFactory.fromAnyRef(strategy) - val newConfig = - p.config.withValue(PKESequentialPropertyStore.TasksManagerKey, strategyValue) - val analysisTime = evalProject(Project.recreate(p, newConfig)) - (i, strategy, analysisTime) - org.opalj.util.gc() - } - println(results.mkString("AllResults:\n\t", "\n\t", "\n")) - } else { - evalProject(p) - } - } -} diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/IFDSEvaluation.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/IFDSEvaluation.scala new file mode 100644 index 0000000000..573b8deca8 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/IFDSEvaluation.scala @@ -0,0 +1,65 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses + +import java.io.File +import org.opalj.tac.fpcf.analyses.heros.analyses.taint.HerosBackwardClassForNameAnalysisRunner +import org.opalj.tac.fpcf.analyses.heros.analyses.taint.HerosForwardClassForNameAnalysisRunner +import org.opalj.tac.fpcf.analyses.heros.analyses.HerosAnalysis +import org.opalj.tac.fpcf.analyses.heros.analyses.HerosVariableTypeAnalysisRunner +import org.opalj.tac.fpcf.analyses.ifds.old.{AbstractIFDSAnalysis, IFDSBasedVariableTypeAnalysisRunner, IFDSBasedVariableTypeAnalysisScheduler} +import org.opalj.tac.fpcf.analyses.taint.BackwardClassForNameTaintAnalysisRunner +import org.opalj.tac.fpcf.analyses.taint.ForwardClassForNameAnalysisRunner + +/** + * Generates some evaluation files related to the AbstractIFDSAnalysis. + * + * @author Mario Trageser + */ +object IFDSEvaluation { + + /** + * args should contain exactly one parameter: + * A directory, which will contain the evaluation files; terminated with a '/'. + * + */ + def main(args: Array[String]): Unit = { + val dir = args(0) + new File(dir).mkdirs + + // Evaluation of AbstractIFDSAnalysis + ForwardClassForNameAnalysisRunner.main(Array("-seq", "-l2", "-f", dir+"ForwardClassForNameAnalysis.txt")) + BackwardClassForNameTaintAnalysisRunner.main(Array("-seq", "-l2", "-f", dir+"BackwardClassForNameAnalysis.txt")) + IFDSBasedVariableTypeAnalysisRunner.main(Array("-seq", "-l2", "-f", dir+"IFDSBasedVariableTypeAnalysis.txt")) + + // Evaluation of cross product split in returnFlow + AbstractIFDSAnalysis.OPTIMIZE_CROSS_PRODUCT_IN_RETURN_FLOW = false + ForwardClassForNameAnalysisRunner.main(Array("-seq", "-l2", "-f", dir+"ForwardClassForNameAnalysisWithFullCrossProduct.txt")) + BackwardClassForNameTaintAnalysisRunner.main(Array("-seq", "-l2", "-f", dir+"BackwardClassForNameAnalysisWithFullCrossProduct.txt")) + AbstractIFDSAnalysis.OPTIMIZE_CROSS_PRODUCT_IN_RETURN_FLOW = true + + // Evaluation of subsuming + IFDSBasedVariableTypeAnalysisScheduler.SUBSUMING = false + IFDSBasedVariableTypeAnalysisRunner.main(Array("-seq", "-l2", "-f", dir+"NoSubsuming.txt")) + IFDSBasedVariableTypeAnalysisScheduler.SUBSUMING = true + + // Evaluation of Heros analyses + HerosForwardClassForNameAnalysisRunner.main(Array("-f", dir+"HerosForwardClassForNameAnalysis.txt")) + HerosBackwardClassForNameAnalysisRunner.main(Array("-f", dir+"HerosBackwardClassForNameAnalysis.txt")) + HerosVariableTypeAnalysisRunner.main(Array("-f", dir+"HerosVariableTypeAnalysis.txt")) + + // Evaluation of parallel Heros analyses + HerosAnalysis.NUM_THREADS = 6 + HerosForwardClassForNameAnalysisRunner.main(Array("-f", dir+"ParallelHerosForwardClassForNameAnalysis.txt")) + HerosBackwardClassForNameAnalysisRunner.main(Array("-f", dir+"ParallelHerosBackwardClassForNameAnalysis.txt")) + HerosVariableTypeAnalysisRunner.main(Array("-f", dir+"ParallelHerosVariableTypeAnalysis.txt")) + HerosAnalysis.NUM_THREADS = 1 + + // Evaluation of the scheduling strategies + IFDSBasedVariableTypeAnalysisScheduler.SUBSUMING = false + ForwardClassForNameAnalysisRunner.main(Array("-evalSchedulingStrategies", "-seq", "-l2", "-f", dir+"SchedulingForwardClassForNameAnalysis.txt")) + BackwardClassForNameTaintAnalysisRunner.main(Array("-evalSchedulingStrategies", "-seq", "-l2", "-f", dir+"SchedulingBackwardClassForNameAnalysis.txt")) + IFDSBasedVariableTypeAnalysisRunner.main(Array("-evalSchedulingStrategies", "-seq", "-l2", "-f", dir+"SchedulingIFDSBasedVariableTypeAnalysis.txt")) + IFDSBasedVariableTypeAnalysisScheduler.SUBSUMING = true + } + +} diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/NewIFDSEvaluation.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/NewIFDSEvaluation.scala new file mode 100644 index 0000000000..ef5b103cd6 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/NewIFDSEvaluation.scala @@ -0,0 +1,32 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses + +import java.io.File + +import org.opalj.tac.fpcf.analyses.heros.analyses.HerosVariableTypeAnalysisRunner +import org.opalj.tac.fpcf.analyses.ifds.old +import org.opalj.tac.fpcf.analyses.ifds.IFDSBasedVariableTypeAnalysisRunner + +/** + * Generates some evaluation files related to the AbstractIFDSAnalysis. + * + * @author Mario Trageser + */ +object NewIFDSEvaluation { + + /** + * args should contain exactly one parameter: + * A directory, which will contain the evaluation files; terminated with a '/'. + * + */ + def main(args: Array[String]): Unit = { + val dir = args(0) + new File(dir).mkdirs + + old.IFDSBasedVariableTypeAnalysisRunner.main(Array("-seq", "-l2", "-f", dir+"OldIFDSBasedVariableTypeAnalysis.txt")) + HerosVariableTypeAnalysisRunner.main(Array("-f", dir+"HerosVariableTypeAnalysis.txt")) + IFDSBasedVariableTypeAnalysisRunner.main(Array("-seq", "-l2", "-f", dir+"IFDSBasedVariableTypeAnalysis.txt")) + IFDSBasedVariableTypeAnalysisRunner.main(Array("-seq", "-l2", "-subsumeFacts", "-f", dir+"IFDSBasedVariableTypeAnalysisSubsuming.txt")) + } + +} diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/HerosAnalysis.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/HerosAnalysis.scala new file mode 100644 index 0000000000..d5c9a7d998 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/HerosAnalysis.scala @@ -0,0 +1,182 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.heros.analyses + +import java.io.File +import java.io.PrintWriter +import heros.template.DefaultIFDSTabulationProblem +import heros.solver.IFDSSolver + +import javax.swing.JOptionPane +import org.opalj.bytecode +import org.opalj.util.Milliseconds +import org.opalj.util.PerformanceEvaluation.time +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.PropertyStore +import org.opalj.br.analyses.SomeProject +import org.opalj.br.Method +import org.opalj.br.analyses.DeclaredMethods +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.br.analyses.Project +import org.opalj.tac.fpcf.properties.cg.Callers +import org.opalj.ai.domain.l2 +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey +import org.opalj.tac.fpcf.analyses.heros.cfg.OpalICFG +import org.opalj.tac.fpcf.analyses.ifds.JavaStatement +import org.opalj.tac.Assignment +import org.opalj.tac.Call +import org.opalj.tac.ExprStmt +import org.opalj.tac.Stmt +import org.opalj.tac.cg.{RTACallGraphKey, TypeProviderKey} +import org.opalj.tac.fpcf.analyses.cg.TypeProvider +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V + +/** + * A common subclass of all Heros analyses. + * + * @param p The project, which will be analyzed. + * @param icfg The project's control flow graph. + * @tparam F The type of data flow facts of the analysis. + * @author Mario Trageser + */ +abstract class HerosAnalysis[F](p: SomeProject, icfg: OpalICFG) + extends DefaultIFDSTabulationProblem[JavaStatement, F, Method, OpalICFG](icfg) { + + /** + * The project's property store. + */ + implicit protected val propertyStore: PropertyStore = p.get(PropertyStoreKey) + + /** + * The project's declared methods. + */ + implicit protected val declaredMethods: DeclaredMethods = p.get(DeclaredMethodsKey) + + implicit protected val typeProvider: TypeProvider = p.get(TypeProviderKey) + + /** + * The number of threads can be configured with NUM_THREADS. + */ + override def numThreads(): Int = HerosAnalysis.NUM_THREADS + + /** + * Gets the Call for a statement that contains a call (MethodCall Stmt or ExprStmt/Assigment + * with FunctionCall) + */ + protected def asCall(stmt: Stmt[V]): Call[V] = stmt.astID match { + case Assignment.ASTID => stmt.asAssignment.expr.asFunctionCall + case ExprStmt.ASTID => stmt.asExprStmt.expr.asFunctionCall + case _ => stmt.asMethodCall + } +} + +object HerosAnalysis { + + /** + * The number of threads, with which analyses should run. + */ + var NUM_THREADS = 1 + + /** + * If this flag is set, a JOptionPane is displayed before and after each analysis run in + * evalProject. By doing so, it is easier to measure the memory consumption with VisualVM. + */ + var MEASURE_MEMORY = false + + /** + * Checks, if some method can be called from outside. + * + * @param method The method. + * @return True, if the method can be called from outside. + */ + def canBeCalledFromOutside( + method: Method + )(implicit declaredMethods: DeclaredMethods, propertyStore: PropertyStore): Boolean = { + val FinalEP(_, callers) = propertyStore(declaredMethods(method), Callers.key) + callers.hasCallersWithUnknownContext + } +} + +/** + * An abstract runner for Heros analyses. + * + * @tparam F The type of data flow facts of the analysis. + * @tparam Analysis The analysis, which will be executed. + * @author Mario Trageser + */ +abstract class HerosAnalysisRunner[F, Analysis <: HerosAnalysis[F]] { + + /** + * Creates the analysis, which will be executed. + * + * @param p The project, which will be analyzed. + * @return The analysis, which will be executed. + */ + protected def createAnalysis(p: SomeProject): Analysis + + /** + * Prints the analysis results to the console. + * + * @param analysis The analysis, which was executed. + * @param analysisTime The time, the analysis needed, in milliseconds. + */ + protected def printResultsToConsole(analysis: Analysis, analysisTime: Milliseconds): Unit + + /** + * Executes the analysis NUM_EXECUTIONS times, prints the analysis results to the console + * and saves evaluation data in evaluationFile, if present. + * + * @param evaluationFile Evaluation data will be saved in this file, if present. + */ + def run(evaluationFile: Option[File]): Unit = { + val p = Project(bytecode.RTJar) + var times = Seq.empty[Milliseconds] + for { + _ <- 1 to HerosAnalysisRunner.NUM_EXECUTIONS + } { + times :+= evalProject(Project.recreate(p)) + } + if (evaluationFile.isDefined) { + val pw = new PrintWriter(evaluationFile.get) + pw.println(s"Average time: ${times.map(_.timeSpan).sum / times.size}ms") + pw.close() + } + } + + /** + * Executes the analysis. + * + * @param p The project, which will be analyzed. + * @return The time, the analysis needed. + */ + private def evalProject(p: SomeProject): Milliseconds = { + p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { + case None => Set(classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[_]]) + case Some(requirements) => + requirements + classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] + } + p.get(RTACallGraphKey) + val analysis = createAnalysis(p) + val solver = new IFDSSolver(analysis) + var analysisTime: Milliseconds = Milliseconds.None + if (HerosAnalysis.MEASURE_MEMORY) + JOptionPane.showMessageDialog(null, "Call Graph finished") + time { + solver.solve() + } { t => + analysisTime = t.toMilliseconds + } + if (HerosAnalysis.MEASURE_MEMORY) + JOptionPane.showMessageDialog(null, "Analysis finished") + printResultsToConsole(analysis, analysisTime) + analysisTime + } +} + +object HerosAnalysisRunner { + + /** + * The number of analysis runs, which will be performed by the run method. + */ + var NUM_EXECUTIONS = 10 +} diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/HerosVariableTypeAnalysis.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/HerosVariableTypeAnalysis.scala new file mode 100644 index 0000000000..6360338a24 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/HerosVariableTypeAnalysis.scala @@ -0,0 +1,260 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.heros.analyses + +import heros.{FlowFunction, FlowFunctions, TwoElementSet} +import heros.flowfunc.{Identity, KillAll} +import org.opalj.br.{ArrayType, DeclaredMethod, FieldType, Method} +import org.opalj.br.analyses.{DeclaredMethods, DeclaredMethodsKey, SomeProject} +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.collection.immutable.EmptyIntTrieSet +import org.opalj.fpcf.{FinalEP, PropertyStore} +import org.opalj.tac._ +import org.opalj.tac.fpcf.analyses.heros.cfg.OpalForwardICFG +import org.opalj.tac.fpcf.analyses.ifds.{JavaIFDSProblem, JavaStatement} +import org.opalj.tac.fpcf.analyses.ifds.old.{CalleeType, VTAFact, VTANullFact, VariableType} +import org.opalj.tac.fpcf.properties.cg.Callees +import org.opalj.util.Milliseconds +import org.opalj.value.ValueInformation + +import java.io.File +import java.util +import java.util.Collections +import scala.annotation.tailrec +import scala.jdk.CollectionConverters._ + +/** + * An implementation of the IFDSBasedVariableTypeAnalysis in the Heros framework. + * For a documentation of the data flow functions, see IFDSBasedVariableTypeAnalysis. + * + * @author Mario Trageser + */ +class HerosVariableTypeAnalysis( + p: SomeProject, + icfg: OpalForwardICFG, + initialMethods: Map[Method, util.Set[VTAFact]] +) extends HerosAnalysis[VTAFact](p, icfg) { + + override val initialSeeds: util.Map[JavaStatement, util.Set[VTAFact]] = { + var result: Map[JavaStatement, util.Set[VTAFact]] = Map.empty + for ((m, facts) <- initialMethods) { + result += icfg.getStartPointsOf(m).iterator().next() -> facts + } + result.asJava + } + + override def createZeroValue(): VTAFact = VTANullFact + + override protected def createFlowFunctionsFactory(): FlowFunctions[JavaStatement, VTAFact, Method] = { + + new FlowFunctions[JavaStatement, VTAFact, Method]() { + + override def getNormalFlowFunction( + statement: JavaStatement, + succ: JavaStatement + ): FlowFunction[VTAFact] = { + if (!insideAnalysisContext(statement.method)) return KillAll.v() + val stmt = statement.stmt + stmt.astID match { + case Assignment.ASTID => + (source: VTAFact) => { + val fact = + newFact(statement.method, statement.stmt.asAssignment.expr, statement.index, source) + if (fact.isDefined) TwoElementSet.twoElementSet(source, fact.get) + else Collections.singleton(source) + } + case ArrayStore.ASTID => + (source: VTAFact) => { + val flow = scala.collection.mutable.Set.empty[VTAFact] + flow += source + newFact(statement.method, stmt.asArrayStore.value, statement.index, source).foreach { + case VariableType(_, t, upperBound) if !(t.isArrayType && t.asArrayType.dimensions <= 254) => + stmt.asArrayStore.arrayRef.asVar.definedBy + .foreach(flow += VariableType(_, ArrayType(t), upperBound)) + case _ => // Nothing to do + } + flow.asJava + } + case _ => Identity.v() + } + } + + override def getCallFlowFunction(stmt: JavaStatement, callee: Method): FlowFunction[VTAFact] = + if (!insideAnalysisContext(callee)) KillAll.v() + else { + val callObject = asCall(stmt.stmt) + val allParams = callObject.allParams + source: VTAFact => { + val flow = scala.collection.mutable.Set.empty[VTAFact] + source match { + case VariableType(definedBy, t, upperBound) => + allParams.iterator.zipWithIndex.foreach { + case (parameter, parameterIndex) if parameter.asVar.definedBy.contains(definedBy) => + flow += VariableType( + JavaIFDSProblem + .switchParamAndVariableIndex(parameterIndex, callee.isStatic), + t, + upperBound + ) + case _ => + } + case _ => + } + flow.asJava + } + } + + override def getReturnFlowFunction( + stmt: JavaStatement, + callee: Method, + exit: JavaStatement, + succ: JavaStatement + ): FlowFunction[VTAFact] = + if (exit.stmt.astID == ReturnValue.ASTID && stmt.stmt.astID == Assignment.ASTID) { + val returnValue = exit.stmt.asReturnValue.expr.asVar + source: VTAFact => { + source match { + // If we know the type of the return value, we create a fact for the assigned variable. + case VariableType(definedBy, t, upperBound) if returnValue.definedBy.contains(definedBy) => + Collections.singleton(VariableType(stmt.index, t, upperBound)) + case _ => Collections.emptySet() + } + } + } else KillAll.v() + + override def getCallToReturnFlowFunction( + statement: JavaStatement, + succ: JavaStatement + ): FlowFunction[VTAFact] = { + if (!insideAnalysisContext(statement.method)) return KillAll.v() + val stmt = statement.stmt + val calleeDefinitionSites = asCall(stmt).receiverOption + .map(callee => callee.asVar.definedBy) + .getOrElse(EmptyIntTrieSet) + val callOutsideOfAnalysisContext = + getCallees(statement).exists( + method => + !((method.hasSingleDefinedMethod || method.hasMultipleDefinedMethods) && insideAnalysisContext( + method.definedMethod + )) + ) + source: VTAFact => { + val result = scala.collection.mutable.Set[VTAFact](source) + source match { + case VariableType(index, t, upperBound) if calleeDefinitionSites.contains(index) => + result += CalleeType(statement.index, t, upperBound) + case _ => + } + if (callOutsideOfAnalysisContext) { + val returnType = asCall(stmt).descriptor.returnType + if (stmt.astID == Assignment.ASTID && returnType.isReferenceType) { + result += VariableType(statement.index, returnType.asReferenceType, upperBound = true) + } + } + result.asJava + } + } + } + } + + private def newFact( + method: Method, + expression: Expr[DUVar[ValueInformation]], + statementIndex: Int, + source: VTAFact + ): Option[VariableType] = expression.astID match { + case New.ASTID => + source match { + case VTANullFact => + Some(VariableType(statementIndex, expression.asNew.tpe, upperBound = false)) + case _ => None + } + case Var.ASTID => + source match { + case VariableType(index, t, upperBound) if expression.asVar.definedBy.contains(index) => + Some(VariableType(statementIndex, t, upperBound)) + case _ => None + } + case ArrayLoad.ASTID => + source match { + case VariableType(index, t, upperBound) if isArrayOfObjectType(t) && + expression.asArrayLoad.arrayRef.asVar.definedBy.contains(index) => + Some(VariableType(statementIndex, t.asArrayType.elementType.asReferenceType, upperBound)) + case _ => None + } + case GetField.ASTID | GetStatic.ASTID => + val t = expression.asFieldRead.declaredFieldType + if (t.isReferenceType) + Some(VariableType(statementIndex, t.asReferenceType, upperBound = true)) + else None + case _ => None + } + + @tailrec private def isArrayOfObjectType( + t: FieldType, + includeObjectType: Boolean = false + ): Boolean = { + if (t.isArrayType) isArrayOfObjectType(t.asArrayType.elementType, includeObjectType = true) + else if (t.isObjectType && includeObjectType) true + else false + } + + private def insideAnalysisContext(callee: Method): Boolean = + callee.body.isDefined && (callee.classFile.fqn.startsWith("java/lang") || + callee.classFile.fqn.startsWith("org/opalj/fpcf/fixtures/vta")) + + private def getCallees(statement: JavaStatement): Iterator[DeclaredMethod] = { + val context = typeProvider.newContext(declaredMethods(statement.method)) + val FinalEP(_, callees) = propertyStore(declaredMethods(statement.method), Callees.key) + callees.directCallees(context, statement.stmt.pc).map(_.method) + } + +} + +class HerosVariableTypeAnalysisRunner + extends HerosAnalysisRunner[VTAFact, HerosVariableTypeAnalysis] { + + override protected def createAnalysis(p: SomeProject): HerosVariableTypeAnalysis = { + implicit val declaredMethods: DeclaredMethods = p.get(DeclaredMethodsKey) + implicit val propertyStore: PropertyStore = p.get(PropertyStoreKey) + val initialMethods = + p.allProjectClassFiles + .filter(_.fqn.startsWith("java/lang")) + .flatMap(classFile => classFile.methods) + .filter(isEntryPoint) + .map(method => method -> entryPointsForMethod(method).asJava) + .toMap + new HerosVariableTypeAnalysis(p, new OpalForwardICFG(p), initialMethods) + } + + override protected def printResultsToConsole( + analysis: HerosVariableTypeAnalysis, + analysisTime: Milliseconds + ): Unit = {} + + private def isEntryPoint( + method: Method + )(implicit declaredMethods: DeclaredMethods, propertyStore: PropertyStore): Boolean = { + method.body.isDefined && HerosAnalysis.canBeCalledFromOutside(method) + } + + private def entryPointsForMethod(method: Method): Set[VTAFact] = { + (method.descriptor.parameterTypes.zipWithIndex.collect { + case (t, index) if t.isReferenceType => + VariableType( + JavaIFDSProblem.switchParamAndVariableIndex(index, method.isStatic), + t.asReferenceType, + upperBound = true + ) + } :+ VTANullFact).toSet + } + +} + +object HerosVariableTypeAnalysisRunner { + def main(args: Array[String]): Unit = { + val fileIndex = args.indexOf("-f") + new HerosVariableTypeAnalysisRunner().run( + if (fileIndex >= 0) Some(new File(args(fileIndex + 1))) else None + ) + } +} diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/VTAEquality.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/VTAEquality.scala new file mode 100644 index 0000000000..7d468a0d40 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/VTAEquality.scala @@ -0,0 +1,220 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.heros.analyses + +import java.io.File +import java.net.URL + +import scala.jdk.CollectionConverters._ + +import com.typesafe.config.Config +import com.typesafe.config.ConfigValueFactory +import heros.solver.IFDSSolver + +import org.opalj.util.ScalaMajorVersion +import org.opalj.fpcf.EPS +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.PropertyStore +import org.opalj.br.analyses.Project +import org.opalj.br.fpcf.FPCFAnalysesManagerKey +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.br.DefinedMethod +import org.opalj.br.Method +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.tac.fpcf.properties.cg.Callers +import org.opalj.br.analyses.DeclaredMethods +import org.opalj.br.analyses.cg.InitialEntryPointsKey +import org.opalj.br.analyses.cg.InitialInstantiatedTypesKey +import org.opalj.br.BaseConfig +import org.opalj.ai.domain.l2 +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey +import org.opalj.tac.cg.RTACallGraphKey +import org.opalj.tac.fpcf.analyses.heros.cfg.OpalForwardICFG +import org.opalj.tac.fpcf.analyses.ifds.JavaStatement +import org.opalj.tac.Assignment +import org.opalj.tac.New +import org.opalj.tac.Return +import org.opalj.tac.ReturnValue +import org.opalj.tac.fpcf.analyses.ifds.old.{CalleeType, IFDSBasedVariableTypeAnalysisScheduler, VariableType, VTAFact, VTANullFact, VTAResult} + +object VTAEquality { + + def main(args: Array[String]): Unit = { + val herosSolver = performHerosAnalysis + val opalResult = performOpalAnalysis + + opalResult.keys.foreach { + /* + * Heros only gives us results for call statements. + * Therefore, we cannot consider return statements. + * Heros also gives us the facts before the call statements, while Opal gives us the facts after. + * Therefore, we cannot consider assignments of objects, which were instantiated using a constructor, + * because the VariableTypeFact holds after the statement. + */ + case statement if statement.stmt.astID != Return.ASTID && statement.stmt.astID != ReturnValue.ASTID + && (statement.stmt.astID != Assignment.ASTID || statement.stmt.asAssignment.expr.astID != New.ASTID) => + val opalFacts = opalResult(statement) + /* + * ifdsResultsAt returns the facts before the statements. + * However, CalleeType facts hold after the statement and are therefore not returned. + * We do not consider them in this test. + */ + val herosFacts = + herosSolver.ifdsResultsAt(statement).asScala.filter(!_.isInstanceOf[CalleeType]) + if (opalFacts.size != herosFacts.size) { + println("Error: Different number of facts:") + println(s"Statement: $statement") + println(s"Opal: $opalFacts") + println(s"Heros: $herosFacts") + println() + } else + opalFacts.filter(!herosFacts.contains(_)).foreach { fact => + println("Error: Heros fact missing:") + println(s"Statement: $statement") + println(s"Fact: $fact") + println(s"Opal: $opalFacts") + println(s"Heros: $herosFacts") + println() + } + case _ => + } + } + + private def performHerosAnalysis = { + val project = FixtureProject.recreate { piKeyUnidueId => + piKeyUnidueId != PropertyStoreKey.uniqueId + } + implicit val propertyStore: PropertyStore = project.get(PropertyStoreKey) + implicit val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) + project.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { + case None => Set(classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[_]]) + case Some(requirements) => + requirements + classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] + } + project.get(RTACallGraphKey) + val initialMethods = + project.allProjectClassFiles + .filter(_.fqn.startsWith("org/opalj/fpcf/fixtures/vta")) + .flatMap(classFile => classFile.methods) + .filter(isEntryPoint) + .map(method => method -> entryPointsForMethod(method).asJava) + .toMap + val cgf = new OpalForwardICFG(project) + val herosAnalysis = new HerosVariableTypeAnalysis(project, cgf, initialMethods) + val herosSolver = new IFDSSolver(herosAnalysis) + herosSolver.solve() + herosSolver + } + + private def performOpalAnalysis = { + val project = FixtureProject.recreate { piKeyUnidueId => + piKeyUnidueId != PropertyStoreKey.uniqueId + } + val propertyStore = project.get(PropertyStoreKey) + var result = Map.empty[JavaStatement, Set[VTAFact]] + project.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { + case None => Set(classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[_]]) + case Some(requirements) => + requirements + classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] + } + project.get(RTACallGraphKey) + project.get(FPCFAnalysesManagerKey).runAll(IFDSBasedVariableTypeAnalysisScheduler) + propertyStore + .entities(IFDSBasedVariableTypeAnalysisScheduler.property.key) + .collect { + case EPS((m: DefinedMethod, inputFact)) => + (m, inputFact) + } + .foreach { entity => + val entityResult = propertyStore(entity, IFDSBasedVariableTypeAnalysisScheduler.property.key) match { + case FinalEP(_, VTAResult(map, _)) => map + case _ => throw new RuntimeException + } + entityResult.keys.foreach { declaredMethodStatement => + val statement = declaredMethodStatement.asJavaStatement + /* + * Heros returns the facts before the statements. + * However, CalleeType facts hold after the statement and are therefore not returned. + * We do not consider them in this test. + * Additionally, Heros does not return null facts, so we filter them. + */ + result = result.updated( + statement, + result.getOrElse(statement, Set.empty) ++ entityResult(declaredMethodStatement) + .filter(fact => fact != VTANullFact && !fact.isInstanceOf[CalleeType]) + ) + } + } + result + } + + private def isEntryPoint( + method: Method + )(implicit declaredMethods: DeclaredMethods, propertyStore: PropertyStore): Boolean = { + val declaredMethod = declaredMethods(method) + val FinalEP(_, callers) = propertyStore(declaredMethod, Callers.key) + method.body.isDefined && callers.hasCallersWithUnknownContext + } + + private def entryPointsForMethod(method: Method): Set[VTAFact] = { + (method.descriptor.parameterTypes.zipWithIndex.collect { + case (t, index) if t.isReferenceType => + VariableType( + switchParamAndVariableIndex(index, method.isStatic), + t.asReferenceType, + upperBound = true + ) + } :+ VTANullFact).toSet + } + + private def switchParamAndVariableIndex(index: Int, isStaticMethod: Boolean): Int = + (if (isStaticMethod) -2 else -1) - index + + final val FixtureProject: Project[URL] = { + val classFileReader = Project.JavaClassFileReader() + import classFileReader.ClassFiles + val sourceFolder = s"DEVELOPING_OPAL/validate/target/scala-$ScalaMajorVersion/test-classes" + val fixtureFiles = new File(sourceFolder) + val fixtureClassFiles = ClassFiles(fixtureFiles) + + val projectClassFiles = fixtureClassFiles.filter { cfSrc => + val (cf, _) = cfSrc + cf.thisType.packageName.startsWith("org/opalj/fpcf/fixtures") + } + + val propertiesClassFiles = fixtureClassFiles.filter { cfSrc => + val (cf, _) = cfSrc + cf.thisType.packageName.startsWith("org/opalj/fpcf/properties") + } + + val libraryClassFiles = propertiesClassFiles + + val configForEntryPoints = BaseConfig + .withValue( + InitialEntryPointsKey.ConfigKeyPrefix+"analysis", + ConfigValueFactory.fromAnyRef("org.opalj.br.analyses.cg.AllEntryPointsFinder") + ) + .withValue( + InitialEntryPointsKey.ConfigKeyPrefix+"AllEntryPointsFinder.projectMethodsOnly", + ConfigValueFactory.fromAnyRef(true) + ) + + implicit val config: Config = configForEntryPoints + .withValue( + InitialInstantiatedTypesKey.ConfigKeyPrefix+"analysis", + ConfigValueFactory.fromAnyRef("org.opalj.br.analyses.cg.AllInstantiatedTypesFinder") + ) + .withValue( + InitialInstantiatedTypesKey.ConfigKeyPrefix+ + "AllInstantiatedTypesFinder.projectClassesOnly", + ConfigValueFactory.fromAnyRef(true) + ) + + Project( + projectClassFiles, + libraryClassFiles, + libraryClassFilesAreInterfacesOnly = false, + virtualClassFiles = Iterable.empty + ) + } + +} diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/taint/HerosBackwardClassForNameAnalysis.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/taint/HerosBackwardClassForNameAnalysis.scala new file mode 100644 index 0000000000..91bd1d6c57 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/taint/HerosBackwardClassForNameAnalysis.scala @@ -0,0 +1,277 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.heros.analyses.taint + +import java.io.File +import java.util +import scala.jdk.CollectionConverters._ +import heros.FlowFunction +import heros.FlowFunctions +import heros.flowfunc.Identity +import org.opalj.util.Milliseconds +import org.opalj.br.analyses.SomeProject +import org.opalj.br.Method +import org.opalj.br.ObjectType +import org.opalj.tac.fpcf.analyses.heros.cfg.OpalBackwardICFG +import org.opalj.tac.fpcf.analyses.ifds.{JavaMethod, JavaStatement} +import org.opalj.tac.Assignment +import org.opalj.tac.BinaryExpr +import org.opalj.tac.Compare +import org.opalj.tac.PrefixExpr +import org.opalj.tac.ArrayLength +import org.opalj.tac.ArrayLoad +import org.opalj.tac.Expr +import org.opalj.tac.GetField +import org.opalj.tac.NewArray +import org.opalj.tac.PrimitiveTypecastExpr +import org.opalj.tac.Var +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V +import org.opalj.tac.ArrayStore +import org.opalj.tac.PutField +import org.opalj.tac.PutStatic +import org.opalj.tac.ReturnValue +import org.opalj.tac.fpcf.analyses.heros.analyses.HerosAnalysis +import org.opalj.tac.fpcf.analyses.heros.analyses.HerosAnalysisRunner +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem +import org.opalj.tac.fpcf.analyses.ifds.taint.TaintProblem +import org.opalj.tac.fpcf.properties._ + +/** + * An implementation of the BackwardClassForNameAnalysis in the Heros framework. + * For a documentation of the data flow functions, see BackwardClassForNameAnalysis. + * + * @author Mario Trageser + */ +class HerosBackwardClassForNameAnalysis(p: SomeProject, icfg: OpalBackwardICFG) extends HerosTaintAnalysis(p, icfg) { + + override val initialSeeds: util.Map[JavaStatement, util.Set[TaintFact]] = + p.allProjectClassFiles.filter(classFile => + classFile.thisType.fqn == "java/lang/Class") + .flatMap(classFile => classFile.methods) + .filter(_.name == "forName") + .map(method => icfg.getExitStmt(method) -> Set[TaintFact](Variable(-2)).asJava).toMap.asJava + + var flowFacts = Map.empty[Method, Set[FlowFact]] + + override def followReturnsPastSeeds(): Boolean = true + + override def createFlowFunctionsFactory(): FlowFunctions[JavaStatement, TaintFact, Method] = { + + new FlowFunctions[JavaStatement, TaintFact, Method]() { + + override def getNormalFlowFunction(statement: JavaStatement, succ: JavaStatement): FlowFunction[TaintFact] = { + val method = statement.method + val stmt = statement.stmt + source: TaintFact => { + var result = stmt.astID match { + case Assignment.ASTID => + if (isTainted(statement.index, source)) + createNewTaints(stmt.asAssignment.expr, statement) + source + else Set(source) + case ArrayStore.ASTID => + val arrayStore = stmt.asArrayStore + val arrayIndex = TaintProblem.getIntConstant(arrayStore.index, statement.code) + val arrayDefinedBy = arrayStore.arrayRef.asVar.definedBy + var facts = (source match { + // In this case, we taint the whole array. + case Variable(index) if arrayDefinedBy.contains(index) => + createNewTaints(arrayStore.value, statement) + // In this case, we taint exactly the stored element. + case ArrayElement(index, taintedElement) if arrayDefinedBy.contains(index) && + (arrayIndex.isEmpty || arrayIndex.get == taintedElement) => + createNewTaints(arrayStore.value, statement) + case _ => Set.empty[TaintFact] + }) + source + if (arrayDefinedBy.size == 1 && arrayIndex.isDefined) + facts -= ArrayElement(arrayDefinedBy.head, arrayIndex.get) + facts + case PutField.ASTID => + val putField = stmt.asPutField + val objectDefinedBy = putField.objRef.asVar.definedBy + if (source match { + case InstanceField(index, declaringClass, name) if objectDefinedBy.contains(index) && + putField.declaringClass == declaringClass && putField.name == name => + true + case _ => false + }) createNewTaints(putField.value, statement) + source + else Set(source) + case PutStatic.ASTID => + val putStatic = stmt.asPutStatic + if (source match { + case StaticField(declaringClass, name) if putStatic.declaringClass == declaringClass && putStatic.name == name => + true + case _ => false + }) createNewTaints(putStatic.value, statement) + source + else Set(source) + case _ => Set(source) + } + if (icfg.isExitStmt(succ) && HerosAnalysis.canBeCalledFromOutside(method) && (source match { + case Variable(index) if index < 0 => true + case ArrayElement(index, _) if index < 0 => true + case InstanceField(index, _, _) if index < 0 => true + case _ => false + })) { + val fact = FlowFact(Seq(JavaMethod(method))) + result += fact + flowFacts = flowFacts.updated(method, flowFacts.getOrElse(method, Set.empty[FlowFact]) + fact) + } + result.asJava + } + + } + + override def getCallFlowFunction(stmt: JavaStatement, callee: Method): FlowFunction[TaintFact] = { + val callObject = asCall(stmt.stmt) + val staticCall = callee.isStatic + source: TaintFact => { + val returnValueFacts = + if (stmt.stmt.astID == Assignment.ASTID) + source match { + case Variable(index) if index == stmt.index => + createNewTaintsForCallee(callee) + case ArrayElement(index, taintedElement) if index == stmt.index => + toArrayElement(createNewTaintsForCallee(callee), taintedElement) + case InstanceField(index, declaringClass, name) if index == stmt.index => + toInstanceField(createNewTaintsForCallee(callee), declaringClass, name) + case _ => Set.empty[TaintFact] + } + else Set.empty + val thisOffset = if (callee.isStatic) 0 else 1 + val parameterFacts = callObject.allParams.zipWithIndex + .filter(pair => (pair._2 == 0 && !staticCall) || callObject.descriptor.parameterTypes(pair._2 - thisOffset).isReferenceType) + .flatMap { pair => + val param = pair._1.asVar + val paramIndex = pair._2 + source match { + case Variable(index) if param.definedBy.contains(index) => + Some(Variable(JavaIFDSProblem.switchParamAndVariableIndex(paramIndex, staticCall))) + case ArrayElement(index, taintedElement) if param.definedBy.contains(index) => + Some(ArrayElement( + JavaIFDSProblem.switchParamAndVariableIndex(paramIndex, staticCall), taintedElement + )) + case InstanceField(index, declaringClass, name) if param.definedBy.contains(index) => + Some(InstanceField( + JavaIFDSProblem.switchParamAndVariableIndex(paramIndex, staticCall), + declaringClass, name + )) + case staticField: StaticField => Some(staticField) + case _ => None + } + } + (returnValueFacts ++ parameterFacts).asJava + } + } + + override def getReturnFlowFunction(statement: JavaStatement, callee: Method, exit: JavaStatement, succ: JavaStatement): FlowFunction[TaintFact] = { + // If a method has no caller, returnFlow will be called with a null statement. + if (statement == null) return Identity.v() + val stmt = statement.stmt + val callStatement = asCall(stmt) + val staticCall = callee.isStatic + val thisOffset = if (staticCall) 0 else 1 + val formalParameterIndices = (0 until callStatement.descriptor.parametersCount) + .map(index => JavaIFDSProblem.switchParamAndVariableIndex(index + thisOffset, staticCall)) + source: TaintFact => + (source match { + case Variable(index) if formalParameterIndices.contains(index) => + createNewTaints( + callStatement.allParams(JavaIFDSProblem.switchParamAndVariableIndex(index, staticCall)), statement + ) + case ArrayElement(index, taintedElement) if formalParameterIndices.contains(index) => + toArrayElement(createNewTaints( + callStatement.allParams(JavaIFDSProblem.switchParamAndVariableIndex(index, staticCall)), + statement + ), taintedElement) + case InstanceField(index, declaringClass, name) if formalParameterIndices.contains(index) => + toInstanceField(createNewTaints( + callStatement.allParams(JavaIFDSProblem.switchParamAndVariableIndex(index, staticCall)), + statement + ), declaringClass, name) + case staticField: StaticField => Set[TaintFact](staticField) + case _ => Set.empty[TaintFact] + }).asJava + } + + override def getCallToReturnFlowFunction(stmt: JavaStatement, succ: JavaStatement): FlowFunction[TaintFact] = + Identity.v() + } + } + + private def isTainted(index: Int, source: TaintFact, taintedElement: Option[Int] = None): Boolean = source match { + case Variable(variableIndex) => variableIndex == index + case ArrayElement(variableIndex, element) => + variableIndex == index && (taintedElement.isEmpty || taintedElement.get == element) + case _ => false + } + + private def createNewTaintsForCallee(callee: Method): Set[TaintFact] = { + icfg.getStartPointsOf(callee).asScala.flatMap { statement => + val stmt = statement.stmt + stmt.astID match { + case ReturnValue.ASTID => createNewTaints(stmt.asReturnValue.expr, statement) + case _ => Set.empty[TaintFact] + } + }.toSet + } + + private def createNewTaints(expression: Expr[V], statement: JavaStatement): Set[TaintFact] = + expression.astID match { + case Var.ASTID => expression.asVar.definedBy.map(Variable) + case ArrayLoad.ASTID => + val arrayLoad = expression.asArrayLoad + val arrayIndex = TaintProblem.getIntConstant(expression.asArrayLoad.index, statement.code) + val arrayDefinedBy = arrayLoad.arrayRef.asVar.definedBy + if (arrayIndex.isDefined) arrayDefinedBy.map(ArrayElement(_, arrayIndex.get)) + else arrayDefinedBy.map(Variable) + case BinaryExpr.ASTID | PrefixExpr.ASTID | Compare.ASTID | + PrimitiveTypecastExpr.ASTID | NewArray.ASTID | ArrayLength.ASTID => + (0 until expression.subExprCount).foldLeft(Set.empty[TaintFact])((acc, subExpr) => + acc ++ createNewTaints(expression.subExpr(subExpr), statement)) + case GetField.ASTID => + val getField = expression.asGetField + getField.objRef.asVar.definedBy + .map(InstanceField(_, getField.declaringClass, getField.name)) + /*case GetStatic.ASTID => + val getStatic = expression.asGetStatic + Set(StaticField(getStatic.declaringClass, getStatic.name))*/ + case _ => Set.empty + } + + private def toArrayElement(facts: Set[TaintFact], taintedElement: Int): Set[TaintFact] = + facts.map { + case Variable(variableIndex) => ArrayElement(variableIndex, taintedElement) + case ArrayElement(variableIndex, _) => ArrayElement(variableIndex, taintedElement) + case InstanceField(variableIndex, _, _) => ArrayElement(variableIndex, taintedElement) + } + + private def toInstanceField(facts: Set[TaintFact], declaringClass: ObjectType, name: String): Set[TaintFact] = + facts.map { + case Variable(variableIndex) => InstanceField(variableIndex, declaringClass, name) + case ArrayElement(variableIndex, _) => InstanceField(variableIndex, declaringClass, name) + case InstanceField(variableIndex, _, _) => + InstanceField(variableIndex, declaringClass, name) + } + +} + +class HerosBackwardClassForNameAnalysisRunner extends HerosAnalysisRunner[TaintFact, HerosBackwardClassForNameAnalysis] { + + override protected def createAnalysis(p: SomeProject): HerosBackwardClassForNameAnalysis = + new HerosBackwardClassForNameAnalysis(p, new OpalBackwardICFG(p)) + + override protected def printResultsToConsole(analysis: HerosBackwardClassForNameAnalysis, analysisTime: Milliseconds): Unit = { + for { + method <- analysis.flowFacts.keys + fact <- analysis.flowFacts(method) + } println(s"flow: "+fact.flow.map(_.signature).mkString(", ")) + println(s"Time: $analysisTime") + } +} + +object HerosBackwardClassForNameAnalysisRunner { + def main(args: Array[String]): Unit = { + val fileIndex = args.indexOf("-f") + new HerosBackwardClassForNameAnalysisRunner().run( + if (fileIndex >= 0) Some(new File(args(fileIndex + 1))) else None + ) + } +} \ No newline at end of file diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/taint/HerosForwardClassForNameAnalysis.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/taint/HerosForwardClassForNameAnalysis.scala new file mode 100644 index 0000000000..8a01d44ad9 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/taint/HerosForwardClassForNameAnalysis.scala @@ -0,0 +1,387 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.heros.analyses.taint + +import heros.{FlowFunction, FlowFunctions, TwoElementSet} +import heros.flowfunc.{Identity, KillAll} +import org.opalj.br.{ClassHierarchy, DeclaredMethod, Method, ObjectType} +import org.opalj.br.analyses.{DeclaredMethodsKey, SomeProject} +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.fpcf.{FinalEP, PropertyStore} +import org.opalj.tac._ +import org.opalj.tac.fpcf.analyses.heros.analyses.{HerosAnalysis, HerosAnalysisRunner} +import org.opalj.tac.fpcf.analyses.heros.cfg.OpalForwardICFG +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V +import org.opalj.tac.fpcf.analyses.ifds.taint._ +import org.opalj.tac.fpcf.analyses.ifds.{JavaIFDSProblem, JavaMethod, JavaStatement} +import org.opalj.tac.fpcf.properties._ +import org.opalj.tac.fpcf.properties.cg.Callers +import org.opalj.util.Milliseconds + +import java.io.File +import java.util +import java.util.Collections +import scala.jdk.CollectionConverters._ + +/** + * An implementation of the ForwardClassForNameAnalysis in the Heros framework. + * For documentation, see ForwardClassForNameAnalysis. + * + * @author Mario Trageser + */ +class HerosForwardClassForNameAnalysis( + p: SomeProject, + icfg: OpalForwardICFG, + initialMethods: Map[Method, util.Set[TaintFact]] +) extends HerosTaintAnalysis(p, icfg) { + + override val initialSeeds: util.Map[JavaStatement, util.Set[TaintFact]] = { + var result: Map[JavaStatement, util.Set[TaintFact]] = Map.empty + for ((m, facts) <- initialMethods) { + result += icfg.getStartPointsOf(m).iterator().next() -> facts + } + result.asJava + } + + val classHierarchy: ClassHierarchy = p.classHierarchy + + var flowFacts = Map.empty[Method, Set[FlowFact]] + + override def createFlowFunctionsFactory(): FlowFunctions[JavaStatement, TaintFact, Method] = { + + new FlowFunctions[JavaStatement, TaintFact, Method]() { + + override def getNormalFlowFunction(stmt: JavaStatement, succ: JavaStatement): FlowFunction[TaintFact] = { + stmt.stmt.astID match { + case Assignment.ASTID => + handleAssignment(stmt, stmt.stmt.asAssignment.expr) + case ArrayStore.ASTID => + val store = stmt.stmt.asArrayStore + val definedBy = store.arrayRef.asVar.definedBy + val index = TaintProblem.getIntConstant(store.index, stmt.code) + (source: TaintFact) => { + if (isTainted(store.value, source)) { + if (index.isDefined) { + (definedBy.iterator.map[TaintFact](ArrayElement(_, index.get)).toSet + source).asJava + } else { + (definedBy.iterator.map[TaintFact](Variable).toSet + source).asJava + } + } else { + if (index.isDefined && definedBy.size == 1) { + val idx = index.get + val arrayDefinedBy = definedBy.head + source match { + case ArrayElement(`arrayDefinedBy`, `idx`) => Collections.emptySet() + case _ => Collections.singleton(source) + } + } else Collections.singleton(source) + } + } + case PutStatic.ASTID => + val put = stmt.stmt.asPutStatic + (source: TaintFact) => { + if (isTainted(put.value, source)) + TwoElementSet.twoElementSet(source, StaticField(put.declaringClass, put.name)) + else Collections.singleton(source) + } + case PutField.ASTID => + val put = stmt.stmt.asPutField + val definedBy = put.objRef.asVar.definedBy + (source: TaintFact) => { + if (isTainted(put.value, source)) { + (definedBy.iterator + .map(InstanceField(_, put.declaringClass, put.name)) + .toSet[TaintFact] + source).asJava + } else { + Collections.singleton(source) + } + } + case _ => Identity.v() + } + } + + def handleAssignment(stmt: JavaStatement, expr: Expr[V]): FlowFunction[TaintFact] = + expr.astID match { + case Var.ASTID => + (source: TaintFact) => { + source match { + case Variable(index) if expr.asVar.definedBy.contains(index) => + TwoElementSet.twoElementSet(source, Variable(stmt.index)) + case _ => Collections.singleton(source) + } + } + case ArrayLoad.ASTID => + val load = expr.asArrayLoad + val arrayDefinedBy = load.arrayRef.asVar.definedBy + (source: TaintFact) => { + source match { + // The specific array element may be tainted + case ArrayElement(index, taintedIndex) => + val arrIndex = TaintProblem.getIntConstant(load.index, stmt.code) + if (arrayDefinedBy.contains(index) && + (arrIndex.isEmpty || taintedIndex == arrIndex.get)) + TwoElementSet.twoElementSet(source, Variable(stmt.index)) + else + Collections.singleton(source) + // Or the whole array + case Variable(index) if arrayDefinedBy.contains(index) => + TwoElementSet.twoElementSet(source, Variable(stmt.index)) + case _ => Collections.singleton(source) + } + } + case GetStatic.ASTID => + val get = expr.asGetStatic + (source: TaintFact) => { + if (source == StaticField(get.declaringClass, get.name)) + TwoElementSet.twoElementSet(source, Variable(stmt.index)) + else Collections.singleton(source) + } + case GetField.ASTID => + val get = expr.asGetField + val objectDefinedBy = get.objRef.asVar.definedBy + (source: TaintFact) => { + source match { + // The specific field may be tainted + case InstanceField(index, _, taintedField) if taintedField == get.name && objectDefinedBy.contains(index) => + TwoElementSet.twoElementSet(source, Variable(stmt.index)) + // Or the whole object + case Variable(index) if objectDefinedBy.contains(index) => + TwoElementSet.twoElementSet(source, Variable(stmt.index)) + case _ => Collections.singleton(source) + } + } + case BinaryExpr.ASTID | PrefixExpr.ASTID | Compare.ASTID | PrimitiveTypecastExpr.ASTID | + NewArray.ASTID | ArrayLength.ASTID => + (source: TaintFact) => { + val result = new util.HashSet[TaintFact] + (0 until expr.subExprCount) + .foreach( + subExpression => + result.addAll( + handleAssignment(stmt, expr.subExpr(subExpression)) + .computeTargets(source) + ) + ) + result + } + case _ => Identity.v() + } + + override def getCallFlowFunction(stmt: JavaStatement, callee: Method): FlowFunction[TaintFact] = { + val callObject = asCall(stmt.stmt) + val allParams = callObject.allParams + if (relevantCallee(callee)) { + val allParamsWithIndices = allParams.zipWithIndex + source: TaintFact => + (source match { + case Variable(index) => // Taint formal parameter if actual parameter is tainted + allParamsWithIndices + .collect { + case (param, paramIdx) if param.asVar.definedBy.contains(index) => + Variable( + JavaIFDSProblem.switchParamAndVariableIndex(paramIdx, callee.isStatic) + ) + } + .toSet[TaintFact] + + case ArrayElement(index, taintedIndex) => + // Taint element of formal parameter if element of actual parameter is tainted + allParamsWithIndices + .collect { + case (param, paramIdx) if param.asVar.definedBy.contains(index) => + ArrayElement( + JavaIFDSProblem.switchParamAndVariableIndex(paramIdx, callee.isStatic), + taintedIndex + ) + } + .toSet[TaintFact] + + case InstanceField(index, declClass, taintedField) => + // Taint field of formal parameter if field of actual parameter is tainted + // Only if the formal parameter is of a type that may have that field! + allParamsWithIndices + .collect { + case (param, paramIdx) if param.asVar.definedBy.contains(index) && + (JavaIFDSProblem.switchParamAndVariableIndex( + paramIdx, + callee.isStatic + ) != -1 || + classHierarchy.isSubtypeOf(declClass, callee.classFile.thisType)) => + InstanceField( + JavaIFDSProblem.switchParamAndVariableIndex(paramIdx, callee.isStatic), + declClass, + taintedField + ) + } + .toSet[TaintFact] + case sf: StaticField => Set(sf).asInstanceOf[Set[TaintFact]] + case _ => Set.empty[TaintFact] + }).asJava + } else KillAll.v() + } + + override def getReturnFlowFunction( + stmt: JavaStatement, + callee: Method, + exit: JavaStatement, + succ: JavaStatement + ): FlowFunction[TaintFact] = { + val callStatement = asCall(stmt.stmt) + val allParams = callStatement.allParams + + source: TaintFact => { + val paramFacts = source match { + + case Variable(index) if index < 0 && index > -100 && JavaIFDSProblem.isRefTypeParam(callee, index) => + val params = allParams( + JavaIFDSProblem.switchParamAndVariableIndex(index, callee.isStatic) + ) + params.asVar.definedBy.iterator.map(Variable).toSet + + case ArrayElement(index, taintedIndex) if index < 0 && index > -100 => + // Taint element of actual parameter if element of formal parameter is tainted + val params = + asCall(stmt.stmt).allParams( + JavaIFDSProblem.switchParamAndVariableIndex(index, callee.isStatic) + ) + params.asVar.definedBy.iterator + .map(ArrayElement(_, taintedIndex)) + .asInstanceOf[Iterator[TaintFact]] + .toSet + + case InstanceField(index, declClass, taintedField) if index < 0 && index > -10 => + // Taint field of actual parameter if field of formal parameter is tainted + val params = + allParams(JavaIFDSProblem.switchParamAndVariableIndex(index, callee.isStatic)) + params.asVar.definedBy.iterator + .map(InstanceField(_, declClass, taintedField)) + .asInstanceOf[Iterator[TaintFact]] + .toSet + + case sf: StaticField => Set(sf) + case FlowFact(flow) => + val flowFact = FlowFact(JavaMethod(stmt.method) +: flow) + if (initialMethods.contains(stmt.method)) + flowFacts = flowFacts.updated( + stmt.method, + flowFacts.getOrElse(stmt.method, Set.empty[FlowFact]) + flowFact + ) + Set(flowFact) + case _ => + Set.empty + } + + val returnFact = + if (exit.stmt.astID == ReturnValue.ASTID && stmt.stmt.astID == Assignment.ASTID) { + val returnValueDefinedBy = exit.stmt.asReturnValue.expr.asVar.definedBy + source match { + case Variable(index) if returnValueDefinedBy.contains(index) => + Some(Variable(stmt.index)) + case ArrayElement(index, taintedIndex) if returnValueDefinedBy.contains(index) => + Some(ArrayElement(stmt.index, taintedIndex)) + case InstanceField(index, declClass, taintedField) if returnValueDefinedBy.contains(index) => + Some(InstanceField(stmt.index, declClass, taintedField)) + case _ => None + } + } else None + + val flowFact = + if (isClassForName(callee) && source == Variable(-2)) { + val flowFact = FlowFact(Seq(JavaMethod(stmt.method))) + if (initialMethods.contains(stmt.method)) + flowFacts = flowFacts.updated( + stmt.method, + flowFacts.getOrElse(stmt.method, Set.empty[FlowFact]) + flowFact + ) + Some(flowFact) + } else None + + val allFacts = paramFacts ++ returnFact.asInstanceOf[Option[TaintFact]] ++ + flowFact.asInstanceOf[Option[TaintFact]] + + allFacts.asJava + } + } + + override def getCallToReturnFlowFunction( + stmt: JavaStatement, + succ: JavaStatement + ): FlowFunction[TaintFact] = + Identity.v() + } + } + + /** + * Returns true if the expression contains a taint. + */ + private def isTainted(expr: Expr[V], in: TaintFact): Boolean = { + expr.isVar && (in match { + case Variable(source) => expr.asVar.definedBy.contains(source) + case ArrayElement(source, _) => expr.asVar.definedBy.contains(source) + case InstanceField(source, _, _) => expr.asVar.definedBy.contains(source) + case _ => false + }) + } + + private def relevantCallee(callee: Method): Boolean = + callee.descriptor.parameterTypes.exists { + case ObjectType.Object => true + case ObjectType.String => true + case _ => false + } && (!HerosAnalysis.canBeCalledFromOutside(callee) || isClassForName(callee)) + + private def isClassForName(method: Method): Boolean = + declaredMethods(method).declaringClassType == ObjectType.Class && method.name == "forName" +} + +class HerosForwardClassForNameAnalysisRunner + extends HerosAnalysisRunner[TaintFact, HerosForwardClassForNameAnalysis] { + + override protected def createAnalysis(p: SomeProject): HerosForwardClassForNameAnalysis = { + val declaredMethods = p.get(DeclaredMethodsKey) + val propertyStore = p.get(PropertyStoreKey) + val initialMethods = scala.collection.mutable.Map.empty[Method, util.Set[TaintFact]] + for { + method <- declaredMethods.declaredMethods + .filter(canBeCalledFromOutside(_, propertyStore)) + .map(_.definedMethod) + index <- method.descriptor.parameterTypes.zipWithIndex.collect { + case (pType, index) if pType == ObjectType.String => + index + } + } { + val fact = Variable(-2 - index) + if (initialMethods.contains(method)) initialMethods(method).add(fact) + else initialMethods += (method -> new util.HashSet[TaintFact](Collections.singleton(fact))) + } + new HerosForwardClassForNameAnalysis(p, new OpalForwardICFG(p), initialMethods.toMap) + } + + override protected def printResultsToConsole( + analysis: HerosForwardClassForNameAnalysis, + analysisTime: Milliseconds + ): Unit = { + for { + method <- analysis.flowFacts.keys + fact <- analysis.flowFacts(method) + } println(s"flow: "+fact.flow.map(_.signature).mkString(", ")) + println(s"Time: $analysisTime") + } + + private def canBeCalledFromOutside( + method: DeclaredMethod, + propertyStore: PropertyStore + ): Boolean = { + val FinalEP(_, callers) = propertyStore(method, Callers.key) + callers.hasCallersWithUnknownContext + } +} + +object HerosForwardClassForNameAnalysisRunner { + + def main(args: Array[String]): Unit = { + val fileIndex = args.indexOf("-f") + new HerosForwardClassForNameAnalysisRunner().run( + if (fileIndex >= 0) Some(new File(args(fileIndex + 1))) else None + ) + } +} diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/taint/HerosTaintAnalysis.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/taint/HerosTaintAnalysis.scala new file mode 100644 index 0000000000..43de79fdb0 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/analyses/taint/HerosTaintAnalysis.scala @@ -0,0 +1,21 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.heros.analyses.taint + +import org.opalj.br.analyses.SomeProject +import org.opalj.tac.fpcf.analyses.heros.analyses.HerosAnalysis +import org.opalj.tac.fpcf.analyses.heros.cfg.OpalICFG +import org.opalj.tac.fpcf.properties._ + +/** + * A common subclass of all Heros taint analyses. + * + * @author Mario Trageser + */ +abstract class HerosTaintAnalysis(p: SomeProject, icfg: OpalICFG) extends HerosAnalysis[TaintFact](p, icfg) { + + /** + * Uses the NullFact of the TaintAnalysis. + */ + override def createZeroValue(): TaintFact = TaintNullFact + +} \ No newline at end of file diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/cfg/OpalBackwardICFG.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/cfg/OpalBackwardICFG.scala new file mode 100644 index 0000000000..1130a6b639 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/cfg/OpalBackwardICFG.scala @@ -0,0 +1,72 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.heros.cfg + +import org.opalj.br.Method +import org.opalj.br.analyses.SomeProject +import org.opalj.br.cfg.BasicBlock +import org.opalj.tac.TACode +import org.opalj.tac.fpcf.analyses.ifds.JavaStatement + +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.{Collections, Collection => JCollection, List => JList, Set => JSet} +import scala.jdk.CollectionConverters._ + +/** + * A backward ICFG for Heros analyses. + * + * @author Mario Trageser + */ +class OpalBackwardICFG(project: SomeProject) extends OpalICFG(project) { + + override def getPredsOf(stmt: JavaStatement): JList[JavaStatement] = super.getSuccsOf(stmt) + + override def getSuccsOf(stmt: JavaStatement): JList[JavaStatement] = super.getPredsOf(stmt) + + override def getStartPointsOf(m: Method): JCollection[JavaStatement] = { + val TACode(_, code, _, cfg, _) = tacai(m) + (cfg.normalReturnNode.predecessors ++ cfg.abnormalReturnNode.predecessors).map { + case bb: BasicBlock => + val index = bb.endPC + JavaStatement(m, index, code, cfg) + }.asJava + } + + override def isExitStmt(stmt: JavaStatement): Boolean = stmt.index == 0 + + override def isStartPoint(stmt: JavaStatement): Boolean = { + val cfg = stmt.cfg + val index = stmt.index + (cfg.normalReturnNode.predecessors ++ cfg.abnormalReturnNode.predecessors).exists { + case bb: BasicBlock => bb.endPC == index + } + } + + override def allNonCallStartNodes(): JSet[JavaStatement] = { + val res = new ConcurrentLinkedQueue[JavaStatement] + project.parForeachMethodWithBody() { mi => + val m = mi.method + val TACode(_, code, _, cfg, _) = tacai(m) + val endIndex = code.length + val startIndices = + (cfg.normalReturnNode.predecessors ++ cfg.abnormalReturnNode.predecessors).map { + case bb: BasicBlock => bb.endPC + } + var index = 0 + while (index < endIndex) { + val statement = JavaStatement(m, index, code, cfg) + if (!(isCallStmt(statement) || startIndices.contains(index))) + res.add(statement) + index += 1 + } + } + new java.util.HashSet(res) + Collections.emptySet() + } + + def getExitStmt(method: Method): JavaStatement = { + val tac = tacai(method) + val cfg = tac.cfg + val code = tac.stmts + JavaStatement(method, 0, code, cfg) + } +} diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/cfg/OpalForwardICFG.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/cfg/OpalForwardICFG.scala new file mode 100644 index 0000000000..b66210b769 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/cfg/OpalForwardICFG.scala @@ -0,0 +1,58 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.heros.cfg + +import java.util.{Collection => JCollection} +import java.util.{Set => JSet} +import java.util.Collections +import java.util.concurrent.ConcurrentLinkedQueue + +import org.opalj.br.Method +import org.opalj.br.analyses.SomeProject +import org.opalj.tac.TACode +import org.opalj.tac.fpcf.analyses.ifds.JavaStatement + +/** + * A forward ICFG for Heros analyses. + * + * @author Mario Trageser + */ +class OpalForwardICFG(project: SomeProject) extends OpalICFG(project) { + + override def getStartPointsOf(m: Method): JCollection[JavaStatement] = { + val TACode(_, code, _, cfg, _) = tacai(m) + Collections.singletonList(JavaStatement(m, 0, code, cfg)) + } + + override def isExitStmt(stmt: JavaStatement): Boolean = stmt.cfg.bb(stmt.index).successors.exists(_.isExitNode) + + override def isStartPoint(stmt: JavaStatement): Boolean = stmt.index == 0 + + override def allNonCallStartNodes(): JSet[JavaStatement] = { + val res = new ConcurrentLinkedQueue[JavaStatement] + project.parForeachMethodWithBody() { mi => + val m = mi.method + val TACode(_, code, _, cfg, _) = tacai(m) + val endIndex = code.length + var index = 1 + while (index < endIndex) { + val statement = JavaStatement(m, index, code, cfg) + if (!isCallStmt(statement)) + res.add(statement) + index += 1 + } + } + new java.util.HashSet(res) + Collections.emptySet() + } + + def getExitStmts(method: Method): Iterator[JavaStatement] = { + val tac = tacai(method) + val cfg = tac.cfg + val code = tac.stmts + cfg.allNodes.filter(_.isExitNode).flatMap(_.predecessors).map { bb => + val endPc = bb.asBasicBlock.endPC + JavaStatement(method, endPc, code, cfg) + } + } + +} \ No newline at end of file diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/cfg/OpalICFG.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/cfg/OpalICFG.scala new file mode 100644 index 0000000000..d69bb1cb5c --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/heros/cfg/OpalICFG.scala @@ -0,0 +1,146 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.heros.cfg + +import java.util.{List => JList} +import java.util.{Collection => JCollection} +import java.util.{Set => JSet} +import scala.jdk.CollectionConverters._ +import heros.InterproceduralCFG +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.PropertyStore +import org.opalj.value.ValueInformation +import org.opalj.br.analyses.SomeProject +import org.opalj.br.Method +import org.opalj.br.analyses.DeclaredMethods +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.tac.fpcf.properties.cg.Callees +import org.opalj.br.DefinedMethod +import org.opalj.br.MultipleDefinedMethods +import org.opalj.tac.fpcf.properties.cg.Callers +import org.opalj.tac.fpcf.analyses.ifds.JavaStatement +import org.opalj.tac.Assignment +import org.opalj.tac.DUVar +import org.opalj.tac.FunctionCall +import org.opalj.tac.LazyDetachedTACAIKey +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V +import org.opalj.tac.Expr +import org.opalj.tac.ExprStmt +import org.opalj.tac.MethodCall +import org.opalj.tac.NonVirtualFunctionCall +import org.opalj.tac.NonVirtualMethodCall +import org.opalj.tac.StaticFunctionCall +import org.opalj.tac.StaticMethodCall +import org.opalj.tac.VirtualFunctionCall +import org.opalj.tac.VirtualMethodCall +import org.opalj.tac.cg.TypeProviderKey +import org.opalj.tac.fpcf.analyses.cg.TypeProvider + +/** + * The superclass of the forward and backward ICFG for Heros analyses. + * + * @param project The project, which is analyzed. + * @author Mario Trageser + */ +abstract class OpalICFG(project: SomeProject) extends InterproceduralCFG[JavaStatement, Method] { + + val tacai: Method => TACode[TACMethodParameter, DUVar[ValueInformation]] = + project.get(LazyDetachedTACAIKey) + implicit val ps: PropertyStore = project.get(PropertyStoreKey) + implicit val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) + implicit val typeProvider: TypeProvider = project.get(TypeProviderKey) + + override def getMethodOf(stmt: JavaStatement): Method = stmt.method + + override def getPredsOf(stmt: JavaStatement): JList[JavaStatement] = { + stmt.cfg + .predecessors(stmt.index) + .map { index => + JavaStatement(stmt.method, index, stmt.code, stmt.cfg) + } + .toList + .asJava + } + + override def getSuccsOf(stmt: JavaStatement): JList[JavaStatement] = { + stmt.cfg + .successors(stmt.index) + .map { index => + JavaStatement(stmt.method, index, stmt.code, stmt.cfg) + } + .toList + .asJava + } + + override def getCalleesOfCallAt(callInstr: JavaStatement): JCollection[Method] = { + val declaredMethod = declaredMethods(callInstr.method) + val FinalEP(_, callees) = ps(declaredMethod, Callees.key) + callees + .directCallees(typeProvider.newContext(declaredMethod), callInstr.stmt.pc) + .map(_.method) + .collect { + case d: DefinedMethod => List(d.definedMethod) + case md: MultipleDefinedMethods => md.definedMethods + } + .flatten + .filter(_.body.isDefined) + .toList + .asJava + } + + override def getCallersOf(m: Method): JCollection[JavaStatement] = { + val declaredMethod = declaredMethods(m) + val FinalEP(_, callers) = ps(declaredMethod, Callers.key) + callers + .callers(declaredMethod) + .iterator.flatMap { + case (method, pc, true) => + val TACode(_, code, pcToIndex, cfg, _) = tacai(method.definedMethod) + val index = pcToIndex(pc) + Some(JavaStatement(method.definedMethod, index, code, cfg)) + case _ => None + } + .toSet + .asJava + } + + override def getCallsFromWithin(m: Method): JSet[JavaStatement] = { + val TACode(_, code, _, cfg, _) = tacai(m) + code.zipWithIndex + .collect { + case (mc: MethodCall[V], index) => JavaStatement(m, index, code, cfg) + case (as @ Assignment(_, _, _: FunctionCall[V]), index) => + JavaStatement(m, index, code, cfg) + case (ex @ ExprStmt(_, _: FunctionCall[V]), index) => + JavaStatement(m, index, code, cfg) + } + .toSet + .asJava + } + + override def getReturnSitesOfCallAt(callInstr: JavaStatement): JCollection[JavaStatement] = + getSuccsOf(callInstr) + + override def isCallStmt(stmt: JavaStatement): Boolean = { + def isCallExpr(expr: Expr[V]) = expr.astID match { + case StaticFunctionCall.ASTID | NonVirtualFunctionCall.ASTID | VirtualFunctionCall.ASTID => + true + case _ => false + } + + stmt.stmt.astID match { + case StaticMethodCall.ASTID | NonVirtualMethodCall.ASTID | VirtualMethodCall.ASTID => + true + case Assignment.ASTID => isCallExpr(stmt.stmt.asAssignment.expr) + case ExprStmt.ASTID => isCallExpr(stmt.stmt.asExprStmt.expr) + case _ => false + } + } + + override def isFallThroughSuccessor(stmt: JavaStatement, succ: JavaStatement): Boolean = ??? + + override def isBranchTarget(stmt: JavaStatement, succ: JavaStatement): Boolean = ??? + +} diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/IFDSSqlAnalysisScheduler.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/IFDSSqlAnalysisScheduler.scala new file mode 100644 index 0000000000..efcadf6b9a --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/IFDSSqlAnalysisScheduler.scala @@ -0,0 +1,18 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.sql + +import org.opalj.br.Method +import org.opalj.br.analyses.{DeclaredMethodsKey, ProjectInformationKeys, SomeProject} +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.fpcf.{PropertyBounds, PropertyStore} +import org.opalj.ifds.{IFDSAnalysisScheduler, IFDSPropertyMetaInformation} +import org.opalj.tac.cg.TypeProviderKey +import org.opalj.tac.fpcf.analyses.ifds.JavaStatement +import org.opalj.tac.fpcf.properties.{Taint, TaintFact} + +object IFDSSqlAnalysisScheduler extends IFDSAnalysisScheduler[TaintFact, Method, JavaStatement] { + override def init(p: SomeProject, ps: PropertyStore) = new SqlTaintAnalysis(p) + override def property: IFDSPropertyMetaInformation[JavaStatement, TaintFact] = Taint + override val uses: Set[PropertyBounds] = Set(PropertyBounds.ub(Taint)) + override def requiredProjectInformation: ProjectInformationKeys = Seq(TypeProviderKey, DeclaredMethodsKey, PropertyStoreKey) +} \ No newline at end of file diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/SQLFact.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/SQLFact.scala new file mode 100644 index 0000000000..e1a4a0e192 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/SQLFact.scala @@ -0,0 +1,10 @@ + +package org.opalj.tac.fpcf.analyses.sql + +import org.opalj.tac.fpcf.properties.TaintFact + +trait SQLFact extends TaintFact + +case class StringValue(index: Int, values: Set[String], taintStatus: Boolean) extends SQLFact with TaintFact + +case class SqlTaintFact(sqlTaintMemory: SqlTaintMemory) extends TaintFact diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/SqlStringAnalyzer.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/SqlStringAnalyzer.scala new file mode 100644 index 0000000000..44b02b8083 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/SqlStringAnalyzer.scala @@ -0,0 +1,288 @@ + +package org.opalj.tac.fpcf.analyses.sql + +object SqlStringAnalyzer { + + /** + * Object to store information about tainted tables and columns. + * The specified taint identifiers are used as input to mark tainted data. + */ + private var taintMemory = new SqlTaintMemory(Set("TAINTED_VALUE","'TAINTED_VALUE'")) + + /** + * When 'true', enables console output that provides information on the results of individual helper methods. + */ + val DEBUG = true + + /** + * The specified regex patterns for the types of SQL commands + */ + val INSERT_PATTERN = "^\\s*(?i)INSERT\\s*(?:IGNORE\\s+)?INTO\\s*(\\w+)\\s*\\((.*?)\\)\\s*VALUES\\s*(.*);?".r + val SELECT_PATTERN = "^\\s*(?i)SELECT\\s+(.*)\\s+(?i)FROM\\s+([^\\s]+)(?:\\s+(?i)WHERE\\s+(.+))?\\s*;?".r + val UPDATE_PATTERN = "^\\s*(?i)UPDATE\\s+([^\\s]+)\\s+(?i)SET\\s+(.+)\\s+(?i)WHERE\\s+(.+)\\s*$".r + + /** + * Analyzes INSERT, UPDATE or SELECT commands regarding taint information + * + * @param sqlString SQL command to be analyzed for taint information + * @param taintMemorySQL the object used to store and retrieve taint information + * @return True, if taint information was added or extracted. + */ + def doAnalyze(sqlString: String, taintMemorySQL: SqlTaintMemory): Boolean = { + taintMemory = taintMemorySQL + doAnalyze(sqlString) + } + + /** + * Analyzes INSERT, UPDATE or SELECT commands regarding taint information + * + * @param sqlString SQL command to be analyzed for taint information + * @return True, if taint information was added or extracted. + */ + def doAnalyze(sqlString: String):Boolean = { + + val hasValidSyntax = hasValidSqlSyntax(sqlString) + + //Normalizes the string to a uniform structure to simplify the analyses + val normalizedString = if (hasValidSyntax) normalizeSQLString(sqlString) else "" + + if(DEBUG){ + println("\n SqlAnalyzer:") + println(s"given String: $sqlString") + val message = if (hasValidSyntax) "has valid syntax" else "has invalid syntax" + println(s"$message") + println(s"Normalized SQL string: $normalizedString") + } + + //Decides which type of SQL commands will be analyzed + val result = normalizedString match { + case i@INSERT_PATTERN(_*) => + analyzeInsertStatement(i) + + case u@UPDATE_PATTERN(_*) => + analyzeUpdateStatement(u) + + case s@SELECT_PATTERN(_*) => + analyzeSelectStatment(s) + + case _ => + if(DEBUG) println("Result of SqlAnalyzer: does not support: "+ sqlString) + false + } + result + } + + /** + * Checks the string for specified and valid SQL syntax + * + * @param str The string to check. + * @return True, if the string has a recognized valid SQL syntax. + */ + def hasValidSqlSyntax(str: String): Boolean = + str match { + case INSERT_PATTERN(_*) | SELECT_PATTERN(_*) | UPDATE_PATTERN(_*) => true + case _ => false + } + + /** + * Returns a normalized version of the given SQL string by: + * replacing multiple whitespaces with a single whitespace, + * surrounding special characters with a whitespace, + * converting the string to upper case, + * and removing leading/trailing + * + * @param sqlString the SQL string to be normalized + * @return the normalized SQL string + */ + def normalizeSQLString(sqlString: String): String = { + val whitespacePattern = raw"(\s+)" + val specialCharsPattern = raw"([,;=()])" + val whitespaceReplacement = " " + val specialCharsReplacement = " $1 " + + sqlString + // Remove double whitespace + .replaceAll(whitespacePattern, whitespaceReplacement) + //Surround special characters by whitespace + .replaceAll(specialCharsPattern, specialCharsReplacement) + // Normalize case sensitivity + .toUpperCase() + //remove leading/following spaces + .trim() + } + + /** + * Checks if the values in a column are tainted, based on the INSERT command. + * + * @param normalizedString The normalized insert command to be analyzed + * @return True, if any tainted columns or values were detected. + */ + def analyzeInsertStatement(normalizedString: String) = { + val (table, columns, valueGroups) = extractInformationOfInsert(normalizedString) + + //Checks for taint identifiers in table columns.If found, the table is considered to be tainted. + var taintedColumns: Set[String] = columns.filter(column => taintMemory.isTainted(column)).toSet + + //searches for taint identifiers within value groups, and assigns any columns that contain them as tainted. + valueGroups.foreach(valueGroup => { + for (i <- valueGroup.indices) { + if (taintMemory.isTainted(valueGroup (i))) taintedColumns += columns(i).trim + } + }) + + // Checks for taint identifiers in table. If so, all addressed columns are considered as tainted + if (taintMemory.isTainted(table)) taintedColumns ++= columns.map(str => str.trim) + + //If tainted columns are found, they are recorded along with the corresponding table. + if (taintedColumns.nonEmpty) taintMemory.taintTableAndColumns(table.trim, taintedColumns) + + if(DEBUG){ + println("analyzeInsertStatement:") + println(" extracted information:") + println(s" table name: $table") + println(s" column names: ${columns.mkString("(", ",", ")")}") + println(s" valueGroups: ${valueGroups.mkString("(", ",", ")")}") + println(s" tainted columns: ${taintedColumns.mkString("(", ",", ")")} of table $table \n") + } + taintedColumns.nonEmpty + } + + /** + * Extracts addressed fields from an INSERT query + * + * @param query the query string to extract information from. + * @return A tuple containing the extracted table name, column names, and value groups. + */ + def extractInformationOfInsert(query: String): (String, Seq[String], Seq[Seq[String]]) = { + val insertPattern = "(?i)INSERT\\s*(?:IGNORE\\s+)?INTO\\s*(\\w+)\\s*\\((.*?)\\)\\s*VALUES\\s*(.*);?".r + + var extractedTableName = "" + var extractedColumnNames: Seq[String] = Seq() + var extractedValueGroups: Seq[Seq[String]] = Seq() + val pre = query.replace(";","") + + pre match { + case insertPattern(tableName, columns, values) => + extractedTableName = tableName + extractedColumnNames = columns.split(",").map(_.trim).toSeq + val removedBrackets = values.split("\\)\\s*,\\s*\\(").map(x => x.replaceAll(raw"(\(|\))", "")) + extractedValueGroups = removedBrackets.map(x => x.split(",").map(_.trim).toSeq).toSeq + + case _ => + } + + (extractedTableName, extractedColumnNames, extractedValueGroups) + } + + /** + * Checks if the values in a column are tainted, based on the UPDATE command. + * + * @param normalizedString The normalized UPDATE command to be analyzed + * @return True, if any tainted columns or values were detected. + */ + def analyzeUpdateStatement(normalizedString: String): Boolean = { + val (table, columnAndValues) = extractInformationOfUpdate(normalizedString) + var taintColumns: Set[String] = Set() + + for ((colmn, value) <- columnAndValues) { + if (taintMemory.isTainted(value) || taintMemory.isTainted(colmn) || taintMemory.isTainted(table)) { + taintColumns += colmn + } + } + + if (taintColumns.nonEmpty) taintMemory.taintTableAndColumns(table, taintColumns) + + if (DEBUG) { + println("analyzeUpdateStatment:") + println(" extracted information:") + println(s" table name: $table") + println(s" column and Values: ${columnAndValues.mkString("(", ",", ")")}") + println(s" tainted columns: ${taintColumns.mkString("(", ",", ")")} of table $table \n") + } + taintColumns.nonEmpty + + } + + /** + * Extracts addressed fields from an UPDATE query + * + * @param query the query string to extract information from. + * @return A tuple containing the extracted table name, as well as a sequence of column and value tuples. + */ + def extractInformationOfUpdate(query: String): (String, Seq[(String, String)]) = { + val updateRegex = "UPDATE\\s+([^\\s]+)\\s+SET\\s+(.+?)\\s+WHERE\\s+(.+?)\\s*;?\\s*$".r + + var table = "" + var columnValues: Seq[(String, String)] = Seq() + + updateRegex.findFirstMatchIn(query) match { + case Some(m) => + table = m.group(1) + + //extracts pairs of columns and their corresponding values. + columnValues = m.group(2).split(",").map(_.trim).toIndexedSeq.map { value => + val parts = value.split("=").map(_.trim) + (parts(0), parts(1)) + } + case _ => + } + (table, columnValues) + } + + + /** + * Checks if columns queried in SELECT commands could contain tainted information + * + * @param normalizedString The normalized SELECT command to be analyzed + * @return True, if any of the queried columns is tainted + */ + def analyzeSelectStatment(normalizedString: String): Boolean = { + val (tableName, selectedColumns) = extractInformationOfSelect(normalizedString) + + var columnsToTaint: Set[String] = selectedColumns.toSet + + // handles "SELECT * ..." + if (selectedColumns.contains("*")) { + taintMemory.getTaintedTableAndColumns(tableName) match { + case Some(x) => columnsToTaint = x + case None => + } + } + if (taintMemory.isTainted(tableName)) taintMemory.taintTableAndColumns(tableName, columnsToTaint) + + val taintedColumns = taintMemory.getTaintedColumns(tableName, columnsToTaint) + if (DEBUG) { + println("analyzeSelectStatment:") + println(" extracted information:") + println(s" table name: $tableName") + println(s" selected columns: ${selectedColumns.mkString("(", ",", ")")}") + println(s" tainted columns: ${taintedColumns.mkString("(", ",", ")")} of table $tableName \n") + } + taintedColumns.nonEmpty + } + + /** + * Extracts addressed fields from an SELECT query + * + * @param query the string to extract information from. + * @return A tuple containing the extracted table name, as well as a sequence of column + */ + def extractInformationOfSelect(query: String): (String, Seq[String]) = { + val selectRegex = "SELECT\\s+(DISTINCT\\s+)?(.*?)\\s+FROM\\s+([^\\s]+)\\s*(WHERE\\s+(.*))?".r + var table = "" + var columns: Seq[String] = Seq() + selectRegex.findFirstMatchIn(query) match { + case Some(str) => + columns = str.group(2).split(",").map(_.trim).toSeq + table = str.group(3) + case _ => + } + (table, columns) + } + + def getTaintMemory(): SqlTaintMemory ={ + taintMemory + } + + +} diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/SqlTaintMemory.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/SqlTaintMemory.scala new file mode 100644 index 0000000000..5de35a6200 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/SqlTaintMemory.scala @@ -0,0 +1,66 @@ + +package org.opalj.tac.fpcf.analyses.sql + +class SqlTaintMemory(defaultTaintIdentifiers:Set[String]) { + + private var taintIdentifiers: Set[String] = defaultTaintIdentifiers + private val taintedTableAndColumns = scala.collection.mutable.Map[String, Set[String]]() + + + /** + * Adds a new identifier to the set of taint identifiers. + * + * @param identifier the identifier to add. + */ + def addTaintIdentifier(identifier: String): Unit = { + taintIdentifiers += identifier + } + + /** + * checks if the string represent a tainted part or not + * + * @param str the String to check. + * @return true, if the string is a taint identifier + */ + def isTainted(str: String): Boolean = { + taintIdentifiers.contains(str) + } + + /** + * Adds a set of tainted columns to a table. + * + * @param tableName the name of the table. + * @param columns the set of column names to add. + */ + def taintTableAndColumns(tableName: String, columns: Set[String]): Unit = { + val prev = taintedTableAndColumns.getOrElseUpdate(tableName, Set()) + taintedTableAndColumns.update(tableName, prev ++ columns) + } + + /** + * Checks if any of the given columns in the specified table are tainted. + * + * @param tableName the name of the table to check. + * @param columns the set of column names to check. + * @return the set of given columns that have been tainted for the given table. + */ + def getTaintedColumns(tableName: String, columns: Set[String]): Set[String] = { + taintedTableAndColumns.get(tableName.trim) match { + case Some(taintedColumns) => columns.intersect(taintedColumns) + case None => Set() + } + } + + /** + * retrieves a Set of all columns in the specified table that have been tainted. + * + * @param tableName the name of the table to retrieve tainted columns from. + * @return option value containing a Set of tainted column names associated with the specified tableName. or None if no columns in the table are tainted. + */ + def getTaintedTableAndColumns(tableName: String): Option[Set[String]] = { + taintedTableAndColumns.get(tableName) + } + + + +} diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/SqlTaintProblem.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/SqlTaintProblem.scala new file mode 100644 index 0000000000..5fac5fa128 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/sql/SqlTaintProblem.scala @@ -0,0 +1,294 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.sql + +import org.opalj.br.Method +import org.opalj.br.analyses.SomeProject +import org.opalj.ifds.IFDSAnalysis +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V +import org.opalj.tac.fpcf.analyses.ifds.taint.ForwardTaintProblem +import org.opalj.tac.{ArrayLoad, ArrayStore, Expr, GetField, GetStatic, PutField, PutStatic, Stmt, StringConst, VirtualFunctionCall} +import org.opalj.tac.fpcf.analyses.ifds.{JavaIFDSProblem, JavaMethod, JavaStatement} +import org.opalj.tac.fpcf.properties._ + + +class SqlTaintAnalysis(project: SomeProject) + extends IFDSAnalysis()(project, new SqlTaintProblem(project), Taint) + +/** + * Java IFDS analysis that is able to resolve calls of sql statements. + * + * @param p project + */ +class SqlTaintProblem(p: SomeProject) extends ForwardTaintProblem(p) { + /* + final type TACAICode = TACode[TACMethodParameter, JavaIFDSProblem.V] + val tacaiKey: Method => AITACode[TACMethodParameter, ValueInformation] = p.get(ComputeTACAIKey) + + */ + + /** + * Called, when the exit to return facts are computed for some `callee` with the null fact and + * the callee's return value is assigned to a variable. + * Creates a taint, if necessary. + * + * @param callee The called method. + * @param call The call. + * @return Some variable fact, if necessary. Otherwise none. + */ + override protected def createTaints(callee: Method, call: JavaStatement): Set[TaintFact] = + if (callee.name == "source") Set(Variable(call.index)) + else Set.empty + + /** + * Called, when the call to return facts are computed for some `callee`. + * Creates a FlowFact, if necessary. + * + * @param callee The method, which was called. + * @param call The call. + * @return Some FlowFact, if necessary. Otherwise None. + */ + override protected def createFlowFact( + callee: Method, + call: JavaStatement, + in: TaintFact + ): Option[FlowFact] = + if (callee.name == "sink" && in == Variable(-2)) + Some(FlowFact(Seq(JavaMethod(call.method), JavaMethod(callee)))) + else None + + /** + * The entry points of this analysis. + */ + override def entryPoints: Seq[(Method, TaintFact)] = + for { + m <- p.allMethodsWithBody + if m.name == "main" + } yield m -> TaintNullFact + + /** + * Checks, if some `callee` is a sanitizer, which sanitizes its return value. + * In this case, no return flow facts will be created. + * + * @param callee The method, which was called. + * @return True, if the method is a sanitizer. + */ + override protected def sanitizesReturnValue(callee: Method): Boolean = callee.name == "sanitize" + + /** + * Called in callToReturnFlow. This method can return whether the input fact + * will be removed after `callee` was called. I.e. the method could sanitize parameters. + * + * @param call The call statement. + * @param in The fact which holds before the call. + * @return Whether in will be removed after the call. + */ + override protected def sanitizesParameter(call: JavaStatement, in: TaintFact): Boolean = false + + override def normalFlow(statement:JavaStatement,in:TaintFact,predecessor:Option[JavaStatement]): Set[TaintFact] ={ + in match { + case sqlTaintFact:SqlTaintFact=> Set(sqlTaintFact) + case _ => super.normalFlow(statement, in, predecessor) + } + } + + override def callFlow(call: JavaStatement, callee: Method, in: TaintFact): Set[TaintFact] = { + in match { + case sqlTaintFact: SqlTaintFact => Set(sqlTaintFact) + case _=> super.callFlow(call, callee, in) + } + } + + override def returnFlow(exit: JavaStatement, in: TaintFact, call: JavaStatement, callFact: TaintFact, successor: JavaStatement): Set[TaintFact] = { + in match { + case sqlTaintFact: SqlTaintFact => Set(sqlTaintFact) ++ super.returnFlow(exit, in, call, callFact, successor) + case _=> super.returnFlow(exit, in, call, callFact, successor) + } + } + + + /** + * Definition of rules for implicit data flows for 'executeUpdate', 'executeQuery' function. + * Definition of rules for 'append','valueOf', 'intValue', 'toString', 'sink' and 'source' function + * (not optimized and only usable for specific cases) + * + * @param call The statement, which invoked the call. + * @param in The facts, which hold before the `call`. + * @param successor + * @return The facts, which hold after the call independently of what happens in the callee + * under the assumption that `in` held before `call`. + */ + override def callToReturnFlow(call: JavaStatement, in: TaintFact, successor: JavaStatement): Set[TaintFact] = { + var flow:Set[TaintFact] = Set(in) + val callStmt = JavaIFDSProblem.asCall(call.stmt) + + callStmt.name match { + case "valueOf" if callStmt.declaringClass.toJava == "java.lang.Integer" => + in match { + case Variable(index) if callStmt.params.find(parm => parm.asVar.definedBy.contains(index)).nonEmpty => flow += Variable(call.index) + case _ => + } + flow + case "intValue" => + val leftSideVar = callStmt.receiverOption.get.asVar + in match { + case Variable(index) if leftSideVar.definedBy.contains(index) => flow += Variable(call.index) + case _ => + } + flow + case "append" => + val rightSideVar = callStmt.params.head.asVar + val leftSideVar = callStmt.receiverOption.get.asVar + in match { + case Variable(index) if leftSideVar.definedBy.contains(index)||rightSideVar.definedBy.contains(index) => + flow += Variable(call.index) + case _=> + } + flow + case "toString" => + val leftSideVar = callStmt.receiverOption.get.asVar + in match { + case Variable(index) if leftSideVar.definedBy.contains(index) => flow += Variable(call.index) + case _ => + } + flow + case "executeUpdate" => + if(callStmt.params.size >0){ + val possibleParamStrings = getPossibleStrings(callStmt.params(0),call.code,in) + possibleParamStrings.foreach(input => + if ( + input.contains("TAINTED_VALUE") && + SqlStringAnalyzer.hasValidSqlSyntax(input) + && SqlStringAnalyzer.doAnalyze(input, new SqlTaintMemory(Set("TAINTED_VALUE","'TAINTED_VALUE'"))) ) { + flow += SqlTaintFact(SqlStringAnalyzer.getTaintMemory()) + }) + } + flow + case "executeQuery" => + in match { + case sqlTaintFact: SqlTaintFact => + val possibleParamStrings = getPossibleStrings(callStmt.params(0),call.code,in) + possibleParamStrings.foreach(string => { + if(SqlStringAnalyzer.hasValidSqlSyntax(string) + && SqlStringAnalyzer.doAnalyze(string,sqlTaintFact.sqlTaintMemory) + && call.stmt.isAssignment){ + flow += Variable(call.index) + } + }) + case _ => + } + flow + case "source" => + in match { + case TaintNullFact => flow += Variable(call.index) + case _ => + } + flow + case "sink" => + in match { + case Variable(index) if callStmt.params.find(parm => parm.asVar.definedBy.contains(index)).nonEmpty => + flow += FlowFact(Seq(JavaMethod(call.method))) + case _=> + } + flow + case _ => + icfg.getCalleesIfCallStatement(call) match { + case Some(callee) if callee.isEmpty => flow + case _ => super.callToReturnFlow(call, in, successor) + } + + } + } + + /** + * Returns all possible constant strings. Concatenations of constants using the "append" function are reconstructed. + * tainted variables are replaced by taint identifiers. + *(not optimized and only usable for specific cases) + * + * @param param Expression of a parameter for which a string is to be obtained. + * @param stmts Statements used for reconstruction + * @param in The fact which holds before the call. + * @return a set of possible and modified strings + */ + def getPossibleStrings(param:Expr[V], stmts: Array[Stmt[V]], in:TaintFact):Set[String] = { + // Initialize the set of possible strings and taint status + var possibleStrings: Set[String] = Set() + + //def sites of the queried variable + val defSites = param.asVar.definedBy + + // Go through all defsites to find out more possible strings + for (defSiteIndex <- defSites) { + var currentSetOfString: Set[String] = Set() + + in match { + // If the expression for the index is maintained, we replace it with the TAINTED_VALUE + case Variable(index) if index == defSiteIndex => + currentSetOfString = Set("TAINTED_VALUE") + case ArrayElement(index: Int,_) if index == defSiteIndex => + currentSetOfString = Set("TAINTED_VALUE") + case InstanceField(index: Int, _, _) if index == defSiteIndex=> + currentSetOfString = Set("TAINTED_VALUE") + case _ if defSiteIndex >= 0 =>{ + val expr = stmts(defSiteIndex).asAssignment.expr + + //Further expression investigation + expr match { + // Take the value from the string constant + case StringConst(_, value) => currentSetOfString = Set(value) + + case GetField(_,_,_,_,_) => + //To get a value we look for the last PutField + val lastPut = stmts.lastIndexWhere(stmt => stmt.astID == PutField.ASTID) + if(lastPut >0){ + val putFieldExpression = stmts(lastPut).asPutField.value + currentSetOfString = getPossibleStrings(putFieldExpression,stmts, in) + } + + case gstc:GetStatic => + //To get a value we look for the last PutStatic + val lastPut = stmts.lastIndexWhere(stmt => stmt.astID == PutStatic.ASTID) + if(lastPut > 0){ + val putExpression = stmts(lastPut).asPutStatic.value + currentSetOfString = getPossibleStrings(putExpression,stmts, in) + } + + case ArrayLoad(pc,index,arrayRef) => + val elementIndex = index.asVar.value + val lastArrayStoreIndex = stmts.lastIndexWhere(stmt => stmt.astID == ArrayStore.ASTID && + stmt.asArrayStore.index.asVar.value == elementIndex) + if(lastArrayStoreIndex > 0){ + val arrayElementExpression = stmts(lastArrayStoreIndex).asArrayStore.value + currentSetOfString = getPossibleStrings(arrayElementExpression,stmts,in) + } + + case VirtualFunctionCall(_, _, _, name, _, receiver, _) if name == "toString" => + currentSetOfString = getPossibleStrings(receiver.asVar, stmts, in) + + case VirtualFunctionCall(_, _, _, name, _, receiver, params) if name == "append" => + + //All possible strings that can be obtained from the receiver of the function 'append' + val leftSideString = getPossibleStrings(receiver.asVar, stmts, in) + + //All possible strings that can be obtained from the parameter of the function 'append' + val rightSideString = getPossibleStrings(params.head.asVar, stmts, in) + + //All strings generated on the left side in combination with all strings on the right side. + for { + leftString <- leftSideString + rightString <- rightSideString + } { + currentSetOfString += leftString + rightString + } + case _ => currentSetOfString = Set("") + } + } + + //We assume that an index < 0 is a parameter. + case _ => currentSetOfString = Set("PARAM_VALUE") + + } + possibleStrings = possibleStrings ++ currentSetOfString + } + possibleStrings + } +} diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/taint/BackwardClassForNameTaintAnalysisScheduler.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/taint/BackwardClassForNameTaintAnalysisScheduler.scala new file mode 100644 index 0000000000..5e4f40c3cc --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/taint/BackwardClassForNameTaintAnalysisScheduler.scala @@ -0,0 +1,154 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.taint + +import org.opalj.br.analyses.SomeProject +import org.opalj.br.{DeclaredMethod, DefinedMethod, Method} +import org.opalj.fpcf.{EPS, FinalEP, PropertyStore} +import org.opalj.ifds.IFDSPropertyMetaInformation +import org.opalj.tac.cg.RTACallGraphKey +import org.opalj.tac.fpcf.analyses.ifds.JavaMethod +import org.opalj.tac.fpcf.analyses.ifds.old.taint.BackwardTaintProblem +import org.opalj.tac.fpcf.analyses.ifds.old._ +import org.opalj.tac.fpcf.properties._ +import org.opalj.tac.fpcf.properties.{FlowFact, OldTaint} + +import java.io.File + +/** + * A backward IFDS taint analysis, which tracks the String parameters of all methods of the rt.jar, + * * which are callable from outside the library, to calls of Class.forName. + * + * @author Mario Trageser + */ +class BackwardClassForNameTaintAnalysisScheduler private (implicit val project: SomeProject) + extends BackwardIFDSAnalysis(new BackwardClassForNameTaintProblem(project), OldTaint) + +class BackwardClassForNameTaintProblem(p: SomeProject) extends BackwardTaintProblem(p) { + + /** + * The string parameters of all public methods are entry points. + */ + override val entryPoints: Seq[(DeclaredMethod, TaintFact)] = + p.allProjectClassFiles.filter(classFile => + classFile.thisType.fqn == "java/lang/Class") + .flatMap(classFile => classFile.methods) + .filter(_.name == "forName") + .map(method => declaredMethods(method) -> Variable(-2)) + + /** + * There is no sanitizing in this analysis. + */ + override protected def sanitizesReturnValue(callee: DeclaredMethod): Boolean = false + + /** + * There is no sanitizing in this analysis. + */ + override protected def sanitizesParameter(call: DeclaredMethodJavaStatement, in: TaintFact): Boolean = false + + /** + * Do not perform unbalanced return for methods, which can be called from outside the library. + */ + override def shouldPerformUnbalancedReturn(source: (DeclaredMethod, TaintFact)): Boolean = { + super.shouldPerformUnbalancedReturn(source) && + (!canBeCalledFromOutside(source._1) || + // The source is callable from outside, but should create unbalanced return facts. + entryPoints.contains(source)) + } + + /** + * This analysis does not create FlowFacts at calls. + * Instead, FlowFacts are created at the start node of methods. + */ + override protected def createFlowFactAtCall(call: DeclaredMethodJavaStatement, in: Set[TaintFact], + source: (DeclaredMethod, TaintFact)): Option[FlowFact] = None + + /** + * This analysis does not create FlowFacts at returns. + * Instead, FlowFacts are created at the start node of methods. + */ + protected def applyFlowFactFromCallee( + calleeFact: FlowFact, + source: (DeclaredMethod, TaintFact) + ): Option[FlowFact] = None + + /** + * If we analyzed a transitive caller of the sink, which is callable from outside the library, + * and a formal parameter is tainted, we create a FlowFact. + */ + override protected def createFlowFactAtBeginningOfMethod( + in: Set[TaintFact], + source: (DeclaredMethod, TaintFact) + ): Option[FlowFact] = { + if (source._2.isInstanceOf[UnbalancedReturnFact[TaintFact @unchecked]] && + canBeCalledFromOutside(source._1) && in.exists { + // index < 0 means, that it is a parameter. + case Variable(index) if index < 0 => true + case ArrayElement(index, _) if index < 0 => true + case InstanceField(index, _, _) if index < 0 => true + case _ => false + }) { + Some(FlowFact(currentCallChain(source).map(JavaMethod(_)))) + } else None + } +} + +object BackwardClassForNameTaintAnalysisScheduler extends IFDSAnalysisScheduler[TaintFact] { + + override def init(p: SomeProject, ps: PropertyStore): BackwardClassForNameTaintAnalysisScheduler = { + p.get(RTACallGraphKey) + new BackwardClassForNameTaintAnalysisScheduler()(p) + } + + override def property: IFDSPropertyMetaInformation[DeclaredMethodJavaStatement, TaintFact] = OldTaint +} + +class BackwardClassForNameTaintAnalysisRunner extends AbsractIFDSAnalysisRunner { + + override def analysisClass: BackwardClassForNameTaintAnalysisScheduler.type = BackwardClassForNameTaintAnalysisScheduler + + override def printAnalysisResults(analysis: AbstractIFDSAnalysis[_], ps: PropertyStore): Unit = { + val propertyKey = BackwardClassForNameTaintAnalysisScheduler.property.key + val flowFactsAtSources = ps.entities(propertyKey).collect { + case EPS((m: DefinedMethod, inputFact)) if canBeCalledFromOutside(m, ps) => + (m, inputFact) + }.flatMap(ps(_, propertyKey) match { + case FinalEP(_, OldTaint(result, _)) => + result.values.fold(Set.empty)((acc, facts) => acc ++ facts).filter { + case FlowFact(_) => true + case _ => false + } + case _ => Seq.empty + }) + for { + fact <- flowFactsAtSources + } { + fact match { + case FlowFact(flow) => println(s"flow: "+flow.asInstanceOf[Seq[Method]].map(_.toJava).mkString(", ")) + case _ => + } + } + } +} + +object BackwardClassForNameTaintAnalysisRunner { + def main(args: Array[String]): Unit = { + if (args.contains("--help")) { + println("Potential parameters:") + println(" -seq (to use the SequentialPropertyStore)") + println(" -l2 (to use the l2 domain instead of the default l1 domain)") + println(" -delay (for a three seconds delay before the taint flow analysis is started)") + println(" -debug (for debugging mode in the property store)") + println(" -evalSchedulingStrategies (evaluates all available scheduling strategies)") + println(" -f (Stores the average runtime to this file)") + } else { + val fileIndex = args.indexOf("-f") + new BackwardClassForNameTaintAnalysisRunner().run( + args.contains("-debug"), + args.contains("-l2"), + args.contains("-delay"), + args.contains("-evalSchedulingStrategies"), + if (fileIndex >= 0) Some(new File(args(fileIndex + 1))) else None + ) + } + } +} \ No newline at end of file diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/taint/ForwardClassForNameTaintAnalysisScheduler.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/taint/ForwardClassForNameTaintAnalysisScheduler.scala new file mode 100644 index 0000000000..bca0428035 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/tac/fpcf/analyses/taint/ForwardClassForNameTaintAnalysisScheduler.scala @@ -0,0 +1,138 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.taint + +import org.opalj.br.analyses.SomeProject +import org.opalj.br.{DeclaredMethod, Method, ObjectType} +import org.opalj.fpcf.PropertyStore +import org.opalj.ifds.{IFDSProperty, IFDSPropertyMetaInformation} + +import org.opalj.tac.cg.RTACallGraphKey +import org.opalj.tac.fpcf.analyses.ifds.old._ +import org.opalj.tac.fpcf.properties._ +import org.opalj.tac.fpcf.analyses.ifds._ +import org.opalj.tac.fpcf.properties.OldTaint +import java.io.File + +import org.opalj.tac.fpcf.analyses.ifds.taint.TaintProblem + +/** + * A forward IFDS taint analysis, which tracks the String parameters of all methods of the rt.jar, + * which are callable from outside the library, to calls of Class.forName. + * + * @author Dominik Helm + * @author Mario Trageser + * @author Michael Eichberg + */ +class ForwardClassForNameTaintAnalysis$Scheduler private (implicit val project: SomeProject) + extends ForwardIFDSAnalysis(new ForwardClassForNameTaintProblem(project), OldTaint) + +class ForwardClassForNameTaintProblem(project: SomeProject) + extends old.taint.ForwardTaintProblem(project) with TaintProblem[DeclaredMethod, DeclaredMethodJavaStatement, TaintFact] { + + /** + * The string parameters of all public methods are entry points. + */ + override def entryPoints: Seq[(DeclaredMethod, TaintFact)] = for { + m <- methodsCallableFromOutside.toSeq + if !m.definedMethod.isNative + index <- m.descriptor.parameterTypes.zipWithIndex.collect { + case (pType, index) if pType == ObjectType.String => index + } + } yield (m, Variable(-2 - index)) + + /** + * There is no sanitizing in this analysis. + */ + override protected def sanitizesReturnValue(callee: DeclaredMethod): Boolean = false + + /** + * There is no sanitizing in this analysis. + */ + override protected def sanitizesParameter(call: DeclaredMethodJavaStatement, in: TaintFact): Boolean = false + + /** + * This analysis does not create new taints on the fly. + * Instead, the string parameters of all public methods are tainted in the entry points. + */ + override protected def createTaints(callee: DeclaredMethod, call: DeclaredMethodJavaStatement): Set[TaintFact] = + Set.empty + + /** + * Create a FlowFact, if Class.forName is called with a tainted variable for the first parameter. + */ + override protected def createFlowFact(callee: DeclaredMethod, call: DeclaredMethodJavaStatement, + in: Set[TaintFact]): Option[FlowFact] = + if (isClassForName(callee) && in.contains(Variable(-2))) + Some(FlowFact(Seq(JavaMethod(call.method)))) + else None + + /** + * We only analyze methods with String parameters (and therefore also in Object parameters). + * Additionally, we have to analyze Class.forName, so that FlowFacts will be created. + */ + override protected def relevantCallee(callee: DeclaredMethod): Boolean = + callee.descriptor.parameterTypes.exists { + case ObjectType.Object => true + case ObjectType.String => true + case _ => false + } && (!canBeCalledFromOutside(callee) || isClassForName(callee)) + + /** + * Checks, if a `method` is Class.forName. + * + * @param method The method. + * @return True, if the method is Class.forName. + */ + private def isClassForName(method: DeclaredMethod): Boolean = + method.declaringClassType == ObjectType.Class && method.name == "forName" +} + +object ForwardClassForNameTaintAnalysis$Scheduler extends IFDSAnalysisScheduler[TaintFact] { + + override def init(p: SomeProject, ps: PropertyStore): ForwardClassForNameTaintAnalysis$Scheduler = { + p.get(RTACallGraphKey) + new ForwardClassForNameTaintAnalysis$Scheduler()(p) + } + + override def property: IFDSPropertyMetaInformation[DeclaredMethodJavaStatement, TaintFact] = OldTaint +} + +class ForwardClassForNameAnalysisRunner extends AbsractIFDSAnalysisRunner { + + override def analysisClass: ForwardClassForNameTaintAnalysis$Scheduler.type = ForwardClassForNameTaintAnalysis$Scheduler + + override def printAnalysisResults(analysis: AbstractIFDSAnalysis[_], ps: PropertyStore): Unit = + for { + e <- analysis.ifdsProblem.entryPoints + flows = ps(e, ForwardClassForNameTaintAnalysis$Scheduler.property.key) + fact <- flows.ub.asInstanceOf[IFDSProperty[DeclaredMethodJavaStatement, TaintFact]].flows.values.flatten.toSet[TaintFact] + } { + fact match { + case FlowFact(flow) => println(s"flow: "+flow.asInstanceOf[Set[Method]].map(_.toJava).mkString(", ")) + case _ => + } + } +} + +object ForwardClassForNameAnalysisRunner { + def main(args: Array[String]): Unit = { + if (args.contains("--help")) { + println("Potential parameters:") + println(" -seq (to use the SequentialPropertyStore)") + println(" -l2 (to use the l2 domain instead of the default l1 domain)") + println(" -delay (for a three seconds delay before the taint flow analysis is started)") + println(" -debug (for debugging mode in the property store)") + println(" -evalSchedulingStrategies (evaluates all available scheduling strategies)") + println(" -f (Stores the average runtime to this file)") + } else { + val fileIndex = args.indexOf("-f") + new ForwardClassForNameAnalysisRunner().run( + args.contains("-debug"), + args.contains("-l2"), + args.contains("-delay"), + args.contains("-evalSchedulingStrategies"), + if (fileIndex >= 0) Some(new File(args(fileIndex + 1))) else None + ) + } + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/js/Java2JsTestClass.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/js/Java2JsTestClass.java new file mode 100644 index 0000000000..46d683d967 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/js/Java2JsTestClass.java @@ -0,0 +1,345 @@ +package org.opalj.fpcf.fixtures.js; + +import org.opalj.fpcf.properties.taint.ForwardFlowPath; + +import javax.script.*; + +public class Java2JsTestClass { + /* Test flows through Javascript. */ + + @ForwardFlowPath({"flowThroughJS"}) + public static void flowThroughJS() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + String pw = source(); + + se.put("secret", pw); + se.eval("var x = 42;"); + String fromJS = (String) se.get("secret"); + sink(fromJS); + } + + @ForwardFlowPath({}) + public static void jsOverwritesBinding() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + String pw = source(); + + se.put("secret", pw); + se.eval("secret = \"42\";"); + String fromJS = (String) se.get("secret"); + sink(fromJS); + } + + @ForwardFlowPath({"flowInsideJS"}) + public static void flowInsideJS() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + String pw = source(); + + se.put("secret", pw); + se.eval("var xxx = secret;"); + String fromJS = (String) se.get("xxx"); + sink(fromJS); + } + + @ForwardFlowPath({"flowInsideJS2"}) + public static void flowInsideJS2() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + String pw = source(); + + se.put("secret", pw); + se.eval("var xxx = secret;" + + "var yyy = xxx;"); + String fromJS = (String) se.get("yyy"); + sink(fromJS); + } + + @ForwardFlowPath({}) + public static void flowInsideJSLateOverwrite() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + String pw = source(); + + se.put("secret", pw); + se.eval("var xxx = secret;" + + "var xxx = 42;"); + String fromJS = (String) se.get("yyy"); + sink(fromJS); + } + + @ForwardFlowPath({"jsInvokeIdentity"}) + public static void jsInvokeIdentity() throws ScriptException, NoSuchMethodException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + String pw = source(); + + se.eval("function id(x) { return x; }"); + String fromJS = (String) ((Invocable) se).invokeFunction("id", pw); + sink(fromJS); + } + + @ForwardFlowPath({}) + public static void jsInvokeOverwrite() throws ScriptException, NoSuchMethodException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + String pw = source(); + + se.eval("function overwrite(x) { return \"42\"; }"); + String fromJS = (String) ((Invocable) se).invokeFunction("id", pw); + sink(fromJS); + } + + @ForwardFlowPath({"jsInvokeWithComputation"}) + public static void jsInvokeWithComputation() throws ScriptException, NoSuchMethodException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + se.eval("function check(str) {\n" + + " return str === \"1337\";\n" + + "}"); + + String pw = source(); + + Invocable inv = (Invocable) se; + Boolean state = (Boolean) inv.invokeFunction("check", pw); + sink(state); + } + + @ForwardFlowPath({}) + public static void jsUnusedTaintedParameter() throws ScriptException, NoSuchMethodException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + se.eval("function check(str, unused) {\n" + + " return str === \"1337\";\n" + + "}"); + + String pw = source(); + Invocable inv = (Invocable) se; + Boolean state = (Boolean) inv.invokeFunction("check", "1337", pw); + sink(state); + } + + /* More advanced flows. */ + + @ForwardFlowPath({"jsInterproceduralFlow"}) + public static void jsInterproceduralFlow() throws ScriptException, NoSuchMethodException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + + se.eval("function add42(x) {" + + " return x + 42;" + + "}" + + "function id(x) {" + + " return x;" + + "}" + + "function check(str) {\n" + + " return id(add42(str)) === id(\"1337\");\n" + + "}"); + String pw = source(); + Invocable inv = (Invocable) se; + Boolean state = (Boolean) inv.invokeFunction("check", pw); + sink(state); + } + + @ForwardFlowPath({"jsFunctionFlow"}) + public static void jsFunctionFlow() throws ScriptException, NoSuchMethodException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + + se.eval("function myfun(x) {\n" + + " var xxx = x;\n" + + " return xxx;\n" + + "}\n"); + String pw = source(); + Invocable inv = (Invocable) se; + String value = (String) inv.invokeFunction("myfun", pw); + sink(value); + } + + /* Test flows through ScriptEngine objects. */ + + @ForwardFlowPath({"simplePutGet"}) + public static void simplePutGet() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + + String pw = source(); + se.put("secret", pw); + Object out = se.get("secret"); + sink(out); + } + + @ForwardFlowPath({"overapproxPutGet"}) + public static void overapproxPutGet() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + + String pw = source(); + // String is no constant + se.put(Integer.toString(1337), pw); + // Because the .put had no constant string, we do not know the key here + // and taint the return as an over-approximation. + Object out = se.get("secret"); + sink(out); + } + + @ForwardFlowPath({}) + public static void overwritePutGet() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + + String pw = source(); + // String is no constant + se.put("secret", pw); + se.put("secret", "Const"); + Object out = se.get("secret"); + sink(out); + } + + @ForwardFlowPath({"bindingsSimplePutGet"}) + public static void bindingsSimplePutGet() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + Bindings b = se.createBindings(); + + String pw = source(); + se.put("secret", pw); + Object out = se.get("secret"); + sink(out); + } + + @ForwardFlowPath({"bindingsOverapproxPutGet"}) + public static void bindingsOverapproxPutGet() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + Bindings b = se.createBindings(); + + String pw = source(); + // String is no constant + se.put(Integer.toString(1337), pw); + // Because the .put had no constant string, we do not know the key here + // and taint the return as an over-approximation. + Object out = se.get("secret"); + sink(out); + } + + @ForwardFlowPath({}) + public static void BindingsOverwritePutGet() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + Bindings b = se.createBindings(); + + String pw = source(); + se.put("secret", pw); + se.put("secret", "Const"); + Object out = se.get("secret"); + sink(out); + } + + @ForwardFlowPath({}) + public static void bindingsPutRemoveGet() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + Bindings b = se.createBindings(); + + String pw = source(); + b.put("secret", pw); + b.remove("secret"); + Object out = b.get("secret"); + sink(out); + } + + @ForwardFlowPath({}) + public static void overwritePutRemoveGet() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + Bindings b = se.createBindings(); + + String pw = source(); + b.put("secret", pw); + b.remove("secret"); + Object out = b.get("secret"); + sink(out); + } + + @ForwardFlowPath({"bindingsPutAll"}) + public static void bindingsPutAll() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + Bindings b = se.createBindings(); + + String pw = source(); + b.put("secret", pw); + Bindings newb = se.createBindings(); + newb.putAll(b); + Object out = newb.get("secret"); + sink(out); + } + + @ForwardFlowPath({"interproceduralPutGet"}) + public static void interproceduralPutGet() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + + String pw = source(); + se.put("secret", pw); + id (se); + Object out = se.get("secret"); + sink(out); + } + + @ForwardFlowPath({}) + public static void interproceduralOverwrite() throws ScriptException + { + ScriptEngineManager sem = new ScriptEngineManager(); + ScriptEngine se = sem.getEngineByName("JavaScript"); + + String pw = source(); + se.put("secret", pw); + removeSecret (se); + Object out = se.get("secret"); + sink(out); + } + + public static Object id(Object obj) { + return obj; + } + + public static void removeSecret(ScriptEngine se) { + se.put("secret", 42); + } + + public static String source() { + return "1337"; + } + + private static void sink(String i) { + System.out.println(i); + } + + private static void sink(Object i) { + System.out.println(i); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/taint/TaintAnalysisTestClass.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/taint/TaintAnalysisTestClass.java new file mode 100644 index 0000000000..932c019863 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/taint/TaintAnalysisTestClass.java @@ -0,0 +1,487 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.fixtures.taint; + +import org.opalj.fpcf.properties.taint.BackwardFlowPath; +import org.opalj.fpcf.properties.taint.ForwardFlowPath; + +/** + * @author Mario Trageser + */ +public class TaintAnalysisTestClass { + + private static int staticField; + + private int instanceField; + + @ForwardFlowPath({"callChainsAreConsidered", "passToSink"}) + @BackwardFlowPath({"callChainsAreConsidered", "passToSink", "sink"}) + public void callChainsAreConsidered() { + passToSink(source()); + } + + @ForwardFlowPath({"returnEdgesFromInstanceMethodsArePresent"}) + @BackwardFlowPath({"returnEdgesFromInstanceMethodsArePresent", "sink"}) + public void returnEdgesFromInstanceMethodsArePresent() { + sink(callSourcePublic()); + } + + @ForwardFlowPath({"returnEdgesFromPrivateMethodsArePresent"}) + @BackwardFlowPath({"returnEdgesFromPrivateMethodsArePresent", "sink"}) + public void returnEdgesFromPrivateMethodsArePresent() { + sink(callSourceNonStatic()); + } + + @ForwardFlowPath({"multiplePathsAreConsidered_1", "indirectPassToSink", "passToSink"}) + @BackwardFlowPath({"multiplePathsAreConsidered_1", "indirectPassToSink", "passToSink", "sink"}) + public void multiplePathsAreConsidered_1() { + int i = source(); + passToSink(i); + indirectPassToSink(i); + } + + @ForwardFlowPath({"multiplePathsAreConsidered_2", "passToSink"}) + @BackwardFlowPath({"multiplePathsAreConsidered_2", "passToSink", "sink"}) + public void multiplePathsAreConsidered_2() { + int i = source(); + passToSink(i); + indirectPassToSink(i); + } + + @ForwardFlowPath({"ifEdgesAreConsidered"}) + @BackwardFlowPath({"ifEdgesAreConsidered", "sink"}) + public void ifEdgesAreConsidered() { + int i; + if(Math.random() < .5) { + i = source(); + } else { + i = 0; + } + sink(i); + } + + @ForwardFlowPath({"elseEdgesAreConsidered"}) + @BackwardFlowPath({"elseEdgesAreConsidered", "sink"}) + public void elseEdgesAreConsidered() { + int i; + if(Math.random() < .5) { + i = 0; + } else { + i = source(); + } + sink(i); + } + + @ForwardFlowPath({"forLoopsAreConsidered"}) + @BackwardFlowPath({"forLoopsAreConsidered", "sink"}) + public void forLoopsAreConsidered() { + int[] arr = new int[2]; + for(int i = 0; i < arr.length; i++) { + sink(arr[0]); + arr[i] = source(); + } + } + + @ForwardFlowPath("returnOfIdentityFunctionIsConsidered") + @BackwardFlowPath({"returnOfIdentityFunctionIsConsidered", "sink"}) + public void returnOfIdentityFunctionIsConsidered() { + sink(identity(source())); + } + + @ForwardFlowPath({"summaryEdgesOfRecursiveFunctionsAreComputedCorrectly"}) + @BackwardFlowPath({"summaryEdgesOfRecursiveFunctionsAreComputedCorrectly", "sink"}) + public void summaryEdgesOfRecursiveFunctionsAreComputedCorrectly() { + sink(recursion(0)); + } + + public int recursion(int i) { + return i == 0 ? recursion(source()) : i; + } + + @ForwardFlowPath({"codeInCatchNodesIsConsidered"}) + @BackwardFlowPath({"codeInCatchNodesIsConsidered", "sink"}) + public void codeInCatchNodesIsConsidered() { + int i = source(); + try { + throw new RuntimeException(); + } catch(RuntimeException e) { + sink(i); + } + } + + @ForwardFlowPath({"codeInFinallyNodesIsConsidered"}) + @BackwardFlowPath({"codeInFinallyNodesIsConsidered", "sink"}) + public void codeInFinallyNodesIsConsidered() { + int i = 1; + try { + throw new RuntimeException(); + } catch(RuntimeException e) { + i = source(); + } finally { + sink(i); + } + } + + @ForwardFlowPath({"unaryExpressionsPropagateTaints"}) + @BackwardFlowPath({"unaryExpressionsPropagateTaints", "sink"}) + public void unaryExpressionsPropagateTaints() { + sink(-source()); + } + + @ForwardFlowPath({"binaryExpressionsPropagateTaints"}) + @BackwardFlowPath({"binaryExpressionsPropagateTaints", "sink"}) + public void binaryExpressionsPropagateTaints() { + sink(source() + 1); + } + + @ForwardFlowPath({"arrayLengthPropagatesTaints"}) + @BackwardFlowPath({"arrayLengthPropagatesTaints", "sink"}) + public void arrayLengthPropagatesTaints() { + sink(new Object[source()].length); + } + + @ForwardFlowPath({"singleArrayIndicesAreTainted_1"}) + @BackwardFlowPath({"singleArrayIndicesAreTainted_1", "sink"}) + public void singleArrayIndicesAreTainted_1() { + int[] arr = new int[2]; + arr[0] = source(); + sink(arr[0]); + } + + @ForwardFlowPath({}) + @BackwardFlowPath({}) + public void singleArrayIndicesAreTainted_2() { + int[] arr = new int[2]; + arr[0] = source(); + sink(arr[1]); + } + + @ForwardFlowPath({"wholeArrayTaintedIfIndexUnknown"}) + @BackwardFlowPath({"wholeArrayTaintedIfIndexUnknown", "sink"}) + public void wholeArrayTaintedIfIndexUnknown() { + int[] arr = new int[2]; + arr[(int) (Math.random() * 2)] = source(); + sink(arr[0]); + } + + @ForwardFlowPath({"arrayElementTaintsArePropagatedToCallee_1", "passFirstArrayElementToSink"}) + @BackwardFlowPath({"arrayElementTaintsArePropagatedToCallee_1", "passFirstArrayElementToSink", + "sink"}) + public void arrayElementTaintsArePropagatedToCallee_1() { + int[] arr = new int[2]; + arr[0] = source(); + passFirstArrayElementToSink(arr); + } + + @ForwardFlowPath({}) + @BackwardFlowPath({}) + public void arrayElementTaintsArePropagatedToCallee_2() { + int[] arr = new int[2]; + arr[1] = source(); + passFirstArrayElementToSink(arr); + } + + @ForwardFlowPath({"arrayElementTaintsArePropagatedBack_1", "passFirstArrayElementToSink"}) + @BackwardFlowPath({"arrayElementTaintsArePropagatedBack_1", "passFirstArrayElementToSink", + "sink"}) + public void arrayElementTaintsArePropagatedBack_1() { + int[] arr = new int[2]; + taintRandomElement(arr); + passFirstArrayElementToSink(arr); + } + + @ForwardFlowPath({}) + @BackwardFlowPath({}) + public void arrayElementTaintsArePropagatedBack_2() { + int[] arr = new int[2]; + taintFirstElement(arr); + sink(arr[1]); + } + + @ForwardFlowPath({"callerParameterIsTaintedIfCalleeTaintsFormalParameter", + "passFirstArrayElementToSink"}) + @BackwardFlowPath({"callerParameterIsTaintedIfCalleeTaintsFormalParameter", + "passFirstArrayElementToSink", "sink"}) + public void callerParameterIsTaintedIfCalleeTaintsFormalParameter() { + int[] arr = new int[2]; + taintRandomElement(arr); + passFirstArrayElementToSink(arr); + } + + @ForwardFlowPath({}) + @BackwardFlowPath({}) + public void taintDisappearsWhenReassigning() { + int[] arr = new int[2]; + arr[0] = source(); + arr[0] = 0; + sink(arr[0]); + } + + @ForwardFlowPath({}) + @BackwardFlowPath({}) + public void nativeMethodsCanBeHandeled() { + int j = nativeMethod(0); + sink(j); + } + + @ForwardFlowPath({"returnValueOfNativeMethodIsTainted"}) + @BackwardFlowPath({"returnValueOfNativeMethodIsTainted", "sink"}) + public void returnValueOfNativeMethodIsTainted() { + sink(nativeMethod(source())); + } + + @ForwardFlowPath({"analysisUsesCallGraph_1"}) + @BackwardFlowPath({"analysisUsesCallGraph_1", "sink"}) + public void analysisUsesCallGraph_1() { + A a = new B(); + sink(a.get()); + } + + @ForwardFlowPath({}) + @BackwardFlowPath({}) + public void analysisUsesCallGraph_2() { + A a = new C(); + sink(a.get()); + } + + @ForwardFlowPath({"analysisUsesCallGraph_3"}) + @BackwardFlowPath({"analysisUsesCallGraph_3", "sink"}) + public void analysisUsesCallGraph_3() { + A a; + if(Math.random() < .5) + a = new B(); + else + a = new C(); + sink(a.get()); + } + + @ForwardFlowPath({}) + @BackwardFlowPath({}) + public void sanitizeRemovesTaint() { + sink(sanitize(source())); + } + + @ForwardFlowPath({"instanceFieldsAreTainted"}) + @BackwardFlowPath({"instanceFieldsAreTainted", "sink"}) + public void instanceFieldsAreTainted() { + instanceField = source(); + sink(instanceField); + } + + @ForwardFlowPath({"staticFieldsAreTainted"}) + @BackwardFlowPath({"staticFieldsAreTainted", "sink"}) + public void staticFieldsAreTainted() { + staticField = source(); + sink(staticField); + } + + @ForwardFlowPath({"fieldTaintsArePassed", "passWrappedValueToSink"}) + @BackwardFlowPath({"fieldTaintsArePassed", "passWrappedValueToSink", "sink"}) + public void fieldTaintsArePassed() { + passWrappedValueToSink(new Wrapper(source())); + } + + @ForwardFlowPath({"fieldTaintsAreAppliedInReturnFlow"}) + @BackwardFlowPath({"fieldTaintsAreAppliedInReturnFlow", "sink"}) + public void fieldTaintsAreAppliedInReturnFlow() { + sink(createTaintedWrapper().field); + } + + @ForwardFlowPath({"fieldTaintsOfParametersAreAppliedInReturnFlow"}) + @BackwardFlowPath({"fieldTaintsOfParametersAreAppliedInReturnFlow", "sink"}) + public void fieldTaintsOfParametersAreAppliedInReturnFlow() { + Wrapper wrapper = new Wrapper(); + taintWrappedValue(wrapper); + sink(wrapper.field); + } + + @ForwardFlowPath({"fieldTaintsAreConsideredInComputations"}) + @BackwardFlowPath({"fieldTaintsAreConsideredInComputations", "sink"}) + public void fieldTaintsAreConsideredInComputations() { + Wrapper wrapper = new Wrapper(source()); + sink(wrapper.field + 1); + } + + @ForwardFlowPath({}) + @BackwardFlowPath({}) + public void staticCalleeOverwritesTaint() { + Wrapper wrapper = new Wrapper(source()); + overwriteField(wrapper); + sink(wrapper.field); + } + + @ForwardFlowPath({}) + @BackwardFlowPath({}) + public void calleeOverwritesItsOwnField() { + Wrapper wrapper = new Wrapper(source()); + wrapper.overwrite(); + sink(wrapper.field); + } + + // TODO: misses that the parameter and this aliases +// @ForwardFlowPath({}) +// @BackwardFlowPath({}) +// public void instanceCalleeOverwritesTaint() { +// Wrapper wrapper = new Wrapper(source()); +// wrapper.overwriteArg(wrapper); +// sink(wrapper.field); +// } + + @ForwardFlowPath({}) + @BackwardFlowPath({}) + public void calleeOverwritesStaticFieldTaint() { + staticField = source(); + overwriteStaticField(); + sink(staticField); + } + + @ForwardFlowPath({}) + @BackwardFlowPath({}) + public void taintPartOfArrayOverwrite() { + int[] arr = new int[1]; + arr[0] = source(); + overwriteFirstArrayElement(arr); + sink(staticField); + } + + //TODO Tests für statische Felder über Methodengrenzen + + //Does not work, because we do not know which exceptions cannot be thrown. + /*@ForwardFlowPath({}) + public void onlyThrowableExceptionsAreConsidered() { + int i = 0; + try { + divide(1, i); + } catch(IllegalArgumentException e) { + i = source(); + } + sink(i); + }*/ + + //Does not work, because the analysis does not know that there is only one iteration. + /*@ForwardFlowPath({}) + public void iterationCountIsConsidered() { + int[] arr = new int[2]; + for(int i = 0; i < 1; i++) { + sink(arr[0]); + arr[i] = source(); + } + }*/ + + /* Tests using summaries. */ + @ForwardFlowPath({"flowWithSummary"}) + public static void flowWithSummary() { + Integer i = Integer.valueOf(source()); + sink(i.intValue()); + } + + public int callSourcePublic() { + return source(); + } + + private int callSourceNonStatic() { + return source(); + } + + private static void passToSink(int i) { + sink(i); + } + + private void indirectPassToSink(int i) { + passToSink(i); + } + + private void passFirstArrayElementToSink(int[] arr) { + sink(arr[0]); + } + + private void taintRandomElement(int[] arr) { + arr[Math.random() < .5 ? 0 : 1] = source(); + } + + private void taintFirstElement(int[] arr) { + arr[0] = source(); + } + + private native int nativeMethod(int i); + + private int identity(int i) {return i;} + + private void passWrappedValueToSink(Wrapper wrapper) { + sink(wrapper.field); + } + + private Wrapper createTaintedWrapper() { + return new Wrapper(source()); + } + + private void taintWrappedValue(Wrapper wrapper) { + wrapper.field = source(); + } + + //If it throws an exception, it is only an arithmetic exception. + private static int divide(int i, int j) { + return i / j; + } + + public static int source() { + return 1; + } + + private static int sanitize(int i) {return 42;} + private static void overwriteField(Wrapper wrapper) { + wrapper.field = 42; + } + + private static void overwriteStaticField() { + staticField = 42; + } + + private static void overwriteFirstArrayElement(int[] arr) { + arr[0] = 42; + } + + private static void sink(int i) { + System.out.println(i); + } + private static void sink(String s) { + System.out.println(s); + } +} + +abstract class A { + abstract int get(); +} + +class B extends A { + @Override + int get() { + return TaintAnalysisTestClass.source(); + } +} + +class C extends A { + @Override + int get() { + return 0; + } +} + +class Wrapper { + public int field; + + Wrapper() { + + } + + Wrapper(int field) { + this.field = field; + } + + public void overwrite() { + this.field = 42; + } + + public void overwriteArg(Wrapper wrapper) { + wrapper.field = 42; + } +} \ No newline at end of file diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/vta/VTATestClass.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/vta/VTATestClass.java new file mode 100644 index 0000000000..ae4c77876c --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/fixtures/vta/VTATestClass.java @@ -0,0 +1,126 @@ +package org.opalj.fpcf.fixtures.vta; + +import org.opalj.fpcf.properties.vta.ExpectedCallee; +import org.opalj.fpcf.properties.vta.ExpectedType; + +public class VTATestClass { + + @ExpectedType.List({ @ExpectedType(lineNumber = 10, value = "B", upperBound = false) }) + public void instantiationsAreConsidered() { + A a = new B(); + } + + @ExpectedType.List({ + @ExpectedType(lineNumber = 17, value = "B", upperBound = false), + @ExpectedType(lineNumber = 18, value = "C", upperBound = false) }) + public void factsAreRemembered() { + A x = new B(); + A y = new C(); + } + + @ExpectedType.List({ + @ExpectedType(lineNumber = 25, value = "B[]", upperBound = false), + @ExpectedType(lineNumber = 25, value = "C[]", upperBound = false) }) + public void arrayTypesAreConsidered_1() { + A[] a = new A[2]; + a[0] = new B(); + a[1] = new C(); + } + + @ExpectedType.List({@ExpectedType(lineNumber = 33, value = "B", upperBound = false)}) + @ExpectedCallee.List({@ExpectedCallee(lineNumber = 34, value = "B", upperBound = false)}) + public void callTargetsAreConsidered() { + A a = new B(); + a.doIt(); + } + + @ExpectedType.List({ + @ExpectedType(lineNumber = 42, value = "B", upperBound = false), + @ExpectedType(lineNumber = 43, value = "C", upperBound = false) }) + @ExpectedCallee.List({@ExpectedCallee(lineNumber = 45, value = "B", upperBound = false)}) + public void variableAssignmentsAreConsidered_1() { + A b = new B(); + A c = new C(); + if(Math.random() < .5) b = c; + b.doIt(); + } + + @ExpectedType.List({@ExpectedType(lineNumber = 51, value = "B[]", upperBound = false)}) + @ExpectedCallee.List({@ExpectedCallee(lineNumber = 52, value = "B", upperBound = false)}) + public void arrayLoadsAreConsidered() { + A[] a = new A[] {new B()}; + a[0].doIt(); + } + + @ExpectedType.List({@ExpectedType(lineNumber = 57, value = "B", upperBound = false)}) + public void typesOfParametersArePassed() { + A a = new B(); + typesOfParametersArePassed_callee(a); + } + + @ExpectedCallee.List({@ExpectedCallee(lineNumber = 63, value = "B", upperBound = false)}) + private void typesOfParametersArePassed_callee(A a) { + a.doIt(); + } + + @ExpectedType.List({@ExpectedType(lineNumber = 69, value = "B", upperBound = false)}) + @ExpectedCallee.List({@ExpectedCallee(lineNumber = 70, value = "B", upperBound = false)}) + public void returnFlowIsConsidered() { + A a = returnB(); + a.doIt(); + } + + private A returnB() { + return new B(); + } + + @ExpectedType.List({@ExpectedType(lineNumber = 80, value = "A", upperBound = true)}) + @ExpectedCallee.List({@ExpectedCallee(lineNumber = 81, value = "A", upperBound = true)}) + public void nativeCallsAreConsidered() { + A a = nativeMethod(); + a.doIt(); + } + + public native A nativeMethod(); + + @ExpectedType.List({@ExpectedType(lineNumber = 88, value = "String", upperBound = true)}) + public void staticFieldReadsAreConsidered() { + Object o = A.STATIC_FIELD; + System.out.println(o); + } + + @ExpectedType.List({@ExpectedType(lineNumber = 94, value = "String", upperBound = true)}) + public void fieldReadsAreConsidered() { + Object o = new B().field; + System.out.println(o); + } + + @ExpectedType.List({ @ExpectedType(lineNumber = 100, value = "B", upperBound = false) }) + protected void protectedMethodsAreConsidered() { + A a = new B(); + } + +} + +abstract class A { + + public static String STATIC_FIELD = "STATIC_FIELD"; + + public String field = "field"; + + public abstract void doIt(); +} + +class B extends A { + @Override + public void doIt() { + System.out.println("B"); + } +} + +class C extends A { + @Override + public void doIt() { + System.out.println("C"); + } +} \ No newline at end of file diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/taint/BackwardFlowPath.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/taint/BackwardFlowPath.java new file mode 100644 index 0000000000..2820c5a6f1 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/taint/BackwardFlowPath.java @@ -0,0 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.taint; + +import org.opalj.fpcf.properties.PropertyValidator; + +import java.lang.annotation.*; + +/** + * @author Mario Trageser + */ +@PropertyValidator(key = BackwardFlowPath.PROPERTY_VALIDATOR_KEY, validator = BackwardFlowPathMatcher.class) +@Target(ElementType.METHOD) +@Documented +@Retention(RetentionPolicy.CLASS) +public @interface BackwardFlowPath { + + String PROPERTY_VALIDATOR_KEY = "BackwardFlowPath"; + + String[] value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/taint/ForwardFlowPath.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/taint/ForwardFlowPath.java new file mode 100644 index 0000000000..9da140e6d4 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/taint/ForwardFlowPath.java @@ -0,0 +1,21 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.taint; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.taint.ForwardFlowPathMatcher; + +import java.lang.annotation.*; + +/** + * @author Mario Trageser + */ +@PropertyValidator(key = ForwardFlowPath.PROPERTY_VALIDATOR_KEY, validator = ForwardFlowPathMatcher.class) +@Target(ElementType.METHOD) +@Documented +@Retention(RetentionPolicy.CLASS) +public @interface ForwardFlowPath { + + String PROPERTY_VALIDATOR_KEY = "ForwardFlowPath"; + + String[] value(); +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/vta/ExpectedCallee.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/vta/ExpectedCallee.java new file mode 100644 index 0000000000..c860eac1fc --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/vta/ExpectedCallee.java @@ -0,0 +1,26 @@ +package org.opalj.fpcf.properties.vta; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.vta.ExpectedCalleeMatcher; + +import java.lang.annotation.*; + +public @interface ExpectedCallee { + + String PROPERTY_VALIDATOR_KEY = "ExpectedCallee"; + + int lineNumber(); + + String value(); + + boolean upperBound(); + + @PropertyValidator(key = ExpectedCallee.PROPERTY_VALIDATOR_KEY, validator = ExpectedCalleeMatcher.class) + @Target(ElementType.METHOD) + @Documented + @Retention(RetentionPolicy.CLASS) + @interface List { + + ExpectedCallee[] value(); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/vta/ExpectedType.java b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/vta/ExpectedType.java new file mode 100644 index 0000000000..e0dbd93144 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/java/org/opalj/fpcf/properties/vta/ExpectedType.java @@ -0,0 +1,26 @@ +package org.opalj.fpcf.properties.vta; + +import org.opalj.fpcf.properties.PropertyValidator; +import org.opalj.fpcf.properties.vta.ExpectedTypeMatcher; + +import java.lang.annotation.*; + +public @interface ExpectedType { + + String PROPERTY_VALIDATOR_KEY = "ExpectedType"; + + int lineNumber(); + + String value(); + + boolean upperBound(); + + @PropertyValidator(key = ExpectedType.PROPERTY_VALIDATOR_KEY, validator = ExpectedTypeMatcher.class) + @Target(ElementType.METHOD) + @Documented + @Retention(RetentionPolicy.CLASS) + @interface List { + + ExpectedType[] value(); + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.c b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.c new file mode 100644 index 0000000000..ce8eb75058 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.c @@ -0,0 +1,106 @@ +#include +#include +#include +#include "TaintTest.h" +JNIEXPORT int JNICALL +Java_TaintTest_sum(JNIEnv *env, jobject obj, jint a, jint b) { + return a + b; +} + +JNIEXPORT int JNICALL +Java_TaintTest_propagate_1source(JNIEnv *env, jobject obj) { + return source() + 23; +} + +JNIEXPORT int JNICALL +Java_TaintTest_propagate_1sanitize(JNIEnv *env, jobject obj, jint a) { + return sanitize(a); +} + +JNIEXPORT int JNICALL +Java_TaintTest_propagate_1sink(JNIEnv *env, jobject obj, jint a) { + sink(a); + return 23; +} + +JNIEXPORT int JNICALL +Java_TaintTest_sanitize_1only_1a_1into_1sink(JNIEnv *env, jobject obj, jint a, jint b) { + a = sanitize(a); + sink(a + b); + return b; +} + +JNIEXPORT void JNICALL +Java_TaintTest_propagate_1identity_1to_1sink(JNIEnv *env, jobject obj, jint a) { + int b = identity(a); + sink(b); +} + +JNIEXPORT void JNICALL +Java_TaintTest_propagate_1zero_1to_1sink(JNIEnv *env, jobject obj, jint a) { + int b = zero(a); + sink(b); +} + +JNIEXPORT void JNICALL +Java_TaintTest_native_1array_1tainted(JNIEnv *env, jobject obj) { + int a[2] = {0, 0}; + a[1] = source(); + sink(a[1]); +} + +JNIEXPORT void JNICALL +Java_TaintTest_native_1array_1untainted(JNIEnv *env, jobject obj) { + int a[2] = {0, 0}; + a[0] = source(); + sink(a[1]); +} + +JNIEXPORT void JNICALL +Java_TaintTest_propagate_1to_1java_1sink(JNIEnv *env, jobject obj, jint a) { + jclass klass = (*env)->GetObjectClass(env, obj); + // https://docs.oracle.com/en/java/javase/13/docs/specs/jni/types.html#type-signatures + // not documented, but "V" is "void" + jmethodID java_sink = (*env)->GetMethodID(env, klass, "indirect_sink", "(I)V"); + (*env)->CallVoidMethod(env, obj, java_sink, a); +} + +JNIEXPORT int JNICALL +Java_TaintTest_propagate_1from_1java_1source(JNIEnv *env, jobject obj) { + jclass klass = (*env)->GetObjectClass(env, obj); + jmethodID java_source = (*env)->GetMethodID(env, klass, "indirect_source", "()I"); + return (*env)->CallIntMethod(env, obj, java_source); +} + +JNIEXPORT int JNICALL +Java_TaintTest_propagate_1java_1sanitize(JNIEnv *env, jobject obj, jint a) { + jclass klass = (*env)->GetObjectClass(env, obj); + jmethodID java_sanitize = (*env)->GetMethodID(env, klass, "indirect_sanitize", "(I)I"); + return (*env)->CallIntMethod(env, obj, java_sanitize, a); +} + +int +identity(int a) { + return a; +} + +int +zero(int a) { + return 0; +} + +int +source() { + return 6*7; +} + +void +sink(int num) { + printf("native %d\n", num); +} + +int +sanitize(int num) { + return num - 19; +} + diff --git a/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.class b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.class new file mode 100644 index 0000000000..32654c849d Binary files /dev/null and b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.class differ diff --git a/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.h b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.h new file mode 100644 index 0000000000..338af49d15 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.h @@ -0,0 +1,48 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class TaintTest */ + +#ifndef _Included_TaintTest +#define _Included_TaintTest +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT int JNICALL Java_TaintTest_sum(JNIEnv *env, jobject obj, jint a, jint b); + +JNIEXPORT int JNICALL Java_TaintTest_propagate_1source(JNIEnv *env, jobject obj); + +JNIEXPORT int JNICALL Java_TaintTest_propagate_1sanitize(JNIEnv *env, jobject obj, jint a); + +JNIEXPORT int JNICALL Java_TaintTest_propagate_1sink(JNIEnv *env, jobject obj, jint a); + +JNIEXPORT int JNICALL Java_TaintTest_sanitize_1only_1a_1into_1sink(JNIEnv *env, jobject obj, jint a, jint b); + +JNIEXPORT void JNICALL Java_TaintTest_propagate_1identity_1to_1sink(JNIEnv *env, jobject obj, jint a); + +JNIEXPORT void JNICALL Java_TaintTest_propagate_1zero_1to_1sink(JNIEnv *env, jobject obj, jint a); + +JNIEXPORT void JNICALL Java_TaintTest_native_1array_1tainted(JNIEnv *env, jobject obj); + +JNIEXPORT void JNICALL Java_TaintTest_native_1array_1untainted(JNIEnv *env, jobject obj); + +JNIEXPORT void JNICALL Java_TaintTest_propagate_1to_1java_1sink(JNIEnv *env, jobject obj, jint a); + +JNIEXPORT int JNICALL Java_TaintTest_propagate_1from_1java_1source(JNIEnv *env, jobject obj); + +JNIEXPORT int JNICALL Java_TaintTest_propagate_1java_1sanitize(JNIEnv *env, jobject obj, jint a); + +int identity(int a); + +int zero(int a); + +int source(); + +void sink(int num); + +int sanitize(int num); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.java b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.java new file mode 100644 index 0000000000..add60865a7 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.java @@ -0,0 +1,156 @@ +public class TaintTest { + private native int sum (int a, int b); + private native int propagate_source (); + private native int propagate_sanitize (int a); + private native int propagate_sink (int a); + private native int sanitize_only_a_into_sink (int a, int b); + private native void propagate_identity_to_sink(int a); + private native void propagate_zero_to_sink(int a); + private native void native_array_tainted(); + private native void native_array_untainted(); + private native void propagate_to_java_sink(int a); + private native int propagate_from_java_source(); + private native int propagate_java_sanitize(int a); + static + { + System.loadLibrary ("tainttest"); + } + public static void main (String[] args) + { + TaintTest demo = new TaintTest(); + // force call graph analysis of indirect methods + // otherwise their callees are not analyzed, + // as they are only reachable through native code + // TODO: trigger cga from within other analysis + demo.indirect_sink(demo.indirect_sanitize(demo.indirect_source())); + + demo.test_java_flow(); + demo.test_java_sanitize_no_flow(); + demo.test_java_untainted_no_flow(); + demo.test_native_sum_flow(); + demo.test_native_to_java_to_native_flow(); + demo.test_native_to_java_to_native_sanitized_no_flow(); + demo.test_native_indirect_sanitized_no_flow(); + demo.test_native_indirect_flow(); + demo.test_native_identity_flow(); + demo.test_native_zero_no_flow(); + demo.test_native_array_tainted_flow(); + demo.test_native_array_untainted_no_flow(); + demo.test_native_call_java_sink_flow(); + demo.test_native_call_java_source_flow(); + demo.test_native_call_java_sanitize_no_flow(); + System.out.println("done"); + } + + public void test_java_flow() { + System.out.println("java"); + int tainted = this.source(); + this.sink(tainted); + } + + public void test_java_sanitize_no_flow() { + System.out.println("java sanitize"); + int tainted = this.source(); + this.sink(this.sanitize(tainted)); + } + + public void test_java_untainted_no_flow() { + System.out.println("java untainted"); + int untainted = 23; + this.sink(untainted); + } + + public void test_native_sum_flow() { + System.out.println("native sum"); + int tainted = this.source(); + int untainted = 23; + int native_tainted = this.sum(tainted, untainted); + this.sink(native_tainted); + } + + public void test_native_to_java_to_native_flow() { + System.out.println("native to java to native"); + int taint = this.propagate_source(); + this.propagate_sink(taint); + } + + public void test_native_to_java_to_native_sanitized_no_flow() { + System.out.println("native to java to native sanitized"); + this.propagate_sink(this.propagate_sanitize(this.propagate_source())); + } + + public void test_native_indirect_sanitized_no_flow() { + System.out.println("native indirect sanitized"); + int tainted = this.source(); + int untainted = 23; + this.sink(this.sanitize_only_a_into_sink(tainted, untainted)); + } + + public void test_native_indirect_flow() { + System.out.println("native indirect"); + int tainted = this.source(); + int untainted = 23; + this.sink(this.sanitize_only_a_into_sink(untainted, tainted)); + } + + public void test_native_identity_flow() { + System.out.println("native identity"); + this.propagate_identity_to_sink(source()); + } + + public void test_native_zero_no_flow() { + System.out.println("native zero"); + this.propagate_zero_to_sink(source()); + } + + public void test_native_array_tainted_flow() { + System.out.println("native array tainted"); + this.native_array_tainted(); + } + + public void test_native_array_untainted_no_flow() { + System.out.println("native array untainted"); + this.native_array_untainted(); + } + + public void test_native_call_java_sink_flow() { + System.out.println("native call java sink"); + this.propagate_to_java_sink(source()); + } + + public void test_native_call_java_source_flow() { + System.out.println("native call java source"); + this.sink(this.propagate_from_java_source()); + } + + public void test_native_call_java_sanitize_no_flow() { + System.out.println("native call java sanitize"); + this.sink(this.propagate_java_sanitize(this.source())); + } + + public int indirect_source() { + return source(); + } + + public void indirect_sink(int a) { + sink(a); + } + + public int indirect_sanitize(int a) { + return sanitize(a); + } + + private static int source() + { + return 42; + } + + private static void sink(int a) { + System.out.println("java " + a); + } + + private static int sanitize(int a) + { + return a - 19; + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.ll b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.ll new file mode 100644 index 0000000000..fcbab7ea71 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.ll @@ -0,0 +1,333 @@ +; ModuleID = 'TaintTest.c' +source_filename = "TaintTest.c" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-pc-linux-gnu" + +%struct.JNINativeInterface_ = type { i8*, i8*, i8*, i8*, i32 (%struct.JNINativeInterface_**)*, %struct._jobject* (%struct.JNINativeInterface_**, i8*, %struct._jobject*, i8*, i32)*, %struct._jobject* (%struct.JNINativeInterface_**, i8*)*, %struct._jmethodID* (%struct.JNINativeInterface_**, %struct._jobject*)*, %struct._jfieldID* (%struct.JNINativeInterface_**, %struct._jobject*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, i8)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, i8)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*, %struct._jobject* (%struct.JNINativeInterface_**)*, void (%struct.JNINativeInterface_**)*, void (%struct.JNINativeInterface_**)*, void (%struct.JNINativeInterface_**, i8*)*, i32 (%struct.JNINativeInterface_**, i32)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*)*, void (%struct.JNINativeInterface_**, %struct._jobject*)*, void (%struct.JNINativeInterface_**, %struct._jobject*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*)*, i32 (%struct.JNINativeInterface_**, i32)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*)*, %struct._jmethodID* (%struct.JNINativeInterface_**, %struct._jobject*, i8*, i8*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i64 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, i64 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i64 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, float (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, float (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, float (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, double (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, double (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, double (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, ...)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, ...)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, ...)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, ...)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, ...)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, ...)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i64 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, ...)*, i64 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i64 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, float (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, ...)*, float (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, float (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, double (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, ...)*, double (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, double (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, ...)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, %struct._jfieldID* (%struct.JNINativeInterface_**, %struct._jobject*, i8*, i8*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, i64 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, float (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, double (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, %struct._jobject*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, i8)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, i8)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, i16)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, i16)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, i32)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, i64)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, float)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, double)*, %struct._jmethodID* (%struct.JNINativeInterface_**, %struct._jobject*, i8*, i8*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, i64 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, i64 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, i64 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, float (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, float (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, float (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, double (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, double (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, double (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %struct.__va_list_tag*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, %union.jvalue*)*, %struct._jfieldID* (%struct.JNINativeInterface_**, %struct._jobject*, i8*, i8*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, i8 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, i16 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, i64 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, float (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, double (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, %struct._jobject*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, i8)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, i8)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, i16)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, i16)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, i32)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, i64)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, float)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jfieldID*, double)*, %struct._jobject* (%struct.JNINativeInterface_**, i16*, i32)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*)*, i16* (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i16*)*, %struct._jobject* (%struct.JNINativeInterface_**, i8*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*)*, i8* (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*)*, %struct._jobject* (%struct.JNINativeInterface_**, i32, %struct._jobject*, %struct._jobject*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*, i32)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, %struct._jobject*)*, %struct._jobject* (%struct.JNINativeInterface_**, i32)*, %struct._jobject* (%struct.JNINativeInterface_**, i32)*, %struct._jobject* (%struct.JNINativeInterface_**, i32)*, %struct._jobject* (%struct.JNINativeInterface_**, i32)*, %struct._jobject* (%struct.JNINativeInterface_**, i32)*, %struct._jobject* (%struct.JNINativeInterface_**, i32)*, %struct._jobject* (%struct.JNINativeInterface_**, i32)*, %struct._jobject* (%struct.JNINativeInterface_**, i32)*, i8* (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*, i8* (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*, i16* (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*, i16* (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*, i32* (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*, i64* (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*, float* (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*, double* (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i8*, i32)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i8*, i32)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i16*, i32)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i16*, i32)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32*, i32)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i64*, i32)*, void (%struct.JNINativeInterface_**, %struct._jobject*, float*, i32)*, void (%struct.JNINativeInterface_**, %struct._jobject*, double*, i32)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, i8*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, i8*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, i16*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, i16*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, i32*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, i64*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, float*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, double*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, i8*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, i8*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, i16*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, i16*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, i32*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, i64*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, float*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, double*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct.JNINativeMethod*, i32)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*)*, i32 (%struct.JNINativeInterface_**, %struct.JNIInvokeInterface_***)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, i16*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i32, i32, i8*)*, i8* (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i8*, i32)*, i16* (%struct.JNINativeInterface_**, %struct._jobject*, i8*)*, void (%struct.JNINativeInterface_**, %struct._jobject*, i16*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*)*, void (%struct.JNINativeInterface_**, %struct._jobject*)*, i8 (%struct.JNINativeInterface_**)*, %struct._jobject* (%struct.JNINativeInterface_**, i8*, i64)*, i8* (%struct.JNINativeInterface_**, %struct._jobject*)*, i64 (%struct.JNINativeInterface_**, %struct._jobject*)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*)* } +%struct._jmethodID = type opaque +%struct._jfieldID = type opaque +%struct.__va_list_tag = type { i32, i32, i8*, i8* } +%union.jvalue = type { i64 } +%struct.JNINativeMethod = type { i8*, i8*, i8* } +%struct.JNIInvokeInterface_ = type { i8*, i8*, i8*, i32 (%struct.JNIInvokeInterface_**)*, i32 (%struct.JNIInvokeInterface_**, i8**, i8*)*, i32 (%struct.JNIInvokeInterface_**)*, i32 (%struct.JNIInvokeInterface_**, i8**, i32)*, i32 (%struct.JNIInvokeInterface_**, i8**, i8*)* } +%struct._jobject = type opaque + +@.str = private unnamed_addr constant [14 x i8] c"indirect_sink\00", align 1 +@.str.1 = private unnamed_addr constant [5 x i8] c"(I)V\00", align 1 +@.str.2 = private unnamed_addr constant [16 x i8] c"indirect_source\00", align 1 +@.str.3 = private unnamed_addr constant [4 x i8] c"()I\00", align 1 +@.str.4 = private unnamed_addr constant [18 x i8] c"indirect_sanitize\00", align 1 +@.str.5 = private unnamed_addr constant [5 x i8] c"(I)I\00", align 1 +@.str.6 = private unnamed_addr constant [11 x i8] c"native %d\0A\00", align 1 + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @Java_TaintTest_sum(%struct.JNINativeInterface_** noundef %0, %struct._jobject* noundef %1, i32 noundef %2, i32 noundef %3) #0 { + %5 = alloca %struct.JNINativeInterface_**, align 8 + %6 = alloca %struct._jobject*, align 8 + %7 = alloca i32, align 4 + %8 = alloca i32, align 4 + store %struct.JNINativeInterface_** %0, %struct.JNINativeInterface_*** %5, align 8 + store %struct._jobject* %1, %struct._jobject** %6, align 8 + store i32 %2, i32* %7, align 4 + store i32 %3, i32* %8, align 4 + %9 = load i32, i32* %7, align 4 + %10 = load i32, i32* %8, align 4 + %11 = add nsw i32 %9, %10 + ret i32 %11 +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @Java_TaintTest_propagate_1source(%struct.JNINativeInterface_** noundef %0, %struct._jobject* noundef %1) #0 { + %3 = alloca %struct.JNINativeInterface_**, align 8 + %4 = alloca %struct._jobject*, align 8 + store %struct.JNINativeInterface_** %0, %struct.JNINativeInterface_*** %3, align 8 + store %struct._jobject* %1, %struct._jobject** %4, align 8 + %5 = call i32 @source() + %6 = add nsw i32 %5, 23 + ret i32 %6 +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @Java_TaintTest_propagate_1sanitize(%struct.JNINativeInterface_** noundef %0, %struct._jobject* noundef %1, i32 noundef %2) #0 { + %4 = alloca %struct.JNINativeInterface_**, align 8 + %5 = alloca %struct._jobject*, align 8 + %6 = alloca i32, align 4 + store %struct.JNINativeInterface_** %0, %struct.JNINativeInterface_*** %4, align 8 + store %struct._jobject* %1, %struct._jobject** %5, align 8 + store i32 %2, i32* %6, align 4 + %7 = load i32, i32* %6, align 4 + %8 = call i32 @sanitize(i32 noundef %7) + ret i32 %8 +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @sanitize(i32 noundef %0) #0 { + %2 = alloca i32, align 4 + store i32 %0, i32* %2, align 4 + %3 = load i32, i32* %2, align 4 + %4 = sub nsw i32 %3, 19 + ret i32 %4 +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @Java_TaintTest_propagate_1sink(%struct.JNINativeInterface_** noundef %0, %struct._jobject* noundef %1, i32 noundef %2) #0 { + %4 = alloca %struct.JNINativeInterface_**, align 8 + %5 = alloca %struct._jobject*, align 8 + %6 = alloca i32, align 4 + store %struct.JNINativeInterface_** %0, %struct.JNINativeInterface_*** %4, align 8 + store %struct._jobject* %1, %struct._jobject** %5, align 8 + store i32 %2, i32* %6, align 4 + %7 = load i32, i32* %6, align 4 + call void @sink(i32 noundef %7) + ret i32 23 +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local void @sink(i32 noundef %0) #0 { + %2 = alloca i32, align 4 + store i32 %0, i32* %2, align 4 + %3 = load i32, i32* %2, align 4 + %4 = call i32 (i8*, ...) @printf(i8* noundef getelementptr inbounds ([11 x i8], [11 x i8]* @.str.6, i64 0, i64 0), i32 noundef %3) + ret void +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @Java_TaintTest_sanitize_1only_1a_1into_1sink(%struct.JNINativeInterface_** noundef %0, %struct._jobject* noundef %1, i32 noundef %2, i32 noundef %3) #0 { + %5 = alloca %struct.JNINativeInterface_**, align 8 + %6 = alloca %struct._jobject*, align 8 + %7 = alloca i32, align 4 + %8 = alloca i32, align 4 + store %struct.JNINativeInterface_** %0, %struct.JNINativeInterface_*** %5, align 8 + store %struct._jobject* %1, %struct._jobject** %6, align 8 + store i32 %2, i32* %7, align 4 + store i32 %3, i32* %8, align 4 + %9 = load i32, i32* %7, align 4 + %10 = call i32 @sanitize(i32 noundef %9) + store i32 %10, i32* %7, align 4 + %11 = load i32, i32* %7, align 4 + %12 = load i32, i32* %8, align 4 + %13 = add nsw i32 %11, %12 + call void @sink(i32 noundef %13) + %14 = load i32, i32* %8, align 4 + ret i32 %14 +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local void @Java_TaintTest_propagate_1identity_1to_1sink(%struct.JNINativeInterface_** noundef %0, %struct._jobject* noundef %1, i32 noundef %2) #0 { + %4 = alloca %struct.JNINativeInterface_**, align 8 + %5 = alloca %struct._jobject*, align 8 + %6 = alloca i32, align 4 + %7 = alloca i32, align 4 + store %struct.JNINativeInterface_** %0, %struct.JNINativeInterface_*** %4, align 8 + store %struct._jobject* %1, %struct._jobject** %5, align 8 + store i32 %2, i32* %6, align 4 + %8 = load i32, i32* %6, align 4 + %9 = call i32 @identity(i32 noundef %8) + store i32 %9, i32* %7, align 4 + %10 = load i32, i32* %7, align 4 + call void @sink(i32 noundef %10) + ret void +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @identity(i32 noundef %0) #0 { + %2 = alloca i32, align 4 + store i32 %0, i32* %2, align 4 + %3 = load i32, i32* %2, align 4 + ret i32 %3 +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local void @Java_TaintTest_propagate_1zero_1to_1sink(%struct.JNINativeInterface_** noundef %0, %struct._jobject* noundef %1, i32 noundef %2) #0 { + %4 = alloca %struct.JNINativeInterface_**, align 8 + %5 = alloca %struct._jobject*, align 8 + %6 = alloca i32, align 4 + %7 = alloca i32, align 4 + store %struct.JNINativeInterface_** %0, %struct.JNINativeInterface_*** %4, align 8 + store %struct._jobject* %1, %struct._jobject** %5, align 8 + store i32 %2, i32* %6, align 4 + %8 = load i32, i32* %6, align 4 + %9 = call i32 @zero(i32 noundef %8) + store i32 %9, i32* %7, align 4 + %10 = load i32, i32* %7, align 4 + call void @sink(i32 noundef %10) + ret void +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @zero(i32 noundef %0) #0 { + %2 = alloca i32, align 4 + store i32 %0, i32* %2, align 4 + ret i32 0 +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local void @Java_TaintTest_native_1array_1tainted(%struct.JNINativeInterface_** noundef %0, %struct._jobject* noundef %1) #0 { + %3 = alloca %struct.JNINativeInterface_**, align 8 + %4 = alloca %struct._jobject*, align 8 + %5 = alloca [2 x i32], align 4 + store %struct.JNINativeInterface_** %0, %struct.JNINativeInterface_*** %3, align 8 + store %struct._jobject* %1, %struct._jobject** %4, align 8 + %6 = bitcast [2 x i32]* %5 to i8* + call void @llvm.memset.p0i8.i64(i8* align 4 %6, i8 0, i64 8, i1 false) + %7 = call i32 @source() + %8 = getelementptr inbounds [2 x i32], [2 x i32]* %5, i64 0, i64 1 + store i32 %7, i32* %8, align 4 + %9 = getelementptr inbounds [2 x i32], [2 x i32]* %5, i64 0, i64 1 + %10 = load i32, i32* %9, align 4 + call void @sink(i32 noundef %10) + ret void +} + +; Function Attrs: argmemonly nofree nounwind willreturn writeonly +declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1 immarg) #1 + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local void @Java_TaintTest_native_1array_1untainted(%struct.JNINativeInterface_** noundef %0, %struct._jobject* noundef %1) #0 { + %3 = alloca %struct.JNINativeInterface_**, align 8 + %4 = alloca %struct._jobject*, align 8 + %5 = alloca [2 x i32], align 4 + store %struct.JNINativeInterface_** %0, %struct.JNINativeInterface_*** %3, align 8 + store %struct._jobject* %1, %struct._jobject** %4, align 8 + %6 = bitcast [2 x i32]* %5 to i8* + call void @llvm.memset.p0i8.i64(i8* align 4 %6, i8 0, i64 8, i1 false) + %7 = call i32 @source() + %8 = getelementptr inbounds [2 x i32], [2 x i32]* %5, i64 0, i64 0 + store i32 %7, i32* %8, align 4 + %9 = getelementptr inbounds [2 x i32], [2 x i32]* %5, i64 0, i64 1 + %10 = load i32, i32* %9, align 4 + call void @sink(i32 noundef %10) + ret void +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local void @Java_TaintTest_propagate_1to_1java_1sink(%struct.JNINativeInterface_** noundef %0, %struct._jobject* noundef %1, i32 noundef %2) #0 { + %4 = alloca %struct.JNINativeInterface_**, align 8 + %5 = alloca %struct._jobject*, align 8 + %6 = alloca i32, align 4 + %7 = alloca %struct._jobject*, align 8 + %8 = alloca %struct._jmethodID*, align 8 + store %struct.JNINativeInterface_** %0, %struct.JNINativeInterface_*** %4, align 8 + store %struct._jobject* %1, %struct._jobject** %5, align 8 + store i32 %2, i32* %6, align 4 + %9 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %4, align 8 + %10 = load %struct.JNINativeInterface_*, %struct.JNINativeInterface_** %9, align 8 + %11 = getelementptr inbounds %struct.JNINativeInterface_, %struct.JNINativeInterface_* %10, i32 0, i32 31 + %12 = load %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*)** %11, align 8 + %13 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %4, align 8 + %14 = load %struct._jobject*, %struct._jobject** %5, align 8 + %15 = call %struct._jobject* %12(%struct.JNINativeInterface_** noundef %13, %struct._jobject* noundef %14) + store %struct._jobject* %15, %struct._jobject** %7, align 8 + %16 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %4, align 8 + %17 = load %struct.JNINativeInterface_*, %struct.JNINativeInterface_** %16, align 8 + %18 = getelementptr inbounds %struct.JNINativeInterface_, %struct.JNINativeInterface_* %17, i32 0, i32 33 + %19 = load %struct._jmethodID* (%struct.JNINativeInterface_**, %struct._jobject*, i8*, i8*)*, %struct._jmethodID* (%struct.JNINativeInterface_**, %struct._jobject*, i8*, i8*)** %18, align 8 + %20 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %4, align 8 + %21 = load %struct._jobject*, %struct._jobject** %7, align 8 + %22 = call %struct._jmethodID* %19(%struct.JNINativeInterface_** noundef %20, %struct._jobject* noundef %21, i8* noundef getelementptr inbounds ([14 x i8], [14 x i8]* @.str, i64 0, i64 0), i8* noundef getelementptr inbounds ([5 x i8], [5 x i8]* @.str.1, i64 0, i64 0)) + store %struct._jmethodID* %22, %struct._jmethodID** %8, align 8 + %23 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %4, align 8 + %24 = load %struct.JNINativeInterface_*, %struct.JNINativeInterface_** %23, align 8 + %25 = getelementptr inbounds %struct.JNINativeInterface_, %struct.JNINativeInterface_* %24, i32 0, i32 61 + %26 = load void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)** %25, align 8 + %27 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %4, align 8 + %28 = load %struct._jobject*, %struct._jobject** %5, align 8 + %29 = load %struct._jmethodID*, %struct._jmethodID** %8, align 8 + %30 = load i32, i32* %6, align 4 + call void (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...) %26(%struct.JNINativeInterface_** noundef %27, %struct._jobject* noundef %28, %struct._jmethodID* noundef %29, i32 noundef %30) + ret void +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @Java_TaintTest_propagate_1from_1java_1source(%struct.JNINativeInterface_** noundef %0, %struct._jobject* noundef %1) #0 { + %3 = alloca %struct.JNINativeInterface_**, align 8 + %4 = alloca %struct._jobject*, align 8 + %5 = alloca %struct._jobject*, align 8 + %6 = alloca %struct._jmethodID*, align 8 + store %struct.JNINativeInterface_** %0, %struct.JNINativeInterface_*** %3, align 8 + store %struct._jobject* %1, %struct._jobject** %4, align 8 + %7 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %3, align 8 + %8 = load %struct.JNINativeInterface_*, %struct.JNINativeInterface_** %7, align 8 + %9 = getelementptr inbounds %struct.JNINativeInterface_, %struct.JNINativeInterface_* %8, i32 0, i32 31 + %10 = load %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*)** %9, align 8 + %11 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %3, align 8 + %12 = load %struct._jobject*, %struct._jobject** %4, align 8 + %13 = call %struct._jobject* %10(%struct.JNINativeInterface_** noundef %11, %struct._jobject* noundef %12) + store %struct._jobject* %13, %struct._jobject** %5, align 8 + %14 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %3, align 8 + %15 = load %struct.JNINativeInterface_*, %struct.JNINativeInterface_** %14, align 8 + %16 = getelementptr inbounds %struct.JNINativeInterface_, %struct.JNINativeInterface_* %15, i32 0, i32 33 + %17 = load %struct._jmethodID* (%struct.JNINativeInterface_**, %struct._jobject*, i8*, i8*)*, %struct._jmethodID* (%struct.JNINativeInterface_**, %struct._jobject*, i8*, i8*)** %16, align 8 + %18 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %3, align 8 + %19 = load %struct._jobject*, %struct._jobject** %5, align 8 + %20 = call %struct._jmethodID* %17(%struct.JNINativeInterface_** noundef %18, %struct._jobject* noundef %19, i8* noundef getelementptr inbounds ([16 x i8], [16 x i8]* @.str.2, i64 0, i64 0), i8* noundef getelementptr inbounds ([4 x i8], [4 x i8]* @.str.3, i64 0, i64 0)) + store %struct._jmethodID* %20, %struct._jmethodID** %6, align 8 + %21 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %3, align 8 + %22 = load %struct.JNINativeInterface_*, %struct.JNINativeInterface_** %21, align 8 + %23 = getelementptr inbounds %struct.JNINativeInterface_, %struct.JNINativeInterface_* %22, i32 0, i32 49 + %24 = load i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)** %23, align 8 + %25 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %3, align 8 + %26 = load %struct._jobject*, %struct._jobject** %4, align 8 + %27 = load %struct._jmethodID*, %struct._jmethodID** %6, align 8 + %28 = call i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...) %24(%struct.JNINativeInterface_** noundef %25, %struct._jobject* noundef %26, %struct._jmethodID* noundef %27) + ret i32 %28 +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @Java_TaintTest_propagate_1java_1sanitize(%struct.JNINativeInterface_** noundef %0, %struct._jobject* noundef %1, i32 noundef %2) #0 { + %4 = alloca %struct.JNINativeInterface_**, align 8 + %5 = alloca %struct._jobject*, align 8 + %6 = alloca i32, align 4 + %7 = alloca %struct._jobject*, align 8 + %8 = alloca %struct._jmethodID*, align 8 + store %struct.JNINativeInterface_** %0, %struct.JNINativeInterface_*** %4, align 8 + store %struct._jobject* %1, %struct._jobject** %5, align 8 + store i32 %2, i32* %6, align 4 + %9 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %4, align 8 + %10 = load %struct.JNINativeInterface_*, %struct.JNINativeInterface_** %9, align 8 + %11 = getelementptr inbounds %struct.JNINativeInterface_, %struct.JNINativeInterface_* %10, i32 0, i32 31 + %12 = load %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*)*, %struct._jobject* (%struct.JNINativeInterface_**, %struct._jobject*)** %11, align 8 + %13 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %4, align 8 + %14 = load %struct._jobject*, %struct._jobject** %5, align 8 + %15 = call %struct._jobject* %12(%struct.JNINativeInterface_** noundef %13, %struct._jobject* noundef %14) + store %struct._jobject* %15, %struct._jobject** %7, align 8 + %16 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %4, align 8 + %17 = load %struct.JNINativeInterface_*, %struct.JNINativeInterface_** %16, align 8 + %18 = getelementptr inbounds %struct.JNINativeInterface_, %struct.JNINativeInterface_* %17, i32 0, i32 33 + %19 = load %struct._jmethodID* (%struct.JNINativeInterface_**, %struct._jobject*, i8*, i8*)*, %struct._jmethodID* (%struct.JNINativeInterface_**, %struct._jobject*, i8*, i8*)** %18, align 8 + %20 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %4, align 8 + %21 = load %struct._jobject*, %struct._jobject** %7, align 8 + %22 = call %struct._jmethodID* %19(%struct.JNINativeInterface_** noundef %20, %struct._jobject* noundef %21, i8* noundef getelementptr inbounds ([18 x i8], [18 x i8]* @.str.4, i64 0, i64 0), i8* noundef getelementptr inbounds ([5 x i8], [5 x i8]* @.str.5, i64 0, i64 0)) + store %struct._jmethodID* %22, %struct._jmethodID** %8, align 8 + %23 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %4, align 8 + %24 = load %struct.JNINativeInterface_*, %struct.JNINativeInterface_** %23, align 8 + %25 = getelementptr inbounds %struct.JNINativeInterface_, %struct.JNINativeInterface_* %24, i32 0, i32 49 + %26 = load i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)*, i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...)** %25, align 8 + %27 = load %struct.JNINativeInterface_**, %struct.JNINativeInterface_*** %4, align 8 + %28 = load %struct._jobject*, %struct._jobject** %5, align 8 + %29 = load %struct._jmethodID*, %struct._jmethodID** %8, align 8 + %30 = load i32, i32* %6, align 4 + %31 = call i32 (%struct.JNINativeInterface_**, %struct._jobject*, %struct._jmethodID*, ...) %26(%struct.JNINativeInterface_** noundef %27, %struct._jobject* noundef %28, %struct._jmethodID* noundef %29, i32 noundef %30) + ret i32 %31 +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @source() #0 { + ret i32 42 +} + +declare i32 @printf(i8* noundef, ...) #2 + +attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +attributes #1 = { argmemonly nofree nounwind willreturn writeonly } +attributes #2 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } + +!llvm.module.flags = !{!0, !1, !2, !3, !4} +!llvm.ident = !{!5} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{i32 7, !"PIC Level", i32 2} +!2 = !{i32 7, !"PIE Level", i32 2} +!3 = !{i32 7, !"uwtable", i32 1} +!4 = !{i32 7, !"frame-pointer", i32 2} +!5 = !{!"Ubuntu clang version 14.0.0-1ubuntu1"} diff --git a/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/build_run.sh b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/build_run.sh new file mode 100755 index 0000000000..3f98574bdb --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/build_run.sh @@ -0,0 +1,4 @@ +clang -shared -I/usr/lib/jvm/java-11-openjdk-amd64/include/ -I/usr/lib/jvm/java-11-openjdk-amd64/include/linux/ -o libtainttest.so TaintTest.c +clang -S -I/usr/lib/jvm/java-11-openjdk-amd64/include/ -I/usr/lib/jvm/java-11-openjdk-amd64/include/linux/ TaintTest.c -emit-llvm +javac TaintTest.java +java -Djava.library.path=. TaintTest \ No newline at end of file diff --git a/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/libtainttest.so b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/libtainttest.so new file mode 100755 index 0000000000..39c3cf47a4 Binary files /dev/null and b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/libtainttest.so differ diff --git a/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/run.sh b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/run.sh new file mode 100755 index 0000000000..7d5eb2bdee --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/run.sh @@ -0,0 +1 @@ +java -Djava.library.path=. TaintTest diff --git a/DEVELOPING_OPAL/validate/src/test/resources/llvm/purity.c b/DEVELOPING_OPAL/validate/src/test/resources/llvm/purity.c new file mode 100644 index 0000000000..0372fb1352 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/resources/llvm/purity.c @@ -0,0 +1,20 @@ +#include + +int foo = 23; + +int pure_function(int a, int b) { + return a + b; +} + +void impure_function() { + foo = 42; +} + +int main() { + printf("hello world, foo is %i\n", foo); + int result = pure_function(1,2); + printf("result: %i\n", result); + impure_function(); + printf("foo: %i\n", foo); + return 0; +} diff --git a/DEVELOPING_OPAL/validate/src/test/resources/llvm/purity.ll b/DEVELOPING_OPAL/validate/src/test/resources/llvm/purity.ll new file mode 100644 index 0000000000..1078c5ec4e --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/resources/llvm/purity.ll @@ -0,0 +1,57 @@ +; ModuleID = 'purity.c' +source_filename = "purity.c" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-pc-linux-gnu" + +@foo = dso_local global i32 23, align 4 +@.str = private unnamed_addr constant [24 x i8] c"hello world, foo is %i\0A\00", align 1 +@.str.1 = private unnamed_addr constant [12 x i8] c"result: %i\0A\00", align 1 +@.str.2 = private unnamed_addr constant [8 x i8] c"foo: %i\00", align 1 + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @pure_function(i32 %0, i32 %1) #0 { + %3 = alloca i32, align 4 + %4 = alloca i32, align 4 + store i32 %0, i32* %3, align 4 + store i32 %1, i32* %4, align 4 + %5 = load i32, i32* %3, align 4 + %6 = load i32, i32* %4, align 4 + %7 = add nsw i32 %5, %6 + ret i32 %7 +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local void @impure_function() #0 { + store i32 42, i32* @foo, align 4 + ret void +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @main() #0 { + %1 = alloca i32, align 4 + %2 = alloca i32, align 4 + store i32 0, i32* %1, align 4 + %3 = load i32, i32* @foo, align 4 + %4 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([24 x i8], [24 x i8]* @.str, i64 0, i64 0), i32 %3) + %5 = call i32 @pure_function(i32 1, i32 2) + store i32 %5, i32* %2, align 4 + %6 = load i32, i32* %2, align 4 + %7 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([12 x i8], [12 x i8]* @.str.1, i64 0, i64 0), i32 %6) + call void @impure_function() + %8 = load i32, i32* @foo, align 4 + %9 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str.2, i64 0, i64 0), i32 %8) + ret i32 0 +} + +declare dso_local i32 @printf(i8*, ...) #1 + +attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } +attributes #1 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } + +!llvm.module.flags = !{!0, !1, !2} +!llvm.ident = !{!3} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{i32 7, !"uwtable", i32 1} +!2 = !{i32 7, !"frame-pointer", i32 2} +!3 = !{!"Ubuntu clang version 13.0.0-2"} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/PropertiesTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/PropertiesTest.scala index 4cb596345a..30c39363a0 100644 --- a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/PropertiesTest.scala +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/PropertiesTest.scala @@ -4,19 +4,16 @@ package fpcf import java.io.File import java.net.URL - import com.typesafe.config.Config import com.typesafe.config.ConfigValueFactory - import org.opalj.bi.reader.ClassFileReader +import org.opalj.bytecode.RTJar import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers - import org.opalj.log.LogContext import org.opalj.util.ScalaMajorVersion import org.opalj.fpcf.properties.PropertyMatcher import org.opalj.fpcf.seq.PKESequentialPropertyStore -import org.opalj.bytecode.RTJar import org.opalj.br.DefinedMethod import org.opalj.br.analyses.VirtualFormalParameter import org.opalj.br.analyses.VirtualFormalParametersKey diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ifds/ForwardTaintAnalysisFixtureTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ifds/ForwardTaintAnalysisFixtureTest.scala new file mode 100644 index 0000000000..51a3163dcd --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ifds/ForwardTaintAnalysisFixtureTest.scala @@ -0,0 +1,45 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.ifds + +import org.opalj.ai.domain.l2 +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey +import org.opalj.br.analyses.Project +import org.opalj.fpcf.PropertiesTest +import org.opalj.fpcf.properties.taint.ForwardFlowPath +import org.opalj.tac.cg.RTACallGraphKey +import org.opalj.tac.fpcf.analyses.ifds.taint.ForwardTaintAnalysisFixtureScheduler +import org.opalj.tac.fpcf.properties.TaintNullFact + +import java.net.URL + +/** + * @author Mario Trageser + */ +class ForwardTaintAnalysisFixtureTest extends PropertiesTest { + override def fixtureProjectPackage: List[String] = List( + "org/opalj/fpcf/fixtures/taint" + ) + + override def init(p: Project[URL]): Unit = { + p.updateProjectInformationKeyInitializationData( + AIDomainFactoryKey + )( + (_: Option[Set[Class[_ <: AnyRef]]]) => + Set[Class[_ <: AnyRef]]( + classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[URL]] + ) + ) + p.get(RTACallGraphKey) + } + + describe("Test the ForwardFlowPath annotations") { + val testContext = executeAnalyses(ForwardTaintAnalysisFixtureScheduler) + val project = testContext.project + val eas = methodsWithAnnotations(project).map { + case (method, entityString, annotations) => + ((method, TaintNullFact), entityString, annotations) + } + testContext.propertyStore.shutdown() + validateProperties(testContext, eas, Set(ForwardFlowPath.PROPERTY_VALIDATOR_KEY)) + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ifds/old/BackwardTaintAnalysisFixtureTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ifds/old/BackwardTaintAnalysisFixtureTest.scala new file mode 100644 index 0000000000..7f16a90125 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ifds/old/BackwardTaintAnalysisFixtureTest.scala @@ -0,0 +1,43 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.ifds.old + +import org.opalj.ai.domain.l2 +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey +import org.opalj.br.analyses.{DeclaredMethodsKey, Project} +import org.opalj.fpcf.PropertiesTest +import org.opalj.fpcf.properties.taint.BackwardFlowPath +import org.opalj.tac.cg.RTACallGraphKey +import org.opalj.tac.fpcf.properties.TaintNullFact +import org.opalj.tac.fpcf.analyses.ifds.taint.old.BackwardTaintAnalysisFixtureScheduler + +import java.net.URL + +/** + * @author Mario Trageser + */ +class BackwardTaintAnalysisFixtureTest extends PropertiesTest { + + override def init(p: Project[URL]): Unit = { + p.updateProjectInformationKeyInitializationData( + AIDomainFactoryKey + )( + (_: Option[Set[Class[_ <: AnyRef]]]) => + Set[Class[_ <: AnyRef]]( + classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[URL]] + ) + ) + p.get(RTACallGraphKey) + } + + describe("Test the BackwardFlowPath annotations") { + val testContext = executeAnalyses(BackwardTaintAnalysisFixtureScheduler) + val project = testContext.project + val declaredMethods = project.get(DeclaredMethodsKey) + val eas = methodsWithAnnotations(project).map { + case (methods, entityString, annotations) => + ((declaredMethods(methods), TaintNullFact), entityString, annotations) + } + testContext.propertyStore.shutdown() + validateProperties(testContext, eas, Set(BackwardFlowPath.PROPERTY_VALIDATOR_KEY)) + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ifds/old/ForwardTaintAnalysisFixtureTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ifds/old/ForwardTaintAnalysisFixtureTest.scala new file mode 100644 index 0000000000..df39091498 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ifds/old/ForwardTaintAnalysisFixtureTest.scala @@ -0,0 +1,43 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.ifds.old + +import org.opalj.ai.domain.l2 +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey +import org.opalj.br.analyses.{DeclaredMethodsKey, Project} +import org.opalj.fpcf.PropertiesTest +import org.opalj.fpcf.properties.taint.ForwardFlowPath +import org.opalj.tac.cg.RTACallGraphKey +import org.opalj.tac.fpcf.analyses.ifds.taint.old.ForwardTaintAnalysisFixtureScheduler +import org.opalj.tac.fpcf.properties.TaintNullFact + +import java.net.URL + +/** + * @author Mario Trageser + */ +class ForwardTaintAnalysisFixtureTest extends PropertiesTest { + + override def init(p: Project[URL]): Unit = { + p.updateProjectInformationKeyInitializationData( + AIDomainFactoryKey + )( + (_: Option[Set[Class[_ <: AnyRef]]]) => + Set[Class[_ <: AnyRef]]( + classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[URL]] + ) + ) + p.get(RTACallGraphKey) + } + + describe("Test the ForwardFlowPath annotations") { + val testContext = executeAnalyses(ForwardTaintAnalysisFixtureScheduler) + val project = testContext.project + val declaredMethods = project.get(DeclaredMethodsKey) + val eas = methodsWithAnnotations(project).map { + case (methods, entityString, annotations) => + ((declaredMethods(methods), TaintNullFact), entityString, annotations) + } + testContext.propertyStore.shutdown() + validateProperties(testContext, eas, Set(ForwardFlowPath.PROPERTY_VALIDATOR_KEY)) + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ifds/old/VTATest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ifds/old/VTATest.scala new file mode 100644 index 0000000000..1b0e99d240 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/ifds/old/VTATest.scala @@ -0,0 +1,41 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.ifds.old + +import org.opalj.ai.domain.l2 +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey +import org.opalj.br.analyses.{DeclaredMethodsKey, Project} +import org.opalj.fpcf.PropertiesTest +import org.opalj.fpcf.properties.vta.{ExpectedCallee, ExpectedType} +import org.opalj.tac.cg.RTACallGraphKey +import org.opalj.tac.fpcf.analyses.ifds.old.{IFDSBasedVariableTypeAnalysisScheduler, VTANullFact} + +import java.net.URL + +class VTATest extends PropertiesTest { + + override def init(p: Project[URL]): Unit = { + p.updateProjectInformationKeyInitializationData( + AIDomainFactoryKey + )( + (_: Option[Set[Class[_ <: AnyRef]]]) => + Set[Class[_ <: AnyRef]]( + classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[URL]] + ) + ) + p.get(RTACallGraphKey) + } + + describe("Test the ExpectedType annotations") { + val testContext = executeAnalyses(IFDSBasedVariableTypeAnalysisScheduler) + val project = testContext.project + val declaredMethods = project.get(DeclaredMethodsKey) + val eas = methodsWithAnnotations(project).map { + case (methods, entityString, annotations) => + ((declaredMethods(methods), VTANullFact), entityString, annotations) + } + testContext.propertyStore.shutdown() + validateProperties(testContext, eas, Set(ExpectedType.PROPERTY_VALIDATOR_KEY)) + validateProperties(testContext, eas, Set(ExpectedCallee.PROPERTY_VALIDATOR_KEY)) + } + +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/taint/BackwardFlowPathMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/taint/BackwardFlowPathMatcher.scala new file mode 100644 index 0000000000..7a2266b0e6 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/taint/BackwardFlowPathMatcher.scala @@ -0,0 +1,50 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.taint + +import org.opalj.br._ +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.fpcf.properties.AbstractPropertyMatcher +import org.opalj.fpcf.{EPS, Entity, FinalEP, Property} +import org.opalj.tac.fpcf.analyses.ifds.taint.old.BackwardTaintAnalysisFixtureScheduler +import org.opalj.tac.fpcf.properties._ +import org.opalj.tac.fpcf.properties.OldTaint + +/** + * @author Mario Trageser + */ +class BackwardFlowPathMatcher extends AbstractPropertyMatcher { + + def validateProperty( + p: SomeProject, + as: Set[ObjectType], + entity: Entity, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val method = entity.asInstanceOf[(DefinedMethod, TaintFact)]._1.definedMethod + val expectedFlow = a.elementValuePairs.map((evp: ElementValuePair) => + evp.value.asArrayValue.values.map((value: ElementValue) => + value.asStringValue.value)).head.toIndexedSeq + val propertyStore = p.get(PropertyStoreKey) + val propertyKey = BackwardTaintAnalysisFixtureScheduler.property.key + val allReachableFlowFacts = + propertyStore.entities(propertyKey).collect { + case EPS((m: DefinedMethod, inputFact)) if m.definedMethod == method => + (m, inputFact) + }.flatMap(propertyStore(_, propertyKey) match { + case FinalEP(_, OldTaint(result, _)) => + result.values.fold(Set.empty)((acc, facts) => acc ++ facts).collect { + case FlowFact(methods) => methods.map(_.name) + } + case _ => Seq.empty + }).toIndexedSeq + if (expectedFlow.isEmpty) { + if (allReachableFlowFacts.nonEmpty) return Some(s"There should be no flow for $entity") + None + } else { + if (allReachableFlowFacts.contains(expectedFlow)) None + else Some(expectedFlow.mkString(", ")) + } + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/taint/ForwardFlowPathMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/taint/ForwardFlowPathMatcher.scala new file mode 100644 index 0000000000..8e59387efd --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/taint/ForwardFlowPathMatcher.scala @@ -0,0 +1,42 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.taint + +import org.opalj.br.analyses.SomeProject +import org.opalj.br.{AnnotationLike, ElementValue, ElementValuePair, ObjectType} +import org.opalj.fpcf.properties.AbstractPropertyMatcher +import org.opalj.fpcf.{Entity, Property} +import org.opalj.ifds.IFDSProperty +import org.opalj.tac.fpcf.properties._ + +/** + * @author Mario Trageser + */ +class ForwardFlowPathMatcher extends AbstractPropertyMatcher { + + def validateProperty( + p: SomeProject, + as: Set[ObjectType], + entity: Entity, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val expectedFlow = a.elementValuePairs.map((evp: ElementValuePair) => + evp.value.asArrayValue.values.map((value: ElementValue) => + value.asStringValue.value)).head.toIndexedSeq + val flows = properties.filter(_.isInstanceOf[IFDSProperty[_, _]]).head + .asInstanceOf[IFDSProperty[_, _]] + .flows + .values + .fold(Set.empty)((acc, facts) => acc ++ facts) + .collect { + case FlowFact(methods) => methods.map(_.name).toIndexedSeq + } + if (expectedFlow.isEmpty) { + if (flows.nonEmpty) return Some(s"There should be no flow for $entity") + None + } else { + if (flows.contains(expectedFlow)) None + else Some(expectedFlow.mkString(", ")) + } + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/vta/ExpectedCalleeMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/vta/ExpectedCalleeMatcher.scala new file mode 100644 index 0000000000..a421de1b85 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/vta/ExpectedCalleeMatcher.scala @@ -0,0 +1,52 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.vta + +import org.opalj.fpcf.Entity +import org.opalj.fpcf.EPS +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.Property +import org.opalj.value.ValueInformation +import org.opalj.br.analyses.SomeProject +import org.opalj.br.AnnotationLike +import org.opalj.br.DefinedMethod +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.br.Method +import org.opalj.tac.DUVar +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode +import org.opalj.tac.fpcf.analyses.ifds.old.{CalleeType, IFDSBasedVariableTypeAnalysisScheduler, VTAResult} + +class ExpectedCalleeMatcher extends VTAMatcher { + + def validateSingleAnnotation(project: SomeProject, entity: Entity, + taCode: TACode[TACMethodParameter, DUVar[ValueInformation]], + method: Method, annotation: AnnotationLike, + properties: Iterable[Property]): Option[String] = { + val elementValuePairs = annotation.elementValuePairs + val expected = ( + elementValuePairs.head.value.asIntValue.value, + elementValuePairs(1).value.asStringValue.value, + elementValuePairs(2).value.asBooleanValue.value + ) + val propertyStore = project.get(PropertyStoreKey) + val propertyKey = IFDSBasedVariableTypeAnalysisScheduler.property.key + // Get ALL the exit facts for the method for ALL input facts + val allReachableExitFacts = + propertyStore.entities(propertyKey).collect { + case EPS((m: DefinedMethod, inputFact)) if m.definedMethod == method => + (m, inputFact) + }.flatMap(propertyStore(_, propertyKey) match { + case FinalEP(_, VTAResult(result, _)) => + result.values.fold(Set.empty)((acc, facts) => acc ++ facts).collect { + case CalleeType(index, t, upperBound) => + Seq(( + taCode.lineNumber(method.body.get, index).get, + referenceTypeToString(t), upperBound + )) + } + case _ => Seq.empty + }).flatten + if (allReachableExitFacts.contains(expected)) None + else Some(expected.toString) + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/vta/ExpectedTypeMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/vta/ExpectedTypeMatcher.scala new file mode 100644 index 0000000000..8cdee1f8c9 --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/vta/ExpectedTypeMatcher.scala @@ -0,0 +1,40 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.vta + +import org.opalj.fpcf.Entity +import org.opalj.fpcf.Property +import org.opalj.value.ValueInformation +import org.opalj.br.analyses.SomeProject +import org.opalj.br.AnnotationLike +import org.opalj.br.Method +import org.opalj.tac.DUVar +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode +import org.opalj.tac.fpcf.analyses.ifds.old.{VTAResult, VariableType} + +class ExpectedTypeMatcher extends VTAMatcher { + + def validateSingleAnnotation(project: SomeProject, entity: Entity, + taCode: TACode[TACMethodParameter, DUVar[ValueInformation]], + method: Method, annotation: AnnotationLike, + properties: Iterable[Property]): Option[String] = { + val elementValuePairs = annotation.elementValuePairs + val expected = ( + elementValuePairs.head.value.asIntValue.value, + elementValuePairs(1).value.asStringValue.value, + elementValuePairs(2).value.asBooleanValue.value + ) + val result = properties.filter(_.isInstanceOf[VTAResult]).head + .asInstanceOf[VTAResult] + .flows + .values + .fold(Set.empty)((acc, facts) => acc ++ facts) + .collect { + case VariableType(definedBy, t, upperBound) => + (taCode.lineNumber(method.body.get, definedBy).get, referenceTypeToString(t), + upperBound) + } + if (result.contains(expected)) None + else Some(expected.toString) + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/vta/VTAMatcher.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/vta/VTAMatcher.scala new file mode 100644 index 0000000000..9162e95d7e --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/fpcf/properties/vta/VTAMatcher.scala @@ -0,0 +1,59 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.fpcf.properties.vta + +import org.opalj.fpcf.Entity +import org.opalj.fpcf.FinalP +import org.opalj.fpcf.Property +import org.opalj.fpcf.properties.AbstractPropertyMatcher +import org.opalj.value.ValueInformation +import org.opalj.br.AnnotationLike +import org.opalj.br.DefinedMethod +import org.opalj.br.ObjectType +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.br.ArrayType +import org.opalj.br.Method +import org.opalj.br.ReferenceType +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.TheTACAI +import org.opalj.tac.DUVar +import org.opalj.tac.TACMethodParameter +import org.opalj.tac.TACode +import org.opalj.tac.fpcf.analyses.ifds.old.VTAFact + +abstract class VTAMatcher extends AbstractPropertyMatcher { + + def validateProperty( + p: SomeProject, + as: Set[ObjectType], + entity: Entity, + a: AnnotationLike, + properties: Iterable[Property] + ): Option[String] = { + val method = entity.asInstanceOf[(DefinedMethod, VTAFact)]._1.definedMethod + val taCode = p.get(PropertyStoreKey)(method, TACAI.key) match { + case FinalP(TheTACAI(tac)) => tac + case _ => + throw new IllegalStateException( + "TAC of annotated method not present after analysis" + ) + } + val result = a.elementValuePairs(0).value.asArrayValue.values + .map(annotationValue => + validateSingleAnnotation(p, entity, taCode, method, + annotationValue.asAnnotationValue.annotation, properties)).filter(_.isDefined) + if (result.isEmpty) None + else Some(result.map(_.get).mkString(", ")) + } + + def validateSingleAnnotation(project: SomeProject, entity: Entity, + taCode: TACode[TACMethodParameter, DUVar[ValueInformation]], + method: Method, annotation: AnnotationLike, + properties: Iterable[Property]): Option[String] + + def referenceTypeToString(t: ReferenceType): String = t match { + case objectType: ObjectType => objectType.simpleName + case arrayType: ArrayType => + referenceTypeToString(arrayType.elementType.asReferenceType)+"[]" + } +} \ No newline at end of file diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/js/JavaScriptAwareTaintProblemFixtureTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/js/JavaScriptAwareTaintProblemFixtureTest.scala new file mode 100644 index 0000000000..92850fa94b --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/js/JavaScriptAwareTaintProblemFixtureTest.scala @@ -0,0 +1,42 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.js + +import org.opalj.js.IFDSAnalysisJSFixtureScheduler +import org.opalj.fpcf.PropertiesTest +import org.opalj.ai.domain.l2 +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey +import org.opalj.br.analyses.Project +import org.opalj.fpcf.properties.taint.ForwardFlowPath +import org.opalj.tac.cg.RTACallGraphKey +import org.opalj.tac.fpcf.properties._ + +import java.net.URL + +class JavaScriptAwareTaintProblemFixtureTest extends PropertiesTest { + override def fixtureProjectPackage: List[String] = List( + "org/opalj/fpcf/fixtures/js" + ) + + override def init(p: Project[URL]): Unit = { + p.updateProjectInformationKeyInitializationData( + AIDomainFactoryKey + )( + (_: Option[Set[Class[_ <: AnyRef]]]) => + Set[Class[_ <: AnyRef]]( + classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[URL]] + ) + ) + p.get(RTACallGraphKey) + } + + describe("Test the ForwardFlowPath annotations") { + val testContext = executeAnalyses(IFDSAnalysisJSFixtureScheduler) + val project = testContext.project + val eas = methodsWithAnnotations(project).map { + case (method, entityString, annotations) => + ((method, TaintNullFact), entityString, annotations) + } + testContext.propertyStore.shutdown() + validateProperties(testContext, eas, Set(ForwardFlowPath.PROPERTY_VALIDATOR_KEY)) + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/ll/CrossLanguageForwardTaintAnalysisTest.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/ll/CrossLanguageForwardTaintAnalysisTest.scala new file mode 100644 index 0000000000..cdedaff9cb --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/ll/CrossLanguageForwardTaintAnalysisTest.scala @@ -0,0 +1,77 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll + +import com.typesafe.config.ConfigValueFactory + +import org.opalj.br.analyses.Project +import org.opalj.br.fpcf.FPCFAnalysesManagerKey +import org.opalj.ifds +import org.opalj.ifds.IFDSProperty +import org.opalj.ll.fpcf.analyses.ifds.{LLVMFunction, LLVMStatement} +import org.opalj.ll.fpcf.analyses.ifds.taint.{JavaForwardTaintAnalysisScheduler, NativeForwardTaintAnalysisScheduler, NativeTaintFact, NativeTaintNullFact} +import org.opalj.ll.fpcf.analyses.ifds.taint.SimpleJavaForwardTaintAnalysis +import org.opalj.ll.llvm.value.Function + +import org.opalj.log.GlobalLogContext +import org.opalj.tac.cg.RTACallGraphKey +import org.opalj.tac.fpcf.analyses.ifds.JavaStatement +import org.opalj.tac.fpcf.properties._ +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.should.Matchers + +class CrossLanguageForwardIFDSTaintAnalysisTests extends AnyFunSpec with Matchers { + describe("CrossLanguageForwardTaintAnalysis") { + implicit val config = BaseConfig.withValue(ifds.ConfigKeyPrefix+"debug", ConfigValueFactory.fromAnyRef(true)) + val project = + Project( + new java.io.File("./DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint"), + GlobalLogContext, + config + ) + + project.updateProjectInformationKeyInitializationData(LLVMProjectKey)( + current => List("./DEVELOPING_OPAL/validate/src/test/resources/llvm/cross_language/taint/TaintTest.ll") + ) + project.get(LLVMProjectKey) + project.get(RTACallGraphKey) + val manager = project.get(FPCFAnalysesManagerKey) + val (ps, analyses) = manager.runAll(JavaForwardTaintAnalysisScheduler, NativeForwardTaintAnalysisScheduler) + for ((method, fact) <- analyses.head._2.asInstanceOf[SimpleJavaForwardTaintAnalysis].ifdsProblem.entryPoints) { + val flows = + ps((method, fact), JavaForwardTaintAnalysisScheduler.property.key) + println("---METHOD: "+method.toJava+" ---") + val flowFacts = flows.ub + .asInstanceOf[IFDSProperty[JavaStatement, TaintFact]] + .flows + .values + .flatten + .toSet[TaintFact] + .flatMap { + case FlowFact(flow) => Some(flow) + case _ => None + } + for (flow <- flowFacts) + println(s"flow: "+flow.map(_.name).mkString(", ")) + if (method.name.contains("no_flow")) { + it(s"${method.name} has no flow") { + assert(flowFacts.isEmpty) + } + } else if (method.name.contains("flow")) { + it(s"${method.name} has some flow") { + assert(!flowFacts.isEmpty) + } + } + } + + val function: Function = project.get(LLVMProjectKey).function("Java_TaintTest_native_1array_1tainted").get + val debugData = ps((LLVMFunction(function), NativeTaintNullFact), NativeForwardTaintAnalysisScheduler.property.key).ub.asInstanceOf[IFDSProperty[LLVMStatement, NativeTaintFact]].debugData + for { + bb <- function.basicBlocks + instruction <- bb.instructions + } { + for (fact <- debugData.getOrElse(LLVMStatement(instruction), Set.empty)) + println("\t"+fact) + println(instruction.repr) + } + } +} diff --git a/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/ll/SimplePurityTests.scala b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/ll/SimplePurityTests.scala new file mode 100644 index 0000000000..565cb4344e --- /dev/null +++ b/DEVELOPING_OPAL/validate/src/test/scala/org/opalj/ll/SimplePurityTests.scala @@ -0,0 +1,36 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll + +import org.opalj.br.analyses.Project +import org.opalj.br.fpcf.FPCFAnalysesManagerKey +import org.opalj.ll.fpcf.analyses.{EagerSimplePurityAnalysis, Impure, Pure} +import org.opalj.ll.llvm.value.Function +import org.scalatest.funspec.AnyFunSpec +import org.scalatest.matchers.should.Matchers + +import scala.collection.immutable.List + +class SimplePurityTests extends AnyFunSpec with Matchers { + describe("SimplePurityAnalysis") { + it("executes") { + val project = Project(Iterable.empty) + project.updateProjectInformationKeyInitializationData(LLVMProjectKey)( + current => List("./DEVELOPING_OPAL/validate/src/test/resources/llvm/purity.ll") + ) + val (propertyStore, _) = project.get(FPCFAnalysesManagerKey).runAll(EagerSimplePurityAnalysis) + + val impureFunctionNames = propertyStore + .finalEntities(Impure) + .asInstanceOf[Iterator[Function]] + .map(_.name) + .toList + impureFunctionNames should contain("impure_function") + val pureFunctionNames = propertyStore + .finalEntities(Pure) + .asInstanceOf[Iterator[Function]] + .map(_.name) + .toList + pureFunctionNames should contain("pure_function") + } + } +} diff --git a/OPAL/bc/src/test/scala/org/opalj/test/fixtures/dynamicConstants/DynamicConstantsCreationTest.scala b/OPAL/bc/src/test/scala/org/opalj/test/fixtures/dynamicConstants/DynamicConstantsCreationTest.scala index d6018b6af9..5703392259 100644 --- a/OPAL/bc/src/test/scala/org/opalj/test/fixtures/dynamicConstants/DynamicConstantsCreationTest.scala +++ b/OPAL/bc/src/test/scala/org/opalj/test/fixtures/dynamicConstants/DynamicConstantsCreationTest.scala @@ -406,7 +406,7 @@ class DynamicConstantsCreationTest extends AnyFlatSpec with Matchers with Before (0xff & 176).toByte // areturn )) )) - ), + ) ), attributes = ArraySeq( BootstrapMethods_attribute(17, ArraySeq( diff --git a/OPAL/br/src/main/scala/org/opalj/br/ElementValue.scala b/OPAL/br/src/main/scala/org/opalj/br/ElementValue.scala index d95c6b3313..ed8cde73b8 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/ElementValue.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/ElementValue.scala @@ -20,6 +20,7 @@ sealed trait ElementValue extends Attribute { def toJava: String def asIntValue: IntValue = throw new ClassCastException(); + def asBooleanValue: BooleanValue = throw new ClassCastException(); def asEnumValue: EnumValue = throw new ClassCastException(); def asAnnotationValue: AnnotationValue = throw new ClassCastException(); def asStringValue: StringValue = throw new ClassCastException(); @@ -168,6 +169,8 @@ case class BooleanValue(value: Boolean) extends BaseTypeElementValue { override def kindId: Int = BooleanValue.KindId + final override def asBooleanValue: BooleanValue = this + } object BooleanValue { diff --git a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/EntryPointFinder.scala b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/EntryPointFinder.scala index 23d96c1298..cbf2456d4b 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/EntryPointFinder.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/EntryPointFinder.scala @@ -348,4 +348,44 @@ object AllEntryPointsFinder extends EntryPointFinder { project.allProjectClassFiles.flatMap(_.methodsWithBody) else project.allMethodsWithBody } +} + +/** + * The AndroidEntryPointFinder considers specific methods of app components as entry points. + * It does not work for androidx + * + * @author Tom Nikisch + */ +object AndroidEntryPointsFinder extends EntryPointFinder { + + val activityEPS: List[String] = List("onCreate", "onRestart", "onStart", "onResume", + "onStop", "onDestroy", "onActivityResult") + val serviceEPS: List[String] = List("onCreate", "onStartCommand", "onBind", "onStart") + val contentProviderEPS: List[String] = List("onCreate", "query", "insert", "update") + val locationListenerEPS: List[String] = List("onLocationChanged", "onProviderDisabled", "onProviderEnabled", + "onStatusChanged") + val onNmeaMessageListenerEPS: List[String] = List("onNmeaMessage") + val defaultEPS = Map("android/app/Activity" -> activityEPS, "android/app/Service" -> serviceEPS, + "android/content/ContentProvider" -> contentProviderEPS, + "android/location/LocationListener" -> locationListenerEPS, + "android/location/onNmeaMessageListener" -> onNmeaMessageListenerEPS) + + override def collectEntryPoints(project: SomeProject): Iterable[Method] = { + val eps = ArrayBuffer.empty[Method] + for ((superClass, methodList) <- defaultEPS) { + eps ++= findEPS(ObjectType(superClass), methodList, project) + } + eps + } + + def findEPS(ot: ObjectType, possibleEPS: List[String], project: SomeProject): ArrayBuffer[Method] = { + val eps = ArrayBuffer.empty[Method] + val classHierarchy = project.classHierarchy + classHierarchy.foreachSubclass(ot, project) { sc => + for (pep <- possibleEPS; m <- sc.findMethod(pep) if m.body.isDefined && !eps.contains(m)) { + eps += m + } + } + eps + } } \ No newline at end of file diff --git a/OPAL/ifds/Readme.md b/OPAL/ifds/Readme.md new file mode 100644 index 0000000000..0fbf431b65 --- /dev/null +++ b/OPAL/ifds/Readme.md @@ -0,0 +1,4 @@ +# Overview +The ***IFDS*** (ifds) module provides a generic implementation for IFDS analyses. + + diff --git a/OPAL/ifds/build.sbt b/OPAL/ifds/build.sbt new file mode 100644 index 0000000000..b511e98651 --- /dev/null +++ b/OPAL/ifds/build.sbt @@ -0,0 +1 @@ +// build settings reside in the opal root build.sbt file diff --git a/OPAL/ifds/src/main/resources/reference.conf b/OPAL/ifds/src/main/resources/reference.conf new file mode 100644 index 0000000000..d1459a04b7 --- /dev/null +++ b/OPAL/ifds/src/main/resources/reference.conf @@ -0,0 +1,5 @@ +org.opalj { + ifds { + debug = false, + } +} diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/AbstractIFDSFact.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/AbstractIFDSFact.scala new file mode 100644 index 0000000000..ae70c466de --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/AbstractIFDSFact.scala @@ -0,0 +1,23 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ifds + +import org.opalj.br.analyses.SomeProject + +/** + * The supertype of all IFDS facts, may implement "subsumes" to enable subsuming. + */ +trait AbstractIFDSFact { + /** + * Checks, if this fact subsumes an `other` fact. + * + * @param other The other fact. + * @param project The analyzed project. + * @return True, if this fact subsumes the `other`fact + */ + def subsumes(other: AbstractIFDSFact, project: SomeProject): Boolean = false +} + +/** + * The super type of all null facts. + */ +trait AbstractIFDSNullFact extends AbstractIFDSFact diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/Callable.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/Callable.scala new file mode 100644 index 0000000000..8319a74e60 --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/Callable.scala @@ -0,0 +1,14 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ifds + +abstract class Callable { + /** + * The name of the Callable + */ + def name: String + + /** + * The full name of the Callable including its signature + */ + def signature: String +} diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/DataFlowAnalysis.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/DataFlowAnalysis.scala new file mode 100644 index 0000000000..e561e1dea2 --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/DataFlowAnalysis.scala @@ -0,0 +1,45 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ifds + +import scala.collection.mutable + +abstract class DataFlowAnalysis[Facts >: Null <: AnyRef, C <: AnyRef, S <: Statement[_ <: C, _]] { + def icfg: ICFG[C, S] + def entryFacts: Facts + def transferFunction(facts: Facts, statement: S, successor: S): Facts + def join(left: Facts, right: Facts): Facts + + def perform(callable: C): Map[S, Facts] = { + var facts = Map.empty[S, Facts] + val workList = new mutable.Queue[S]() + + for (entryStatement <- icfg.startStatements(callable)) { + facts = facts.updated(entryStatement, entryFacts) + workList.enqueue(entryStatement) + } + + while (workList.nonEmpty) { + val statement = workList.dequeue() + val inFacts = facts.get(statement).get + + for (successor <- icfg.nextStatements(statement)) { + val newOutFacts = transferFunction(inFacts, statement, successor) + facts.get(successor) match { + case None => { + facts = facts.updated(successor, newOutFacts) + workList.enqueue(successor) + } + case Some(existingOutFacts) => { + val outFacts = join(existingOutFacts, newOutFacts) + if (outFacts ne existingOutFacts) { + facts = facts.updated(successor, outFacts) + workList.enqueue(successor) + } + } + } + } + } + + facts + } +} \ No newline at end of file diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/ICFG.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/ICFG.scala new file mode 100644 index 0000000000..4d3d2c8bb1 --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/ICFG.scala @@ -0,0 +1,39 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ifds + +import scala.collection.{Set => SomeSet} + +abstract class ICFG[C <: AnyRef, S <: Statement[_ <: C, _]] { + /** + * Determines the statements at which the analysis starts. + * + * @param callable The analyzed callable. + * @return The statements at which the analysis starts. + */ + def startStatements(callable: C): Set[S] + + /** + * Determines the statement, that will be analyzed after some other `statement`. + * + * @param statement The source statement. + * @return The successor statements + */ + def nextStatements(statement: S): Set[S] + + /** + * Gets the set of all methods possibly called at some statement. + * + * @param statement The statement. + * @return All callables possibly called at the statement or None, if the statement does not + * contain a call. + */ + def getCalleesIfCallStatement(statement: S): Option[SomeSet[_ <: C]] + + /** + * Determines whether the statement is an exit statement. + * + * @param statement The source statement. + * @return Whether the statement flow may exit its callable (function/method) + */ + def isExitStatement(statement: S): Boolean +} diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/IFDS.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/IFDS.scala new file mode 100644 index 0000000000..075ad7dc26 --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/IFDS.scala @@ -0,0 +1,29 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ifds + +object IFDS { + + /** + * Merges two maps that have sets as values. + * + * @param map1 The first map. + * @param map2 The second map. + * @return A map containing the keys of both maps. Each key is mapped to the union of both maps' + * values. + */ + def mergeMaps[S, T](map1: Map[S, Set[T]], map2: Map[S, Set[T]]): Map[S, Set[T]] = { + var result = map1 + for ((key, values) <- map2) { + result.get(key) match { + case Some(resultValues) => + if (resultValues.size > values.size) + result = result.updated(key, resultValues ++ values) + else + result = result.updated(key, values ++ resultValues) + case None => + result = result.updated(key, values) + } + } + result + } +} diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/IFDSAnalysis.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/IFDSAnalysis.scala new file mode 100644 index 0000000000..216ce58160 --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/IFDSAnalysis.scala @@ -0,0 +1,496 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ifds + +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.{FPCFAnalysis, FPCFLazyAnalysisScheduler} +import org.opalj.fpcf._ +import org.opalj.ifds.Dependees.Getter + +import scala.collection.{mutable, Set => SomeSet} + +case class Dependees[Work]() { + case class Dependee(eOptionP: SomeEOptionP, worklist: Set[Work] = Set.empty) + var dependees = Map.empty[SomeEPK, Dependee] + def get(entity: Entity, propertyKey: PropertyKey[Property])(implicit propertyStore: PropertyStore, work: Work): SomeEOptionP = { + val epk = EPK(entity, propertyKey) + val dependee = dependees.get(epk) match { + case Some(dependee) => Dependee(dependee.eOptionP, dependee.worklist + work) + case None => Dependee(propertyStore(epk), Set(work)) + } + if (!dependee.eOptionP.isFinal) dependees += epk -> dependee + dependee.eOptionP + } + + def forResult: Set[SomeEOptionP] = { + dependees.values.map(_.eOptionP).toSet + } + def takeWork(epk: SomeEPK): Set[Work] = { + val dependee = dependees(epk) + dependees -= epk + dependee.worklist + } + + def getter()(implicit propertyStore: PropertyStore, work: Work): Getter = + (entity: Entity, propertyKey: PropertyKey[Property]) => get(entity, propertyKey) +} + +object Dependees { + type Getter = (Entity, PropertyKey[Property]) => SomeEOptionP +} + +/** + * Keeps book of the path edges. + * An entry of (statement, fact) means an edge (s0, source fact) -> (statement, fact) exists, + * that is the fact reaches the statement as an input. + * Source fact is the fact within the analysis entity. + */ +case class PathEdges[IFDSFact <: AbstractIFDSFact, S <: Statement[_ <: C, _], C <: AnyRef](subsumes: (Set[IFDSFact], IFDSFact) => Boolean) { + var edges = Map.empty[S, Either[Set[IFDSFact], Map[S, Set[IFDSFact]]]] + + /** + * Add the edge (s0, source fact) -> (statement, fact) to the path edges. + * Optionally give a predecessor for the statement. This is used for phi statements + * to distinguish the input flow and merge the facts later. + * @param statement the destination statement of the edge + * @param predecessor the predecessor of the statement. + * @return whether the edge was new + */ + def add(statement: S, fact: IFDSFact, predecessor: Option[S] = None): Boolean = { + edges.get(statement) match { + case None => + predecessor match { + case Some(predecessor) => + edges = edges.updated(statement, Right(Map(predecessor -> Set(fact)))) + case None => + edges = edges.updated(statement, Left(Set(fact))) + } + true + case Some(Left(existingFacts)) => + if (predecessor.isDefined) throw new IllegalArgumentException(s"${statement} does not accept a predecessor") + if (isNew(existingFacts, fact)) { + edges = edges.updated(statement, Left(existingFacts + fact)) + true + } else false + case Some(Right(existingFacts)) => + predecessor match { + case None => throw new IllegalArgumentException(s"${statement} requires a predecessor") + case Some(predecessor) => existingFacts.get(statement) match { + case Some(existingPredecessorFacts) => { + if (isNew(existingPredecessorFacts, fact)) { + edges = edges.updated(statement, Right(existingFacts.updated(predecessor, existingPredecessorFacts + fact))) + true + } else false + } + case None => { + edges = edges.updated(statement, Right(existingFacts.updated(predecessor, Set(fact)))) + true + } + } + } + } + } + + private def isNew(existingFacts: Set[IFDSFact], newFact: IFDSFact): Boolean = { + !existingFacts.contains(newFact) && !subsumes(existingFacts, newFact) + } + + /** + * @param statement + * @return The edges reaching statement if any. In case the statement minds about predecessors it is a map with an entry for each predecessor + */ + def get(statement: S): Option[Either[Set[IFDSFact], Map[S, Set[IFDSFact]]]] = edges.get(statement) + + def debugData: Map[S, Set[IFDSFact]] = edges.foldLeft(Map.empty[S, Set[IFDSFact]])((result, elem) => { + val facts: Set[IFDSFact] = elem._2 match { + case Right(facts) => facts.foldLeft(Set.empty[IFDSFact])(_ ++ _._2) + case Left(facts) => facts + } + result.updated(elem._1, result.getOrElse(elem._1, Set.empty) ++ facts) + }) +} + +/** + * The state of the analysis. For each method and source fact, there is a separate state. + * + * @param source The callable and input fact for which the callable is analyzed. + * @param subsumes The subsuming function, return whether a new fact is subsume by the existing ones + */ +protected class IFDSState[IFDSFact <: AbstractIFDSFact, C <: AnyRef, S <: Statement[_ <: C, _], Work]( + val source: (C, IFDSFact), + subsumes: (Set[IFDSFact], IFDSFact) => Boolean +) { + val dependees: Dependees[Work] = Dependees[Work]() + val pathEdges: PathEdges[IFDSFact, S, C] = PathEdges[IFDSFact, S, C](subsumes) + var endSummaries: Set[(S, IFDSFact)] = Set.empty[(S, IFDSFact)] + var selfDependees: Set[Work] = Set.empty[Work] +} + +/** + * Contains int variables, which count, how many times some method was called. + */ +class Statistics { + var normalFlow = 0 + var callFlow = 0 + var returnFlow = 0 + var callToReturnFlow = 0 + var subsumeTries = 0 + var subsumptions = 0 +} + +protected class ProjectFPCFAnalysis(val project: SomeProject) extends FPCFAnalysis + +/** + * + * @param ifdsProblem + * @param propertyKey Provides the concrete property key that must be unique for every distinct concrete analysis and the lower bound for the IFDSProperty. + * @tparam IFDSFact + */ +class IFDSAnalysis[IFDSFact <: AbstractIFDSFact, C <: AnyRef, S <: Statement[_ <: C, _]]( + implicit + project: SomeProject, + val ifdsProblem: IFDSProblem[IFDSFact, C, S], + val propertyKey: IFDSPropertyMetaInformation[S, IFDSFact] +) extends ProjectFPCFAnalysis(project) { + type Work = (S, IFDSFact, Option[S]) // statement, fact, predecessor + type Worklist = mutable.Queue[Work] + type State = IFDSState[IFDSFact, C, S, Work] + + implicit var statistics = new Statistics + val icfg = ifdsProblem.icfg + + val DEBUG = false + + /** + * Performs an IFDS analysis for a method-fact-pair. + * + * @param entity The method-fact-pair, that will be analyzed. + * @return An IFDS property mapping from exit statements to the facts valid after these exit + * statements. Returns an interim result, if the TAC or call graph of this method or the + * IFDS analysis for a callee is still pending. + */ + def performAnalysis(entity: (C, IFDSFact)): ProperPropertyComputationResult = { + val (function, sourceFact) = entity + + // Start processing at the start of the icfg with the given source fact + implicit val state: State = new IFDSState[IFDSFact, C, S, Work](entity, subsumes) + implicit val queue: Worklist = mutable.Queue + .empty[Work] + icfg.startStatements(function).foreach { start => + state.pathEdges.add(start, sourceFact) // ifds line 2 + queue.enqueue((start, sourceFact, None)) // ifds line 3 + } + process() + createResult() + } + + /** + * Creates the current (intermediate) result for the analysis. + * + * @return A result containing a map, which maps each exit statement to the facts valid after + * the statement, based on the current results. If the analysis is still waiting for its + * method's TAC or call graph or the result of another method-fact-pair, an interim + * result will be returned. + * + */ + private def createResult()(implicit state: State): ProperPropertyComputationResult = { + val propertyValue = createPropertyValue() + val dependees = state.dependees.forResult + if (dependees.isEmpty) Result(state.source, propertyValue) + else InterimResult.forUB(state.source, propertyValue, dependees, propertyUpdate) + } + + /** + * Creates an IFDSProperty containing the result of this analysis. + * + * @param result Maps each exit statement to the facts, which hold after the exit statement. + * @return An IFDSProperty containing the `result`. + */ + private def createPropertyValue()(implicit state: State): IFDSProperty[S, IFDSFact] = + if (project.config.getBoolean(ConfigKeyPrefix+"debug")) + propertyKey.create(collectResult, state.pathEdges.debugData) + else + propertyKey.create(collectResult) + + /** + * Collects the facts valid at all exit nodes based on the current results. + * + * @return A map, mapping from each exit statement to the facts, which flow into exit statement. + */ + private def collectResult(implicit state: State): Map[S, Set[IFDSFact]] = { + state.endSummaries.foldLeft(Map.empty[S, Set[IFDSFact]])((result, entry) => + result.updated(entry._1, result.getOrElse(entry._1, Set.empty[IFDSFact]) + entry._2)) + } + + /** + * Called, when there is an updated result for a tac, call graph or another method-fact-pair, + * for which the current analysis is waiting. Re-analyzes the relevant parts of this method and + * returns the new analysis result. + * + * @param eps The new property value. + * @return The new (interim) result of this analysis. + */ + private def propertyUpdate( + eps: SomeEPS + )(implicit state: State): ProperPropertyComputationResult = { + implicit val queue: mutable.Queue[Work] = mutable.Queue() + state.dependees.takeWork(eps.toEPK).foreach(queue.enqueue(_)) + process() + createResult() + } + + /** + * Analyzes a queue of BasicBlocks. + * + * @param worklist + */ + private def process()(implicit state: State, worklist: Worklist): Unit = { + while (worklist.nonEmpty) { // ifds line 10 + implicit val work = worklist.dequeue() // ifds line 11 + val (statement, in, predecessor) = work + icfg.getCalleesIfCallStatement(statement) match { + case Some(callees) => + handleCall(statement, callees, in) // ifds line 13 + case None => { + if (icfg.isExitStatement(statement)) handleExit(statement, in) // ifds line 21 + // in case of exceptions exit statements may also have some normal flow so no else here + handleOther(statement, in, predecessor) // ifds line 33 + } + } + } + } + + /** + * Processes a statement with a call. + * + * @param call The call statement. + * @param callees All possible callees. + * @param in The facts, which hold before the call statement. + * instead of the facts returned by callToStartFacts. + * @return A map, mapping from each successor statement of the `call` to the facts, which hold + * at their start. + */ + private def handleCall( + call: S, + callees: SomeSet[_ <: C], + in: IFDSFact + )( + implicit + state: State, + worklist: Worklist, + work: Work + ): Unit = { + val successors = icfg.nextStatements(call) + for (callee <- callees) { + ifdsProblem.outsideAnalysisContext(callee) match { + case Some(outsideAnalysisHandler) => + // Let the concrete analysis decide what to do. + for { + successor <- successors + out <- outsideAnalysisHandler(call, successor, in, state.dependees.getter()) // ifds line 17 (only summary edges) + } { + propagate(successor, out, call) // ifds line 18 + } + case None => + for { + successor <- successors + out <- concreteCallFlow(call, callee, in, successor) // ifds line 17 (only summary edges) + } { + propagate(successor, out, call) // ifds line 18 + } + } + } + for { + successor <- successors + out <- callToReturnFlow(call, in, successor) // ifds line 17 (without summary edge propagation) + } { + propagate(successor, out, call) // ifds line 18 + } + } + + private def concreteCallFlow(call: S, callee: C, in: IFDSFact, successor: S)(implicit state: State, work: Work): Set[IFDSFact] = { + var result = Set.empty[IFDSFact] + val entryFacts = callFlow(call, callee, in) + for (entryFact <- entryFacts) { // ifds line 14 + val e = (callee, entryFact) + val exitFacts: Map[S, Set[IFDSFact]] = if (e == state.source) { + // handle self dependency on our own because property store can't handle it + state.selfDependees += work + collectResult(state) + } else { + // handle all other dependencies using property store + val callFlows = state.dependees.get(e, propertyKey.key).asInstanceOf[EOptionP[(C, IFDSFact), IFDSProperty[S, IFDSFact]]] + callFlows match { + case ep: FinalEP[_, IFDSProperty[S, IFDSFact]] => + ep.p.flows + case ep: InterimEUBP[_, IFDSProperty[S, IFDSFact]] => + ep.ub.flows + case _ => + Map.empty + } + } + for { + (exitStatement, exitStatementFacts) <- exitFacts // ifds line 15.2 + exitStatementFact <- exitStatementFacts // ifds line 15.3 + } { + result ++= returnFlow(exitStatement, exitStatementFact, call, in, successor) + } + } + result + } + + private def handleExit(statement: S, in: IFDSFact)(implicit state: State, worklist: Worklist): Unit = { + val newEdge = (statement, in) + if (!state.endSummaries.contains(newEdge)) { + state.endSummaries += ((statement, in)) // ifds line 21.1 + state.selfDependees.foreach(selfDependee => + worklist.enqueue(selfDependee)) + } + // ifds lines 22 - 31 are handled by the dependency propagation of the property store + // except for self dependencies which are handled above + } + + private def handleOther(statement: S, in: IFDSFact, predecessor: Option[S])(implicit state: State, worklist: Worklist): Unit = { + for { // ifds line 34 + successor <- icfg.nextStatements(statement) + out <- normalFlow(statement, in, predecessor) + } { + propagate(successor, out, statement) // ifds line 35 + } + } + + private def propagate(successor: S, out: IFDSFact, predecessor: S)(implicit state: State, worklist: Worklist): Unit = { + val predecessorOption = if (ifdsProblem.needsPredecessor(successor)) Some(predecessor) else None + + // ifds line 9 + if (state.pathEdges.add(successor, out, predecessorOption)) { + worklist.enqueue((successor, out, predecessorOption)) + } + } + + /** + * ifds flow function + * @param statement n + * @param in d2 + * @param predecessor pi + */ + private def normalFlow(statement: S, in: IFDSFact, predecessor: Option[S]): Set[IFDSFact] = { + statistics.normalFlow += 1 + val out = ifdsProblem.normalFlow(statement, in, predecessor) + if (DEBUG) { + printf("Normal: %s\n", statement.toString) + printf("In: %s\n", in.toString) + printf("Out: [") + out.foreach(f => printf("%s, ", f.toString)) + printf("]\n\n") + } + addNullFactIfConfigured(in, out) + } + + /** + * ifds passArgs function + * @param call n + * @param callee + * @param in d2 + * @return + */ + private def callFlow(call: S, callee: C, in: IFDSFact): Set[IFDSFact] = { + statistics.callFlow += 1 + val out = ifdsProblem.callFlow(call, callee, in) + if (DEBUG) { + printf("Call: %s\n", call.toString) + printf("In: %s\n", in.toString) + printf("Out: [") + out.foreach(f => printf("%s, ", f.toString)) + printf("]\n\n") + } + addNullFactIfConfigured(in, out) + } + + /** + * ifds returnVal function + * @param exit n + * @param in d2 + * @param call c + * @param callFact d4 + * @return + */ + private def returnFlow(exit: S, in: IFDSFact, call: S, callFact: IFDSFact, successor: S): Set[IFDSFact] = { + statistics.returnFlow += 1 + val out = ifdsProblem.returnFlow(exit, in, call, callFact, successor) + if (DEBUG) { + printf("Return: %s\n", call.toString) + printf("In: %s\n", in.toString) + printf("Out: [") + out.foreach(f => printf("%s, ", f.toString)) + printf("]\n\n") + } + addNullFactIfConfigured(in, out) + } + + /** + * ifds callFlow function + * @param call n + * @param in d2 + * @return + */ + private def callToReturnFlow(call: S, in: IFDSFact, successor: S): Set[IFDSFact] = { + statistics.callToReturnFlow += 1 + val out = ifdsProblem.callToReturnFlow(call, in, successor) + if (DEBUG) { + printf("CallToReturn: %s\n", call.toString) + printf("In: %s\n", in.toString) + printf("Out: [") + out.foreach(f => printf("%s, ", f.toString)) + printf("]\n\n") + } + addNullFactIfConfigured(in, out) + } + + private def addNullFactIfConfigured(in: IFDSFact, out: Set[IFDSFact]): Set[IFDSFact] = { + if (ifdsProblem.automaticallyPropagateNullFactInFlowFunctions && in == ifdsProblem.nullFact) + out + ifdsProblem.nullFact + else out + } + + private def subsumes(existingFacts: Set[IFDSFact], newFact: IFDSFact)(implicit project: SomeProject): Boolean = { + statistics.subsumeTries += 1 + if (ifdsProblem.subsumeFacts && existingFacts.exists(_.subsumes(newFact, project))) { + statistics.subsumptions += 1 + true + } else false + } +} + +abstract class IFDSAnalysisScheduler[IFDSFact <: AbstractIFDSFact, C <: AnyRef, S <: Statement[_ <: C, _]] + extends FPCFLazyAnalysisScheduler { + final override type InitializationData = IFDSAnalysis[IFDSFact, C, S] + def property: IFDSPropertyMetaInformation[S, IFDSFact] + final override def derivesLazily: Some[PropertyBounds] = Some(PropertyBounds.ub(property)) + override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} + + /** + * Registers the analysis as a lazy computation, that is, the method + * will call `ProperytStore.scheduleLazyComputation`. + */ + override def register( + p: SomeProject, + ps: PropertyStore, + analysis: IFDSAnalysis[IFDSFact, C, S] + ): FPCFAnalysis = { + ps.registerLazyPropertyComputation(property.key, analysis.performAnalysis) + analysis + } + + override def afterPhaseScheduling(ps: PropertyStore, analysis: FPCFAnalysis): Unit = { + val ifdsAnalysis = analysis.asInstanceOf[IFDSAnalysis[IFDSFact, C, S]] + for (e <- ifdsAnalysis.ifdsProblem.entryPoints) { + ps.force(e, ifdsAnalysis.propertyKey.key) + } + } + + override def afterPhaseCompletion( + p: SomeProject, + ps: PropertyStore, + analysis: FPCFAnalysis + ): Unit = {} +} diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/IFDSProblem.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/IFDSProblem.scala new file mode 100644 index 0000000000..1394d4ebf1 --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/IFDSProblem.scala @@ -0,0 +1,122 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ifds + +import org.opalj.ifds.Dependees.Getter + +/** + * A framework for IFDS analyses. + * + * @tparam IFDSFact The type of flow facts, which are tracked by the concrete analysis. + * @author Dominik Helm + * @author Mario Trageser + * @author Marc Clement + */ +abstract class IFDSProblem[IFDSFact <: AbstractIFDSFact, C <: AnyRef, S <: Statement[_ <: C, _]](val icfg: ICFG[C, S]) { + type Work = (S, IFDSFact, Option[S]) + + /** + * The null fact of this analysis. + */ + def nullFact: IFDSFact + + /** + * @return Whether the null Fact is automatically added to the result of every flow function where it is passed into + */ + def automaticallyPropagateNullFactInFlowFunctions: Boolean = true + + /** + * @return Whether to try to subsume new facts under existing facts and save graph edges + */ + def subsumeFacts: Boolean = false + + /** + * The entry points of this analysis. + */ + def entryPoints: Seq[(C, IFDSFact)] + + /** + * Computes the data flow for a normal statement. + * + * @param statement The analyzed statement. + * @param in The fact which holds before the execution of the `statement`. + * @param predecessor The predecessor of the analyzed `statement`, for which the data flow shall be + * computed. Used for phi statements to distinguish the flow. + * @return The facts, which hold after the execution of `statement` under the assumption + * that the facts in `in` held before `statement` and `successor` will be + * executed next. + */ + def normalFlow( + statement: S, + in: IFDSFact, + predecessor: Option[S] + ): Set[IFDSFact] + + /** + * Computes the data flow for a call to start edge. + * + * @param call The analyzed call statement. + * @param callee The called method, for which the data flow shall be computed. + * @param in The fact which holds before the execution of the `call`. + * @param source The entity, which is analyzed. + * @return The facts, which hold after the execution of `statement` under the assumption that + * the facts in `in` held before `statement` and `statement` calls `callee`. + */ + def callFlow( + call: S, + callee: C, + in: IFDSFact + ): Set[IFDSFact] + + /** + * Computes the data flow for an exit to return edge. + * + * @param call The statement, which called the `callee`. + * @param exit The statement, which terminated the `callee`. + * @param in The fact which holds before the execution of the `exit`. + * @return The facts, which hold after the execution of `exit` in the caller's context + * under the assumption that `in` held before the execution of `exit` and that + * `successor` will be executed next. + */ + def returnFlow( + exit: S, + in: IFDSFact, + call: S, + callFact: IFDSFact, + successor: S + ): Set[IFDSFact] + + /** + * Computes the data flow for a call to return edge. + * + * @param call The statement, which invoked the call. + * @param in The facts, which hold before the `call`. + * @return The facts, which hold after the call independently of what happens in the callee + * under the assumption that `in` held before `call`. + */ + def callToReturnFlow( + call: S, + in: IFDSFact, + successor: S + ): Set[IFDSFact] + + def needsPredecessor(statement: S): Boolean + + type OutsideAnalysisContextHandler = ((S, S, IFDSFact, Getter) => Set[IFDSFact]) { + def apply(call: S, successor: S, in: IFDSFact, dependeesGetter: Getter): Set[IFDSFact] + } + + /** + * Checks, if a callee is outside this analysis' context. + * By default, native methods are not inside the analysis context. + * For callees outside this analysis' context the returned handler is called + * to compute the summary edge for the call instead of analyzing the callee. + * + * @param callee The method called by `call`. + * @return The handler function. It receives + * the statement which invoked the call, + * the successor statement, which will be executed after the call and + * the set of input facts which hold before the `call`. + * It returns facts, which hold after the call, excluding the call to return flow. + */ + def outsideAnalysisContext(callee: C): Option[OutsideAnalysisContextHandler] +} \ No newline at end of file diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/IFDSProperty.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/IFDSProperty.scala new file mode 100644 index 0000000000..85a491e788 --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/IFDSProperty.scala @@ -0,0 +1,42 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ifds +import org.opalj.fpcf.Property +import org.opalj.fpcf.PropertyMetaInformation + +trait IFDSPropertyMetaInformation[S, IFDSFact <: AbstractIFDSFact] extends PropertyMetaInformation { + /** + * Creates an IFDSProperty containing the result of this analysis. + * + * @param result Maps each exit statement to the facts, which hold after the exit statement. + * @return An IFDSProperty containing the `result`. + */ + def create(result: Map[S, Set[IFDSFact]]): IFDSProperty[S, IFDSFact] + def create(result: Map[S, Set[IFDSFact]], debugData: Map[S, Set[IFDSFact]]): IFDSProperty[S, IFDSFact] +} + +abstract class IFDSProperty[S, IFDSFact <: AbstractIFDSFact] + extends Property + with IFDSPropertyMetaInformation[S, IFDSFact] { + + /** + * Maps exit statements to the data flow facts which hold before them. + */ + def flows: Map[S, Set[IFDSFact]] + + /** + * Maps all statements to the data flow facts which hold before them if debug setting is enabled. + */ + def debugData: Map[S, Set[IFDSFact]] + + override def equals(other: Any): Boolean = other match { + case that: IFDSProperty[S @unchecked, IFDSFact @unchecked] => + // We cached the "hashCode" to make the following comparison more efficient; + // note that all properties are eventually added to some set and therefore + // the hashCode is required anyway! + (this eq that) || (this.hashCode == that.hashCode && this.flows == that.flows) + case _ => + false + } + + override lazy val hashCode: Int = flows.hashCode() +} diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/Statement.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/Statement.scala new file mode 100644 index 0000000000..886fe3c906 --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/Statement.scala @@ -0,0 +1,7 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ifds + +abstract class Statement[C, Node] { + def node: Node + def callable: C +} diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/old/ICFG.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/old/ICFG.scala new file mode 100644 index 0000000000..c7d5fb5d97 --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/old/ICFG.scala @@ -0,0 +1,81 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ifds.old + +import org.opalj.ifds.{AbstractIFDSFact, Statement} + +import scala.collection.{Set => SomeSet} + +abstract class ICFG[IFDSFact <: AbstractIFDSFact, C <: AnyRef, S <: Statement[_, Node], Node] { + /** + * Determines the basic blocks, at which the analysis starts. + * + * @param sourceFact The source fact of the analysis. + * @param callable The analyzed callable. + * @return The basic blocks, at which the analysis starts. + */ + def startNodes(sourceFact: IFDSFact, callable: C): Set[Node] + + /** + * Determines the nodes, that will be analyzed after some `node`. + * + * @param node The basic block, that was analyzed before. + * @return The nodes, that will be analyzed after `node`. + */ + def nextNodes(node: Node): Set[Node] + + /** + * Checks, if some `node` is the last node. + * + * @return True, if `node` is the last node, i.e. there is no next node. + */ + def isLastNode(node: Node): Boolean + + /** + * Determines the first index of some `basic block`, that will be analyzed. + * + * @param node The basic block. + * @return The first index of some `basic block`, that will be analyzed. + */ + def firstStatement(node: Node): S + + /** + * Determines the last index of some `basic block`, that will be analzyed. + * + * @param node The basic block. + * @return The last index of some `basic block`, that will be analzyed. + */ + def lastStatement(node: Node): S + + /** + * Determines the statement that will be analyzed after some other statement. + * + * @param statement The current statement. + * @return The statement that will be analyzed after `statement`. + */ + def nextStatement(statement: S): S + + /** + * Determines the statement, that will be analyzed after some other `statement`. + * + * @param statement The source statement. + * @return The successor statements + */ + def nextStatements(statement: S): Set[S] + + /** + * Gets the set of all methods possibly called at some statement. + * + * @param statement The statement. + * @return All callables possibly called at the statement or None, if the statement does not + * contain a call. + */ + def getCalleesIfCallStatement(statement: S): Option[SomeSet[C]] + + /** + * Determines whether the statement is an exit statement. + * + * @param statement The source statement. + * @return Whether the statement flow may exit its callable (function/method) + */ + def isExitStatement(statement: S): Boolean +} diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/old/IFDSAnalysis.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/old/IFDSAnalysis.scala new file mode 100644 index 0000000000..17269161c9 --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/old/IFDSAnalysis.scala @@ -0,0 +1,647 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ifds.old + +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.{FPCFAnalysis, FPCFLazyAnalysisScheduler} +import org.opalj.fpcf._ +import org.opalj.ifds.{AbstractIFDSFact, IFDS, IFDSProperty, IFDSPropertyMetaInformation, Statement} + +import scala.collection.{mutable, Set => SomeSet} + +/** + * The state of the analysis. For each method and source fact, there is a separate state. + * + * @param source The input fact, for which the `method` is analyzed. + * @param pendingIfdsCallSites Maps callees of the analyzed `method` together with their input + * facts to the instruction, at which they may be called. + * @param pendingIfdsDependees Maps callees of the analyzed `method` together with their input + * facts to the intermediate result of their IFDS analysis. + * Only contains method-fact-pairs, for which this analysis is + * waiting for a final result. + * @param pendingCgCallSites The basic blocks containing call sites, for which the analysis is + * waiting for the final call graph result. + * @param incomingFacts Maps each basic block to the data flow facts valid at its first + * statement. + * @param outgoingFacts Maps each basic block and successor node to the data flow facts valid at + * the beginning of the successor. For exit statements the successor is None + */ +protected class IFDSState[IFDSFact <: AbstractIFDSFact, C <: AnyRef, S <: Statement[C, Node], Node]( + val source: (C, IFDSFact), + var pendingIfdsCallSites: Map[(C, IFDSFact), Set[S]] = Map.empty[(C, IFDSFact), Set[S]], + var pendingIfdsDependees: Map[(C, IFDSFact), EOptionP[(C, IFDSFact), IFDSProperty[S, IFDSFact]]] = Map.empty[(C, IFDSFact), EOptionP[(C, IFDSFact), IFDSProperty[S, IFDSFact]]], + var pendingCgCallSites: Set[Node] = Set.empty[Node], + var incomingFacts: Map[Node, Set[IFDSFact]] = Map.empty[Node, Set[IFDSFact]], + var outgoingFacts: Map[Node, Map[Option[Node], Set[IFDSFact]]] = Map.empty[Node, Map[Option[Node], Set[IFDSFact]]] +) + +/** + * Contains int variables, which count, how many times some method was called. + */ +class NumberOfCalls { + var normalFlow = 0 + var callFlow = 0 + var returnFlow = 0 + var callToReturnFlow = 0 +} + +protected class Statistics { + + /** + * Counts, how many times the abstract methods were called. + */ + var numberOfCalls = new NumberOfCalls() + + /** + * Counts, how many input facts are passed to callbacks. + */ + var sumOfInputfactsForCallbacks = 0L +} + +protected class ProjectFPCFAnalysis(val project: SomeProject) extends FPCFAnalysis + +/** + * + * @param ifdsProblem + * @param propertyKey Provides the concrete property key that must be unique for every distinct concrete analysis and the lower bound for the IFDSProperty. + * @tparam IFDSFact + */ +class IFDSAnalysis[IFDSFact <: AbstractIFDSFact, C <: AnyRef, S <: Statement[C, Node], Node]( + implicit + project: SomeProject, + val ifdsProblem: IFDSProblem[IFDSFact, C, S, Node], + val propertyKey: IFDSPropertyMetaInformation[S, IFDSFact] +) extends ProjectFPCFAnalysis(project) with Subsumable[S, IFDSFact] { + type State = IFDSState[IFDSFact, C, S, Node] + type QueueEntry = (Node, Set[IFDSFact], Option[S], Option[C], Option[IFDSFact]) + + implicit var statistics = new Statistics + val icfg = ifdsProblem.icfg + + /** + * Creates an IFDSProperty containing the result of this analysis. + * + * @param result Maps each exit statement to the facts, which hold after the exit statement. + * @return An IFDSProperty containing the `result`. + */ + protected def createPropertyValue(result: Map[S, Set[IFDSFact]]): IFDSProperty[S, IFDSFact] = + propertyKey.create(result) + + /** + * Performs an IFDS analysis for a method-fact-pair. + * + * @param entity The method-fact-pair, that will be analyzed. + * @return An IFDS property mapping from exit statements to the facts valid after these exit + * statements. Returns an interim result, if the TAC or call graph of this method or the + * IFDS analysis for a callee is still pending. + */ + def performAnalysis(entity: (C, IFDSFact)): ProperPropertyComputationResult = { + val (function, sourceFact) = entity + + // Start processing at the start of the cfg with the given source fact + implicit val state: State = + new State(entity, Map(entity -> Set.empty), Map.empty, Set.empty[Node]) + val queue = mutable.Queue + .empty[QueueEntry] + icfg.startNodes(sourceFact, function).foreach { start => + state.incomingFacts += start -> Set(sourceFact) + queue.enqueue((start, Set(sourceFact), None, None, None)) + } + process(queue) + createResult() + } + + /** + * Creates the current (intermediate) result for the analysis. + * + * @return A result containing a map, which maps each exit statement to the facts valid after + * the statement, based on the current results. If the analysis is still waiting for its + * method's TAC or call graph or the result of another method-fact-pair, an interim + * result will be returned. + * + */ + protected def createResult()(implicit state: State): ProperPropertyComputationResult = { + val propertyValue = createPropertyValue(collectResult) + val dependees = state.pendingIfdsDependees.values + if (dependees.isEmpty) Result(state.source, propertyValue) + else InterimResult.forUB(state.source, propertyValue, dependees.toSet, propertyUpdate) + } + + /** + * Called, when there is an updated result for a tac, call graph or another method-fact-pair, + * for which the current analysis is waiting. Re-analyzes the relevant parts of this method and + * returns the new analysis result. + * + * @param eps The new property value. + * @return The new (interim) result of this analysis. + */ + protected def propertyUpdate( + eps: SomeEPS + )(implicit state: State): ProperPropertyComputationResult = { + (eps: @unchecked) match { + case FinalE(e: (C, IFDSFact) @unchecked) => + reAnalyzeCalls(state.pendingIfdsCallSites(e), e._1, Some(e._2)) + + case interimEUBP @ InterimEUBP( + e: (C, IFDSFact) @unchecked, + ub: IFDSProperty[S, IFDSFact] @unchecked + ) => + if (ub.flows.values + .forall(facts => facts.size == 1 && facts.forall(_ == ifdsProblem.nullFact))) { + // Do not re-analyze the caller if we only get the null fact. + // Update the pendingIfdsDependee entry to the new interim result. + state.pendingIfdsDependees += + e -> interimEUBP + .asInstanceOf[EOptionP[(C, IFDSFact), IFDSProperty[S, IFDSFact]]] + } else + reAnalyzeCalls(state.pendingIfdsCallSites(e), e._1, Some(e._2)) + + case FinalEP(_: C @unchecked, _: Any) => + // TODO: Any was Callees, how to verify this? + reAnalyzeBasicBlocks(state.pendingCgCallSites) + + case InterimEUBP(_: C @unchecked, _: Any) => + // TODO: Any was Callees, how to verify this? + reAnalyzeBasicBlocks(state.pendingCgCallSites) + } + + createResult() + } + + /** + * Processes a statement with a call. + * + * @param basicBlock The basic block, which contains the `call`. + * @param call The call statement. + * @param callees All possible callees. + * @param in The facts, which hold before the call statement. + * @param calleeWithUpdateFact If present, the `callees` will only be analyzed with this fact + * instead of the facts returned by callToStartFacts. + * @return A map, mapping from each successor statement of the `call` to the facts, which hold + * at their start. + */ + protected def handleCall( + basicBlock: Node, + call: S, + callees: SomeSet[C], + in: Set[IFDSFact], + calleeWithUpdateFact: Option[IFDSFact] + )( + implicit + state: State + ): Map[S, Set[IFDSFact]] = { + val successors = icfg.nextStatements(call) + // Facts valid at the start of each successor + var summaryEdges: Map[S, Set[IFDSFact]] = Map.empty + + /* + * If calleeWithUpdateFact is present, this means that the basic block already has been + * analyzed with the `inputFacts`. + */ + if (calleeWithUpdateFact.isEmpty) + for (successor <- successors) { + statistics.numberOfCalls.callToReturnFlow += 1 + statistics.sumOfInputfactsForCallbacks += in.size + summaryEdges += successor -> + propagateNullFact( + in, + ifdsProblem.callToReturnFlow(call, successor, in, state.source) + ) + } + + for (callee <- callees) { + ifdsProblem.outsideAnalysisContext(callee) match { + case Some(handler) => + // Let the concrete analysis decide what to do. + for { + successor <- successors + } summaryEdges += + successor -> (summaryEdges(successor) ++ + handler(call, successor, in)) + case None => + val callToStart = + if (calleeWithUpdateFact.isDefined) Set(calleeWithUpdateFact.get) + else { + propagateNullFact(in, callToStartFacts(call, callee, in)) + } + var allNewExitFacts: Map[S, Set[IFDSFact]] = Map.empty + // Collect exit facts for each input fact separately + for (fact <- callToStart) { + /* + * If this is a recursive call with the same input facts, we assume that the + * call only produces the facts that are already known. The call site is added to + * `pendingIfdsCallSites`, so that it will be re-evaluated if new output facts + * become known for the input fact. + */ + if ((callee eq state.source._1) && fact == state.source._2) { + val newDependee = + if (state.pendingIfdsCallSites.contains(state.source)) + state.pendingIfdsCallSites(state.source) + call + else Set(call) + state.pendingIfdsCallSites = + state.pendingIfdsCallSites.updated(state.source, newDependee) + allNewExitFacts = IFDS.mergeMaps(allNewExitFacts, collectResult) + } else { + val e = (callee, fact) + val callFlows = propertyStore(e, propertyKey.key) + .asInstanceOf[EOptionP[(C, IFDSFact), IFDSProperty[S, IFDSFact]]] + val oldValue = state.pendingIfdsDependees.get(e) + val oldExitFacts: Map[S, Set[IFDSFact]] = oldValue match { + case Some(ep: InterimEUBP[_, IFDSProperty[S, IFDSFact]]) => ep.ub.flows + case _ => Map.empty + } + val exitFacts: Map[S, Set[IFDSFact]] = callFlows match { + case ep: FinalEP[_, IFDSProperty[S, IFDSFact]] => + if (state.pendingIfdsCallSites.contains(e) + && state.pendingIfdsCallSites(e).nonEmpty) { + val newDependee = + state.pendingIfdsCallSites(e) - call + state.pendingIfdsCallSites = state.pendingIfdsCallSites.updated(e, newDependee) + } + state.pendingIfdsDependees -= e + ep.p.flows + case ep: InterimEUBP[_, IFDSProperty[S, IFDSFact]] => + /* + * Add the call site to `pendingIfdsCallSites` and + * `pendingIfdsDependees` and continue with the facts in the interim + * result for now. When the analysis for the callee finishes, the + * analysis for this call site will be triggered again. + */ + addIfdsDependee(e, callFlows, basicBlock, call) + ep.ub.flows + case _ => + addIfdsDependee(e, callFlows, basicBlock, call) + Map.empty + } + // Only process new facts that are not in `oldExitFacts` + allNewExitFacts = IFDS.mergeMaps( + allNewExitFacts, + filterNewInformation(exitFacts, oldExitFacts, project) + ) + /* + * If new exit facts were discovered for the callee-fact-pair, all call + * sites depending on this pair have to be re-evaluated. oldValue is + * undefined if the callee-fact pair has not been queried before or returned + * a FinalEP. + */ + if (oldValue.isDefined && oldExitFacts != exitFacts) { + reAnalyzeCalls( + state.pendingIfdsCallSites(e), + e._1, + Some(e._2) + ) + } + } + } + summaryEdges = addExitToReturnFacts(summaryEdges, successors, call, callee, allNewExitFacts) + } + } + summaryEdges + } + + /** + * Collects the facts valid at all exit nodes based on the current results. + * + * @return A map, mapping from each predecessor of all exit nodes to the facts, which hold at + * the exit node under the assumption that the predecessor was executed before. + */ + protected def collectResult(implicit state: State): Map[S, Set[IFDSFact]] = { + var result = Map.empty[S, Set[IFDSFact]] + state.outgoingFacts.foreach( + blockFacts => + blockFacts._2.get(None) match { + case Some(facts) => result += icfg.lastStatement(blockFacts._1) -> facts + case None => + } + ) + result + } + + /** + * Calls callFlow. + */ + protected def callToStartFacts(call: S, callee: C, in: Set[IFDSFact])( + implicit + state: State + ): Set[IFDSFact] = { + statistics.numberOfCalls.callFlow += 1 + statistics.sumOfInputfactsForCallbacks += in.size + ifdsProblem.callFlow(call, callee, in, state.source) + } + + /** + * Combines each normal exit node with each normal successor and each abnormal exit statement + * with each catch node. Calls returnFlow for those pairs and adds them to the summary edges. + */ + protected def addExitToReturnFacts( + summaryEdges: Map[S, Set[IFDSFact]], + successors: Set[S], + call: S, + callee: C, + exitFacts: Map[S, Set[IFDSFact]] + ): Map[S, Set[IFDSFact]] = { + // First process for normal returns, then abnormal returns. + var result = summaryEdges + for { + successor <- successors + exitStatement <- exitFacts.keys + } result = addSummaryEdge(result, call, exitStatement, successor, callee, exitFacts) + result + } + + /** + * Analyzes a queue of BasicBlocks. + * + * @param worklist A queue of the following elements: + * bb The basic block that will be analyzed. + * in New data flow facts found to hold at the beginning of the basic block. + * calleeWithUpdateIndex If the basic block is analyzed because there is new information + * for a callee, this is the call site's index. + * calleeWithUpdate If the basic block is analyzed because there is new information for a + * callee, this is the callee. + * calleeWithUpdateFact If the basic block is analyzed because there is new information + * for a callee with a specific input fact, this is the input fact. + */ + private def process( + worklist: mutable.Queue[QueueEntry] + )(implicit state: State): Unit = { + while (worklist.nonEmpty) { + val (basicBlock, in, calleeWithUpdateSite, calleeWithUpdate, calleeWithUpdateFact) = + worklist.dequeue() + val oldOut = state.outgoingFacts.getOrElse(basicBlock, Map.empty) + val nextOut = analyzeBasicBlock( + basicBlock, + in, + calleeWithUpdateSite, + calleeWithUpdate, + calleeWithUpdateFact + ) + val allOut = IFDS.mergeMaps(oldOut, nextOut).view.mapValues(facts => subsume(facts, project)).toMap + state.outgoingFacts = state.outgoingFacts.updated(basicBlock, allOut) + + for (successor <- icfg.nextNodes(basicBlock)) { + if (icfg.isLastNode(successor)) { + // Re-analyze recursive call sites with the same input fact. + val nextOutSuccessors = nextOut.get(Some(successor)) + if (nextOutSuccessors.isDefined && nextOutSuccessors.get.nonEmpty) { + val oldOutSuccessors = oldOut.get(Some(successor)) + if (oldOutSuccessors.isEmpty || containsNewInformation( + nextOutSuccessors.get, + oldOutSuccessors.get, + project + )) { + val source = state.source + reAnalyzeCalls( + state.pendingIfdsCallSites(source), + source._1, + Some(source._2) + ) + } + } + } else { + val successorBlock = successor + val nextIn = nextOut.getOrElse(Some(successorBlock), Set.empty) + val oldIn = state.incomingFacts.getOrElse(successorBlock, Set.empty) + val newIn = notSubsumedBy(nextIn, oldIn, project) + val mergedIn = + if (nextIn.size > oldIn.size) nextIn ++ oldIn + else oldIn ++ nextIn + state.incomingFacts = + state.incomingFacts.updated(successorBlock, subsume(mergedIn, project)) + /* + * Only process the successor with new facts. + * It is analyzed at least one time because of the null fact. + */ + if (newIn.nonEmpty) worklist.enqueue((successorBlock, newIn, None, None, None)) + } + } + } + } + + /** + * Computes for one basic block the facts valid on each CFG edge leaving the block if `sources` + * held before the block. + * + * @param basicBlock The basic block, that will be analyzed. + * @param in The facts, that hold before the block. + * @param calleeWithUpdateSite If the basic block is analyzed because there is new information + * for a callee, this is the call site. + * @param calleeWithUpdate If the basic block is analyzed because there is new information for + * a callee, this is the callee. + * @param calleeWithUpdateFact If the basic block is analyzed because there is new information + * for a callee with a specific input fact, this is the input fact. + * @return A map, mapping each successor node to its input facts. Instead of catch nodes, this + * map contains their handler nodes. + */ + private def analyzeBasicBlock( + basicBlock: Node, + in: Set[IFDSFact], + calleeWithUpdateSite: Option[S], + calleeWithUpdate: Option[C], + calleeWithUpdateFact: Option[IFDSFact] + )( + implicit + state: State + ): Map[Option[Node], Set[IFDSFact]] = { + + /* + * Collects information about a statement. + * + * @param index The statement's index. + * @return A tuple of the following elements: + * statement: The statement at `index`. + * callees: The methods possibly called at this statement, if it contains a call. + * If `index` equals `calleeWithUpdateIndex`, only `calleeWithUpdate` will + * be returned. + * calleeFact: If `index` equals `calleeWithUpdateIndex`, only + * `calleeWithUpdateFact` will be returned, None otherwise. + */ + def collectInformation(statement: S): (Option[SomeSet[C]], Option[IFDSFact]) = { + val calleesO = + if (calleeWithUpdateSite.contains(statement)) calleeWithUpdate.map(Set(_)) + else icfg.getCalleesIfCallStatement(statement) + val calleeFact = + if (calleeWithUpdateSite.contains(statement)) calleeWithUpdateFact + else None + (calleesO, calleeFact) + } + + val last = icfg.lastStatement(basicBlock) + var flows: Set[IFDSFact] = in + var statement: S = icfg.firstStatement(basicBlock) + + // Iterate over all statements but the last one, only keeping the resulting DataFlowFacts. + while (statement != last) { + val (calleesO, calleeFact) = collectInformation(statement) + val successor = icfg.nextStatement(statement) + flows = if (calleesO.isEmpty) { + statistics.numberOfCalls.normalFlow += 1 + statistics.sumOfInputfactsForCallbacks += in.size + ifdsProblem.normalFlow(statement, Some(successor), flows) + } else + // Inside a basic block, we only have one successor --> Take the head + handleCall(basicBlock, statement, calleesO.get, flows, calleeFact).values.head + statement = successor + } + + // Analyze the last statement for each possible successor statement. + val (calleesO, callFact) = collectInformation(last) + var result: Map[Option[Node], Set[IFDSFact]] = + if (calleesO.isEmpty) { + var result: Map[Option[Node], Set[IFDSFact]] = Map.empty + for (node <- icfg.nextNodes(basicBlock)) { + statistics.numberOfCalls.normalFlow += 1 + statistics.sumOfInputfactsForCallbacks += in.size + result += Some(node) -> ifdsProblem.normalFlow( + statement, + Some(icfg.firstStatement(node)), + flows + ) + } + if (icfg.isExitStatement(last)) { + statistics.numberOfCalls.normalFlow += 1 + statistics.sumOfInputfactsForCallbacks += in.size + result += None -> ifdsProblem.normalFlow(statement, None, flows) + } + result + } else + handleCall(basicBlock, statement, calleesO.get, flows, callFact) + .map(entry => Some(entry._1.node) -> entry._2) + + // Propagate the null fact. + result = result.map(result => result._1 -> propagateNullFact(in, result._2)) + result + } + + /** + * Re-analyzes some basic blocks. + * + * @param basicBlocks The basic blocks, that will be re-analyzed. + */ + private def reAnalyzeBasicBlocks(basicBlocks: Set[Node])(implicit state: State): Unit = { + val queue: mutable.Queue[QueueEntry] = mutable.Queue.empty + for (bb <- basicBlocks) queue.enqueue((bb, state.incomingFacts(bb), None, None, None)) + process(queue) + } + + /** + * Re-analyzes some call sites with respect to one specific callee. + * + * @param callSites The call sites, which are analyzed. + * @param callee The callee, which will be considered at the `callSites`. + * @param fact If defined, the `callee` will only be analyzed for this fact. + */ + private def reAnalyzeCalls( + callSites: Set[S], + callee: C, + fact: Option[IFDSFact] + )(implicit state: State): Unit = { + val queue: mutable.Queue[QueueEntry] = mutable.Queue.empty + for (callSite <- callSites) { + val node = callSite.node + queue.enqueue((node, state.incomingFacts(node), Some(callSite), Some(callee), fact)) + } + process(queue) + } + + /** + * If `from` contains a null fact, it will be added to `to`. + * + * @param from The set, which may contain the null fact initially. + * @param to The set, to which the null fact may be added. + * @return `to` with the null fact added, if it is contained in `from`. + */ + private def propagateNullFact(from: Set[IFDSFact], to: Set[IFDSFact]): Set[IFDSFact] = { + if (from.contains(ifdsProblem.nullFact)) to + ifdsProblem.nullFact + else to + } + + /** + * Adds a method-fact-pair as to the IFDS call sites and dependees. + * + * @param entity The method-fact-pair. + * @param calleeProperty The property, that was returned for `entity`. + * @param callBB The basic block of the call site. + * @param call The call site. + */ + private def addIfdsDependee( + entity: (C, IFDSFact), + calleeProperty: EOptionP[(C, IFDSFact), IFDSProperty[S, IFDSFact]], + callBB: Node, + call: S + )(implicit state: State): Unit = { + val callSites = state.pendingIfdsCallSites + state.pendingIfdsCallSites = callSites.updated( + entity, + callSites.getOrElse(entity, Set.empty) + call + ) + state.pendingIfdsDependees += entity -> calleeProperty + } + + /** + * Adds a summary edge for a call to a map representing summary edges. + * + * @param summaryEdges The current map representing the summary edges. + * Maps from successor statements to facts, which hold at their beginning. + * @param call The call, calling the `callee`. + * @param exitStatement The exit statement for the new summary edge. + * @param successor The successor statement of the call for the new summary edge. + * @param callee The callee, called by `call`. + * @param allNewExitFacts A map, mapping from the exit statements of `callee` to their newly + * found exit facts. + * @return `summaryEdges` with an additional or updated summary edge from `call` to `successor`. + */ + private def addSummaryEdge( + summaryEdges: Map[S, Set[IFDSFact]], + call: S, + exitStatement: S, + successor: S, + callee: C, + allNewExitFacts: Map[S, Set[IFDSFact]] + ): Map[S, Set[IFDSFact]] = { + val in = allNewExitFacts.getOrElse(exitStatement, Set.empty) + statistics.numberOfCalls.returnFlow += 1 + statistics.sumOfInputfactsForCallbacks += in.size + val returned = ifdsProblem.returnFlow(call, callee, exitStatement, successor, in) + val newFacts = + if (summaryEdges.contains(successor) && summaryEdges(successor).nonEmpty) { + val summaryForSuccessor = summaryEdges(successor) + if (summaryForSuccessor.size >= returned.size) summaryForSuccessor ++ returned + else returned ++ summaryForSuccessor + } else returned + summaryEdges.updated(successor, newFacts) + } +} + +abstract class IFDSAnalysisScheduler[IFDSFact <: AbstractIFDSFact, C <: AnyRef, S <: Statement[C, Node], Node] + extends FPCFLazyAnalysisScheduler { + final override type InitializationData = IFDSAnalysis[IFDSFact, C, S, Node] + def property: IFDSPropertyMetaInformation[S, IFDSFact] + final override def derivesLazily: Some[PropertyBounds] = Some(PropertyBounds.ub(property)) + override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} + + /** + * Registers the analysis as a lazy computation, that is, the method + * will call `ProperytStore.scheduleLazyComputation`. + */ + override def register( + p: SomeProject, + ps: PropertyStore, + analysis: IFDSAnalysis[IFDSFact, C, S, Node] + ): FPCFAnalysis = { + ps.registerLazyPropertyComputation(property.key, analysis.performAnalysis) + analysis + } + + override def afterPhaseScheduling(ps: PropertyStore, analysis: FPCFAnalysis): Unit = { + val ifdsAnalysis = analysis.asInstanceOf[IFDSAnalysis[IFDSFact, C, S, Node]] + for (e <- ifdsAnalysis.ifdsProblem.entryPoints) { + ps.force(e, ifdsAnalysis.propertyKey.key) + } + } + + override def afterPhaseCompletion( + p: SomeProject, + ps: PropertyStore, + analysis: FPCFAnalysis + ): Unit = {} +} diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/old/IFDSProblem.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/old/IFDSProblem.scala new file mode 100644 index 0000000000..21f6934963 --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/old/IFDSProblem.scala @@ -0,0 +1,117 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ifds.old + +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.ifds.{AbstractIFDSFact, IFDSPropertyMetaInformation, Statement} + +/** + * A framework for IFDS analyses. + * + * @tparam IFDSFact The type of flow facts, which are tracked by the concrete analysis. + * @author Dominik Helm + * @author Mario Trageser + */ +abstract class IFDSProblem[IFDSFact <: AbstractIFDSFact, C <: AnyRef, S <: Statement[_, Node], Node](val icfg: ICFG[IFDSFact, C, S, Node]) { + /** + * The null fact of this analysis. + */ + def nullFact: IFDSFact + + /** + * The entry points of this analysis. + */ + def entryPoints: Seq[(C, IFDSFact)] + + /** + * Computes the data flow for a normal statement. + * + * @param statement The analyzed statement. + * @param successor The successor of the analyzed `statement`, for which the data flow shall be + * computed. It is not present for exit statements. + * @param in The facts, which hold before the execution of the `statement`. + * @return The facts, which hold after the execution of `statement` under the assumption + * that the facts in `in` held before `statement` and `successor` will be + * executed next. + */ + def normalFlow( + statement: S, + successor: Option[S], + in: Set[IFDSFact] + ): Set[IFDSFact] + + /** + * Computes the data flow for a call to start edge. + * + * @param call The analyzed call statement. + * @param callee The called method, for which the data flow shall be computed. + * @param in The facts, which hold before the execution of the `call`. + * @param source The entity, which is analyzed. + * @return The facts, which hold after the execution of `statement` under the assumption that + * the facts in `in` held before `statement` and `statement` calls `callee`. + */ + def callFlow( + call: S, + callee: C, + in: Set[IFDSFact], + source: (C, IFDSFact) + ): Set[IFDSFact] + + /** + * Computes the data flow for an exit to return edge. + * + * @param call The statement, which called the `callee`. + * @param callee The method called by `call`, for which the data flow shall be computed. + * @param exit The statement, which terminated the `calle`. + * @param successor The statement of the caller, which will be executed after the `callee` + * returned. + * @param in The facts, which hold before the execution of the `exit`. + * @return The facts, which hold after the execution of `exit` in the caller's context + * under the assumption that `in` held before the execution of `exit` and that + * `successor` will be executed next. + */ + def returnFlow( + call: S, + callee: C, + exit: S, + successor: S, + in: Set[IFDSFact] + ): Set[IFDSFact] + + /** + * Computes the data flow for a call to return edge. + * + * @param call The statement, which invoked the call. + * @param successor The statement, which will be executed after the call. + * @param in The facts, which hold before the `call`. + * @param source The entity, which is analyzed. + * @return The facts, which hold after the call independently of what happens in the callee + * under the assumption that `in` held before `call`. + */ + def callToReturnFlow( + call: S, + successor: S, + in: Set[IFDSFact], + source: (C, IFDSFact) + ): Set[IFDSFact] + + type OutsideAnalysisContextHandler = ((S, S, Set[IFDSFact]) => Set[IFDSFact]) { + def apply(call: S, successor: S, in: Set[IFDSFact]): Set[IFDSFact] + } + + /** + * Checks, if a callee is outside this analysis' context. + * By default, native methods are not inside the analysis context. + * For callees outside this analysis' context the returned handler is called + * to compute the summary edge for the call instead of analyzing the callee. + * + * @param callee The method called by `call`. + * @return The handler function. It receives + * the statement which invoked the call, + * the successor statement, which will be executed after the call and + * the set of input facts which hold before the `call`. + * It returns facts, which hold after the call, excluding the call to return flow. + */ + def outsideAnalysisContext(callee: C): Option[OutsideAnalysisContextHandler] + + def specialCase(source: (C, IFDSFact), propertyKey: IFDSPropertyMetaInformation[S, IFDSFact]): Option[ProperPropertyComputationResult] = None +} \ No newline at end of file diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/old/Subsumable.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/old/Subsumable.scala new file mode 100644 index 0000000000..66438926ef --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/old/Subsumable.scala @@ -0,0 +1,83 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ifds.old + +import org.opalj.br.analyses.SomeProject +import org.opalj.ifds.AbstractIFDSFact + +/** + * Defines functions, which can be overwritten to implement subsuming. + * + * @author Mario Trageser + */ +trait Subsumable[S, IFDSFact <: AbstractIFDSFact] { + + /** + * A subclass can override this method to filter the `facts` in some set, which are not subsumed + * by another fact in the set. + * + * + * @param facts The set of facts. + * @param project The project, which is analyzed. + * @return The facts, which are not subsumed by any other fact in `facts`. + * By default, `facts` is returned without removing anything. + */ + protected def subsume[T <: IFDSFact](facts: Set[T], project: SomeProject): Set[T] = facts + + /** + * Checks, if any fact from some set is not equal to or subsumed by any fact in another set. + * A subclass implementing subsuming must overwrite this method to consider subsuming. + * + * @param newFacts The facts, which were found. Not empty. + * @param oldFacts The facts, which are already known. Not empty. + * @param project The project, which is analyzed. + * @return True, if any fact in `newFacts`, is not equal to or subsumed by any fact in + * `oldFacts`. + */ + protected def containsNewInformation[T <: IFDSFact]( + newFacts: Set[T], + oldFacts: Set[T], + project: SomeProject + ): Boolean = + newFacts.exists(newFact => !oldFacts.contains(newFact)) + + /** + * Filters the new information from a new set of exit facts given the already known exit facts. + * A subclass implementing subsuming must overwrite this method to consider subsuming. + * + * @param newExitFacts The new exit facts. + * @param oldExitFacts The old exit facts. + * @param project The project, which is analyzed. + * @return A map, containing the keys of `newExitFacts`. + * Facts, which are equal to or subsumed by any fact for the same statement in + * `oldExitFacts` are not present. + */ + protected def filterNewInformation[T <: IFDSFact]( + newExitFacts: Map[S, Set[T]], + oldExitFacts: Map[S, Set[T]], + project: SomeProject + ): Map[S, Set[T]] = { + var result = newExitFacts + for ((key, values) <- oldExitFacts) { + result = result.updated(key, result(key) -- values) + } + result + } + + /** + * Filters the `facts` from some set, which are not equal to or subsumed by any fact in an + * `other` set. + * A subclass implementing subsuming must overwrite this method to consider subsuming. + * + * @param facts The set, from which facts are filtered. + * @param otherFacts The set, which may subsume facts from `facts`. + * @param project The project, which is analyzed. + * @return The facts from `facts`, which are not equal to or subsumed by any fact in an + * `otherFacts`. + */ + protected def notSubsumedBy[T <: IFDSFact]( + facts: Set[T], + otherFacts: Set[T], + project: SomeProject + ): Set[T] = + facts -- otherFacts +} diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/old/Subsuming.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/old/Subsuming.scala new file mode 100644 index 0000000000..06cd2476a4 --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/old/Subsuming.scala @@ -0,0 +1,92 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ifds.old + +import org.opalj.ifds.AbstractIFDSFact + +import org.opalj.br.analyses.SomeProject + +/** + * An IFDS analysis, which implements subsuming. + * + * @author Mario Trageser + */ +trait Subsuming[S, IFDSFact <: AbstractIFDSFact] extends Subsumable[S, IFDSFact] { + + val numberOfSubsumptions = new NumberOfSubsumptions + + /** + * Considers subsuming. + */ + override protected def subsume[T <: IFDSFact](facts: Set[T], project: SomeProject): Set[T] = { + val result = facts.foldLeft(facts) { + (result, fact) => + if (facts.exists(other => other != fact && other.subsumes(fact, project))) result - fact + else result + } + numberOfSubsumptions.triesToSubsume += 1 + if (result.size != facts.size) numberOfSubsumptions.successfulSubsumes += 1 + result + } + + /** + * Considers subsuming. + */ + override protected def containsNewInformation[T <: IFDSFact](newFacts: Set[T], oldFacts: Set[T], + project: SomeProject): Boolean = + newFacts.exists { + /* + * In most cases, the fact will be contained in the old facts. + * This is why we first do the contains check before linearly iterating over the old facts. + */ + fact => !(oldFacts.contains(fact) || oldFacts.exists(_.subsumes(fact, project))) + } + + /** + * Considers subsuming. + */ + override protected def filterNewInformation[T <: IFDSFact]( + newExitFacts: Map[S, Set[T]], + oldExitFacts: Map[S, Set[T]], project: SomeProject + ): Map[S, Set[T]] = + newExitFacts.keys.map { + statement => + val old = oldExitFacts.get(statement) + val newFacts = newExitFacts(statement) + val newInformation = + if (old.isDefined && old.get.nonEmpty) notSubsumedBy(newFacts, old.get, project) + else newFacts + statement -> newInformation + }.toMap + + /** + * Considers subsuming. + */ + override protected def notSubsumedBy[T <: IFDSFact](facts: Set[T], otherFacts: Set[T], + project: SomeProject): Set[T] = { + val result = facts.foldLeft(facts) { + (result, fact) => + if (otherFacts.contains(fact) || otherFacts.exists(_.subsumes(fact, project))) + result - fact + else result + } + numberOfSubsumptions.triesToSubsume += 1 + if (result.size != facts.size) numberOfSubsumptions.successfulSubsumes += 1 + result + } +} + +/** + * Counts, how often subsume was called and how often it eliminated a fact. + */ +class NumberOfSubsumptions { + + /** + * The number of subsume and notSubsumedBy calls. + */ + var triesToSubsume = 0 + + /** + * The number of subsume and notSubsumedBy calls, which eliminated a fact. + */ + var successfulSubsumes = 0 +} \ No newline at end of file diff --git a/OPAL/ifds/src/main/scala/org/opalj/ifds/package.scala b/OPAL/ifds/src/main/scala/org/opalj/ifds/package.scala new file mode 100644 index 0000000000..8bfcc78717 --- /dev/null +++ b/OPAL/ifds/src/main/scala/org/opalj/ifds/package.scala @@ -0,0 +1,30 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj + +import com.typesafe.config.{Config, ConfigFactory} +import org.opalj.log.LogContext +import org.opalj.log.GlobalLogContext +import org.opalj.log.OPALLogger.info + +package object ifds { + + final val FrameworkName = "OPAL IFDS" + + { + implicit val logContext: LogContext = GlobalLogContext + try { + assert(false) // <= test whether assertions are turned on or off... + info(FrameworkName, "Production Build") + } catch { + case _: AssertionError => info(FrameworkName, "Development Build with Assertions") + } + } + + // We want to make sure that the class loader is used which potentially can + // find the config files; the libraries (e.g., Typesafe Config) may have + // been loaded using the parent class loader and, hence, may not be able to + // find the config files at all. + val BaseConfig: Config = ConfigFactory.load(this.getClass.getClassLoader) + + final val ConfigKeyPrefix = "org.opalj.ifds." +} diff --git a/OPAL/js/Readme.md b/OPAL/js/Readme.md new file mode 100644 index 0000000000..d0995ae939 --- /dev/null +++ b/OPAL/js/Readme.md @@ -0,0 +1,146 @@ +# JavaScript module +This module provides an IFDS taint analysis that is aware of calls to `ScriptEngine` objects that evaluate JavaScript and models them by internally invoking another IFDS taint analysis running on the JavaScript code, handing back the results to the Java IFDS taint analysis. + +We identified four challenges: +1. Finding the JavaScript source code which is executed on a call to a `ScriptEngine`. +2. Tracking the taints inside the `ScriptEngine` object. +3. Handing over the taints between two taint analyses across different static analysis frameworks without rewriting an analysis. +4. Scalability in presence unboxing of primitive type wrappers. + +## 1. Finding the source code +`org.opal.js.LocalJSSourceFinder` implements a way to find the JavaScript source code. The class uses the Def-Use information provided by *TACAI* to find the source code. + +There are two ways in how JavaScript is invoked from Java. First, `eval()` can be used to evaluate code using a `ScriptEngine`: +```java +ScriptEngineManager sem = new ScriptEngineManager(); +ScriptEngine se = sem.getEngineByName("JavaScript"); +se.eval("*** insert JavaScript code here ***"); +``` +If the argument to `eval` is a constant string, the source code is already known. The argument could also be a `FileReader` or `File`. In that case, the `LocalJSSourceFinder` looks up to find the definition of the `FileReader`/`File` and tries to get the file name. + +The other way is to call `invokeFunction(functionName, ...args)` on the `ScriptEngine` object. That case is a bit more complicated, because the source code of the function was provided with an earlier call to `eval()`. So here `LocalJSSourceFinder` goes up to the definition of the `ScriptEngine` and then looks for `eval()` calls on the definition. + +### Limitations +The `LocalJSSourceFinder` is only able to find the source code intraprocedural, inherited from using the def-use chains. + +## 2. Tracking tainted JavaScript variables in Java +A `ScriptEngine` also holds an environment called `Binding` that maps variables names to values. With `put(varName, obj)` data can be handed to JavaScript and with `get(varName)` can be retrieved back from JavaScript. Every variable in the `Binding` is available as a global variable in the execution of the JavaScript script. Thus, to precisely model what's happening in JavaScript, we need to know the JavaScript variables that are tainted at a call to `eval` or `invokeFunction`. + +In essence, the environment is just a `Map` for Java. To track the tainted keys inside the map, we do manually model the behavior of the `get`, `put`, `remove` and `putAll` methods called on a `ScriptEngine` or `Binding` instance. At each call to first three, the first argument represents the variable name. Again, we use the def-use chains from *TACAI* to find a string constant. We introduce a new taint type called `BindingFact`, which holds a variable, either of type `ScriptEngine` or `Binding` and the tainted key. + +### Design Decisions +#### In case of a non-constant argument +```java +String var = getUserInput(); +se.put(var, "some value"); +``` +Whenever the analysis does not know the constant string value of the first argument to `put`, it does derive a wildcard fact. The analysis considers a wildcard fact as if all variables inside the environment are tainted. + +#### Killing taints +```java + String var; + | + se.put("v1", secret); + | + if (cond) + / \ + var = "v1"; var = "v2"; + \ / + se.remove(var); +``` +In the example above, `v1` is tainted inside the environment. Later on, `var` is removed from the environment but the constant string depends on the control-flow so `var` is either `"v1"` or `"v2"`. Herre two analysis could do two things: + +1. Kill the `BindingFact(_, "v1")`, because `"v1"` might have been removed. +2. Leave `BindingFact(_, "v1")` alive, because `"v2"` might have been removed. + +The analysis implements the second way. Having to decide in this case is just a limitation of our analysis not being path-sensitive. + +## 3. Gluing two analyses together +The semantics of `eval` and `invokeFunction` can be modelled by transforming the JavaScript source code. For this, we introduce two functions: `opal_source()` and `opal_last_stmt(...)`. The first one is marked as a source in the JavaScript analysis and used to taint all global variables that were handed over from Java. The latter is, as the name suggest, the last statement and records all taints that reach the end of the execution. We do provide dummy implementations for both functions to work around the fact that WALAs Call Graph generation does eliminate call sites without a callee. + +We do exploit that the entry and exit points of JavaScript script are easy to find out: The script is executed from top to bottom. Thus, we do perform the transformation on the source code directly. + +We do inject four symbols that may not be already in the script: `opal_source`, `opal_last_stmt`, `opal_fill_arg` and `opal_tainted_arg`. We assume that is the case, there is no check for this. + +* `opal_source` is a function that has no parameters and returns a value. This function shall be marked as a source in the JavaScript analysis. +* `opal_last_stmt` is a function with n parameters where n is the number of variables visible in the top-level scope. +* `opal_fill_arg` is an untainted variable used to generate valid function calls. +* `opal_tainted_arg` is a tainted variable used to generate valid function calls. + +```javascript +// Begin of OPAL generated code +function opal_source() { + return "secret"; +} +function opal_last_stmt(p0, p1) { } + +var secret = opal_source(); +// End of OPAL generated code + +var xxx = secret; + +// Begin of OPAL generated code +opal_last_stmt(secret, xxx); +// End of OPAL generated code +``` +Above is the generated code for a simple example where `secret` is handed over from the environment inside a `ScriptEngine`. Before the actual script, `secret` is declared in the top-level scope and tainted. After the script, `opal_last_stmt` takes all variable names as arguments. For each tainted argument, WALA is queried for the variable name and converted back to a `BindingFact`. + +One might ask why we need to pass all variables in scope as arguments. Take a look at the following JavaScript snippet: +```javascript +var secret = opal_source(); +secret = 42; +opal_last_stmt(secret); +``` +and now in SSA form: +```javascript +_1 = opal_source(); +_2 = 42; +opal_last_stmt(_2); +``` +In the SSA form, `_1` is tainted at the end and if we'd query the name of `_1`, that would be `secret`. To find out for which SSA variables we should query the name, we pass them as arguments. That also makes the Java analysis less dependent on the JavaScript analysis. In theory, to switch the underlying JavaScript analysis, one would only need to implement a way such that the JavaScript analysis returns the original variable names for the tainted arguments of `opal_last_stmt`. + +```javascript +// Begin of OPAL generated code +function opal_source() { + return "secret"; +} +function opal_last_stmt(p0) { } +// End of OPAL generated code + +function check(str, unused) { + return str === "1337"; +} + +// Begin of OPAL generated code +var opal_fill_arg = 42; +var opal_tainted_arg = opal_source(); +var opal_tainted_return = check(opal_fill_arg, opal_tainted_arg); +opal_last_stmt(opal_tainted_return); +// End of OPAL generated code +``` +Above is another example of a transformation, but this time for tainted arguments to `invokeFunction`. Here, we first generate the two variables, one tainted and one untainted. These are used to generate a valid call to the function that is invoked by `invokeFunction`. The return value is always named `opal_tainted_return` and also flows at the end into `opal_last_stmt`. Back in Java, when converting the variable names back to facts, the variable name `opal_tainted_return` indicates the special case that instead of a `BindingFact`, the return value should be tainted. + +### Limitations +In theory, if there are too many variables in the top-level scope, the number of arguments in `opal_last_stmt` could reach the maximum number of arguments of the Rhino JavaScript engine, which WALA uses. The limit seems to be in the thousands, so that should never accidentally happen in practise. + +Also, there is a bug in the WALA JavaScript IFDS analysis I did not fix. Look at the snippet below: +```javascript +var x; +function setX(v) { + x = v; +} +``` +Assume a call to `setX` with a tainted argument. At the end of the execution, `x` should be tainted. But in the WALA IFDS analysis, this is not the case. + +## 4. Scalability +When using `invokeFunction` to call JavaScript from Java, the variable parameters are passed as `Object` and the return value is an `Object` as well. In case of primitive arguments or returns, boxing and unboxing is needed. In that case, the analysis needs the JDK to precisely resolve boxings. But the IFDS solver in the current state is too inefficient such that the `JavaScriptAwareTaintProblem` gets slow. + +I have decided to implement a reader of the summaries from the *StubDroid* paper. Overriding the `useSummaries` field of `ForwardTaintProblem` enables the use of those summaries. With those, there is also no need to load the JDK, which further brings the analysis up to speed. +> S. Arzt and E. Bodden, "StubDroid: Automatic Inference of Precise Data-Flow Summaries for the Android Framework," 2016 IEEE/ACM 38th International Conference on Software Engineering (ICSE), 2016, pp. 725-735, doi: 10.1145/2884781.2884816. + + +## Things left open +* While the `LocalJSSourceFinder` is able to find filenames, I did not implement a way to find these files in the filesystem. Thus, the `JavaScriptAnalysisCaller` ignores `JavaScriptFileSource`. +* In various places, the analysis depends on finding a string constant. Plugging in a string analysis before running the JavaScript aware taint analysis might greatly improve the accuracy. +* Pooling of JavaScript analysis calls. Currently, for each taint that gets handed over to the JavaScript, a new analysis run is started. We could save the overhead of constructing a JavaScript AST and exploded supergraph everytime by pooling all taints and only calling the JavaScript analysis when the worklist of the Java IFDS solver is empty. +* Another optimization could be inside the IFDS solver: implementing unbalanced returns and only propagate zero-facts at sources. diff --git a/OPAL/js/build.sbt b/OPAL/js/build.sbt new file mode 100644 index 0000000000..b511e98651 --- /dev/null +++ b/OPAL/js/build.sbt @@ -0,0 +1 @@ +// build settings reside in the opal root build.sbt file diff --git a/OPAL/js/src/main/scala/org/opalj/js/IFDSAnalysisFixtureScheduler.scala b/OPAL/js/src/main/scala/org/opalj/js/IFDSAnalysisFixtureScheduler.scala new file mode 100644 index 0000000000..0adb3c38d8 --- /dev/null +++ b/OPAL/js/src/main/scala/org/opalj/js/IFDSAnalysisFixtureScheduler.scala @@ -0,0 +1,21 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org +package opalj +package js + +import org.opal.js.JavaScriptAwareTaintAnalysisFixture +import org.opalj.br.Method +import org.opalj.br.analyses.{DeclaredMethodsKey, ProjectInformationKeys, SomeProject} +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.fpcf.{PropertyBounds, PropertyStore} +import org.opalj.ifds.{IFDSAnalysisScheduler, IFDSPropertyMetaInformation} +import org.opalj.tac.cg.TypeProviderKey +import org.opalj.tac.fpcf.analyses.ifds.JavaStatement +import org.opalj.tac.fpcf.properties.{Taint, TaintFact} + +object IFDSAnalysisJSFixtureScheduler extends IFDSAnalysisScheduler[TaintFact, Method, JavaStatement] { + override def init(p: SomeProject, ps: PropertyStore) = new JavaScriptAwareTaintAnalysisFixture(p) + override def property: IFDSPropertyMetaInformation[JavaStatement, TaintFact] = Taint + override val uses: Set[PropertyBounds] = Set(PropertyBounds.ub(Taint)) + override def requiredProjectInformation: ProjectInformationKeys = Seq(TypeProviderKey, DeclaredMethodsKey, PropertyStoreKey) +} \ No newline at end of file diff --git a/OPAL/js/src/main/scala/org/opalj/js/JSFact.scala b/OPAL/js/src/main/scala/org/opalj/js/JSFact.scala new file mode 100644 index 0000000000..2b54b893ae --- /dev/null +++ b/OPAL/js/src/main/scala/org/opalj/js/JSFact.scala @@ -0,0 +1,22 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.js + +import org.opalj.tac.fpcf.properties.TaintFact + +/* Common trait for facts for ScriptEngines. */ +trait JSFact extends TaintFact + +/** + * A tainted value inside a Key-Value-Map. + * + * @param index variable of a map type + * @param keyName name of the key. Empty string if unknown. + */ +case class BindingFact(index: Int, keyName: String) extends JSFact with TaintFact + +/** + * A tainted value inside a Key-Value-Map where the value is not statically known. + * + * @param index variable of a map type + */ +case class WildcardBindingFact(index: Int) extends JSFact with TaintFact diff --git a/OPAL/js/src/main/scala/org/opalj/js/JavaScriptAwareTaintAnalysisFixture.scala b/OPAL/js/src/main/scala/org/opalj/js/JavaScriptAwareTaintAnalysisFixture.scala new file mode 100644 index 0000000000..ce944b5057 --- /dev/null +++ b/OPAL/js/src/main/scala/org/opalj/js/JavaScriptAwareTaintAnalysisFixture.scala @@ -0,0 +1,67 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org +package opal +package js + +import org.opalj.br.Method +import org.opalj.br.analyses.SomeProject +import org.opalj.ifds.IFDSAnalysis +import org.opalj.js.JavaScriptAwareTaintProblem +import org.opalj.tac.fpcf.analyses.ifds.JavaMethod +import org.opalj.tac.fpcf.analyses.ifds.JavaStatement +import org.opalj.tac.fpcf.properties.{FlowFact, Taint, TaintFact, TaintNullFact, Variable} + +/** + * An analysis that checks, if the return value of the method `source` can flow to the parameter of + * the method `sink`. + * + * @author Mario Trageser + */ +class JavaScriptAwareTaintAnalysisFixture(project: SomeProject) + extends IFDSAnalysis()(project, new JavaScriptAwareTaintProblemProblemFixture(project), Taint) + +class JavaScriptAwareTaintProblemProblemFixture(p: SomeProject) extends JavaScriptAwareTaintProblem(p) { + /* Without, the tests are unbearably slow. */ + override def useSummaries = true + + /** + * The analysis starts with all public methods in TaintAnalysisTestClass. + */ + override val entryPoints: Seq[(Method, TaintFact)] = + p.allProjectClassFiles + .flatMap(classFile => classFile.methods) + .filter(method => method.isPublic && outsideAnalysisContext(method).isEmpty) + .map( + method => + method -> TaintNullFact) + + + + /** + * The sanitize method is a sanitizer. + */ + override protected def sanitizesReturnValue(callee: Method): Boolean = callee.name == "sanitize" + + /** + * We do not sanitize paramters. + */ + override protected def sanitizesParameter(call: JavaStatement, in: TaintFact): Boolean = false + + /** + * Creates a new variable fact for the callee, if the source was called. + */ + override protected def createTaints(callee: Method, call: JavaStatement): Set[TaintFact] = + if (callee.name == "source") Set(Variable(call.index)) + else Set.empty + + /** + * Create a FlowFact, if sink is called with a tainted variable. + * Note, that sink does not accept array parameters. No need to handle them. + */ + override protected def createFlowFact(callee: Method, call: JavaStatement, + in: TaintFact): Option[FlowFact] = + if (callee.name == "sink" && in == Variable(-2)) + Some(FlowFact(Seq(JavaMethod(call.method)))) + else None +} + diff --git a/OPAL/js/src/main/scala/org/opalj/js/JavaScriptAwareTaintProblem.scala b/OPAL/js/src/main/scala/org/opalj/js/JavaScriptAwareTaintProblem.scala new file mode 100644 index 0000000000..9fc96ad14d --- /dev/null +++ b/OPAL/js/src/main/scala/org/opalj/js/JavaScriptAwareTaintProblem.scala @@ -0,0 +1,278 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.js + +import org.opalj.br.{Method, ObjectType} +import org.opalj.br.analyses.SomeProject +import org.opalj.ifds.Dependees.Getter +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.{NO_MATCH, V} +import org.opalj.tac.fpcf.analyses.ifds.taint.ForwardTaintProblem +import org.opalj.tac.{AITACode, Assignment, Call, ComputeTACAIKey, Expr, ReturnValue, TACMethodParameter, TACode} +import org.opalj.tac.fpcf.analyses.ifds.{JavaIFDSProblem, JavaMethod, JavaStatement} +import org.opalj.tac.fpcf.properties._ +import org.opalj.value.ValueInformation + +import scala.annotation.nowarn + +/** + * Java IFDS analysis that is able to resolve calls to JavaScript. + * + * @param p project + */ +class JavaScriptAwareTaintProblem(p: SomeProject) extends ForwardTaintProblem(p) { + final type TACAICode = TACode[TACMethodParameter, JavaIFDSProblem.V] + val tacaiKey: Method => AITACode[TACMethodParameter, ValueInformation] = p.get(ComputeTACAIKey) + + /** + * Called, when the exit to return facts are computed for some `callee` with the null fact and + * the callee's return value is assigned to a variable. + * Creates a taint, if necessary. + * + * @param callee The called method. + * @param call The call. + * @return Some variable fact, if necessary. Otherwise none. + */ + override protected def createTaints(callee: Method, call: JavaStatement): Set[TaintFact] = + if (callee.name == "source") Set(Variable(call.index)) + else Set.empty + + /** + * Called, when the call to return facts are computed for some `callee`. + * Creates a FlowFact, if necessary. + * + * @param callee The method, which was called. + * @param call The call. + * @return Some FlowFact, if necessary. Otherwise None. + */ + override protected def createFlowFact( + callee: Method, + call: JavaStatement, + in: TaintFact + ): Option[FlowFact] = + if (callee.name == "sink" && in == Variable(-2)) + Some(FlowFact(Seq(JavaMethod(call.method), JavaMethod(callee)))) + else None + + /** + * The entry points of this analysis. + */ + override def entryPoints: Seq[(Method, TaintFact)] = + for { + m <- p.allMethodsWithBody + if m.name == "main" + } yield m -> TaintNullFact + + /** + * Checks, if some `callee` is a sanitizer, which sanitizes its return value. + * In this case, no return flow facts will be created. + * + * @param callee The method, which was called. + * @return True, if the method is a sanitizer. + */ + override protected def sanitizesReturnValue(callee: Method): Boolean = callee.name == "sanitize" + + /** + * Called in callToReturnFlow. This method can return whether the input fact + * will be removed after `callee` was called. I.e. the method could sanitize parameters. + * + * @param call The call statement. + * @param in The fact which holds before the call. + * @return Whether in will be removed after the call. + */ + override protected def sanitizesParameter(call: JavaStatement, in: TaintFact): Boolean = false + + override def callFlow(call: JavaStatement, callee: Method, in: TaintFact): Set[TaintFact] = { + val callObject = JavaIFDSProblem.asCall(call.stmt) + val allParams = callObject.allParams + + val allParamsWithIndices = allParams.zipWithIndex + in match { + case BindingFact(index, keyName) => allParamsWithIndices.flatMap { + case (param, paramIndex) if param.asVar.definedBy.contains(index) => + Some(BindingFact(JavaIFDSProblem.switchParamAndVariableIndex( + paramIndex, + callee.isStatic + ), keyName)) + case _ => None // Nothing to do + }.toSet + case _ => super.callFlow(call, callee, in) + } + } + + override def returnFlow(exit: JavaStatement, in: TaintFact, call: JavaStatement, + callFact: TaintFact, successor: JavaStatement): Set[TaintFact] = { + if (!isPossibleReturnFlow(exit, successor)) return Set.empty + val callee = exit.callable + if (sanitizesReturnValue(callee)) return Set.empty + val callStatement = JavaIFDSProblem.asCall(call.stmt) + val allParams = callStatement.allParams + + in match { + case BindingFact(index, keyName) => + var flows: Set[TaintFact] = Set.empty + if (index < 0 && index > -100) { + val param = allParams( + JavaIFDSProblem.switchParamAndVariableIndex(index, callee.isStatic) + ) + flows ++= param.asVar.definedBy.map(i => BindingFact(i, keyName)) + } + if (exit.stmt.astID == ReturnValue.ASTID && call.stmt.astID == Assignment.ASTID) { + val returnValueDefinedBy = exit.stmt.asReturnValue.expr.asVar.definedBy + if (returnValueDefinedBy.contains(index)) + flows += BindingFact(call.index, keyName) + } + flows + case _ => super.returnFlow(exit, in, call, callFact, successor) + } + } + + /** + * Kills the flow. + * + * @return empty set + */ + def killFlow( + @nowarn call: JavaStatement, + @nowarn successor: JavaStatement, + @nowarn in: TaintFact, + @nowarn dependeesGetter: Getter + ): Set[TaintFact] = Set.empty + + val scriptEngineMethods: Map[ObjectType, List[String]] = Map( + ObjectType("javax/script/Invocable") -> List("invokeFunction"), + ObjectType("javax/script/ScriptEngine") -> List("put", "get", "eval"), + ObjectType("javax/script/Bindings") -> List("put", "get", "putAll", "remove"), + ) + + /** + * Checks whether we handle the method. + * + * @param objType type of the base object + * @param methodName method name of the call + * @return true if we have a rule for the method call + */ + def invokesScriptFunction(objType: ObjectType, methodName: String): Boolean = + scriptEngineMethods.exists(kv => objType.isSubtypeOf(kv._1)(p.classHierarchy) && kv._2.contains(methodName)) + + def invokesScriptFunction(callStmt: Call[JavaIFDSProblem.V]): Boolean = + invokesScriptFunction(callStmt.declaringClass.mostPreciseObjectType, callStmt.name) + + def invokesScriptFunction(method: Method): Boolean = + invokesScriptFunction(method.classFile.thisType, method.name) + + /** + * Kill the flow if we handle the call outside. + */ + override def outsideAnalysisContext(callee: Method): Option[OutsideAnalysisContextHandler] = { + if (invokesScriptFunction(callee)) { + Some(killFlow _) + } else { + super.outsideAnalysisContext(callee) + } + } + + /** + * Returns all possible constant strings. Contains the empty string if at least one was non-constant. + * + * @param method method + * @param defSites def sites of the queried variable + * @return + */ + /*private def getPossibleStrings(method: Method, defSites: IntTrieSet): Set[String] = { + val taCode = tacaiKey(method) + + // TODO: use string analysis here + defSites.map(site => taCode.stmts.apply(site)).map { + case a: Assignment[JavaIFDSProblem.V] if a.expr.isStringConst => + a.expr.asStringConst.value + case _ => "" + } + } */ + + override def callToReturnFlow(call: JavaStatement, in: TaintFact, successor: JavaStatement): Set[TaintFact] = { + val callStmt = JavaIFDSProblem.asCall(call.stmt) + // val allParams = callStmt.allParams + val allParamsWithIndex = callStmt.allParams.zipWithIndex + + // if (!invokesScriptFunction(callStmt)) { + in match { + case BindingFact(index, _) => + if (JavaIFDSProblem.getParameterIndex(allParamsWithIndex, index) == NO_MATCH) + Set(in) + else + Set() + case _ => super.callToReturnFlow(call, in, successor) + } + /* } else { + in match { + /* Call to invokeFunction. The variable length parameter list is an array in TACAI. */ + case arrIn: ArrayElement if callStmt.name == "invokeFunction" + && JavaIFDSProblem.getParameterIndex(allParamsWithIndex, arrIn.index) == -3 => + val fNames = getPossibleStrings(call.method, allParams(1).asVar.definedBy) + fNames.map(fName => + if (fName == "") + /* Function name is unknown. We don't know what to call */ + None + else + {}//Some(jsAnalysis.analyze(call, arrIn, fName))).filter(_.isDefined).flatMap(_.get) ++ Set(in) + /* Call to eval. */ + case f: BindingFact if callStmt.name == "eval" + && (JavaIFDSProblem.getParameterIndex(allParamsWithIndex, f.index) == -1 + || JavaIFDSProblem.getParameterIndex(allParamsWithIndex, f.index) == -3) => + {}//jsAnalysis.analyze(call, f) + case f: WildcardBindingFact if callStmt.name == "eval" + && (JavaIFDSProblem.getParameterIndex(allParamsWithIndex, f.index) == -1 + || JavaIFDSProblem.getParameterIndex(allParamsWithIndex, f.index) == -3) => + {}//jsAnalysis.analyze(call, f) + /* Put obj in Binding */ + case Variable(index) if callStmt.name == "put" && JavaIFDSProblem.getParameterIndex(allParamsWithIndex, index) == -3 => + val keyNames = getPossibleStrings(call.method, allParams(1).asVar.definedBy) + val defSites = callStmt.receiverOption.get.asVar.definedBy + keyNames.flatMap(keyName => defSites.map(i => if (keyName == "") WildcardBindingFact(i) else BindingFact(i, keyName))) ++ Set(in) + /* putAll BindingFact to other BindingFact */ + case BindingFact(index, keyName) if callStmt.name == "putAll" && JavaIFDSProblem.getParameterIndex(allParamsWithIndex, index) == -2 => + callStmt.receiverOption.get.asVar.definedBy.map(i => if (keyName == "") WildcardBindingFact(i) else BindingFact(i, keyName)) ++ Set(in) + case WildcardBindingFact(index) if callStmt.name == "putAll" && JavaIFDSProblem.getParameterIndex(allParamsWithIndex, index) == -2 => + callStmt.receiverOption.get.asVar.definedBy.map(i => WildcardBindingFact(i)) ++ Set(in) + /* Overwrite BindingFact */ + case BindingFact(index, keyName) if callStmt.name == "put" && JavaIFDSProblem.getParameterIndex(allParamsWithIndex, index) == -1 => + val possibleFields = getPossibleStrings(call.method, allParams(1).asVar.definedBy) + if (possibleFields.size == 1 && possibleFields.contains(keyName)) + /* Key is definitely overwritten */ + Set() + else + Set(in) + case WildcardBindingFact(index) if callStmt.name == "put" && JavaIFDSProblem.getParameterIndex(allParamsWithIndex, index) == -1 => + /* We never overwrite here as we don't know the key */ + Set(in) + /* Remove BindingFact */ + case BindingFact(index, keyName) if callStmt.name == "remove" && JavaIFDSProblem.getParameterIndex(allParamsWithIndex, index) == -1 => + val possibleFields = getPossibleStrings(call.method, allParams(1).asVar.definedBy) + if (possibleFields.size == 1 && possibleFields.contains(keyName)) + Set() + else + Set(in) + case WildcardBindingFact(index) if callStmt.name == "remove" && JavaIFDSProblem.getParameterIndex(allParamsWithIndex, index) == -1 => + /* We never kill here as we don't know the key */ + Set(in) + /* get from BindingFact */ + case BindingFact(index, keyName) if callStmt.name == "get" && JavaIFDSProblem.getParameterIndex(allParamsWithIndex, index) == -1 => + val possibleFields = getPossibleStrings(call.method, allParams(1).asVar.definedBy) + if (possibleFields.size == 1 && possibleFields.contains(keyName)) + Set(Variable(call.index), in) + else + Set(in) + case WildcardBindingFact(index) if callStmt.name == "get" && JavaIFDSProblem.getParameterIndex(allParamsWithIndex, index) == -1 => + Set(Variable(call.index), in) + case _ => Set(in) + } + } */ + } + + override def isTainted(expression: Expr[V], in: TaintFact): Boolean = { + val definedBy = expression.asVar.definedBy + expression.isVar && (in match { + case BindingFact(index, _) => definedBy.contains(index) + case _ => super.isTainted(expression, in) + }) + } +} diff --git a/OPAL/js/src/main/scala/org/opalj/js/JavaScriptSource.scala b/OPAL/js/src/main/scala/org/opalj/js/JavaScriptSource.scala new file mode 100644 index 0000000000..128e6a1c48 --- /dev/null +++ b/OPAL/js/src/main/scala/org/opalj/js/JavaScriptSource.scala @@ -0,0 +1,45 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.js + +import java.io.{File, FileOutputStream} +import java.nio.file.Files + +/** + * Common trait for representing JavaScript sources embedded in Java source code. + */ +sealed trait JavaScriptSource { + /** + * Return the JavaScript source code as a string. + * @return JavaScript source + */ + def asString: String + + /** + * + * @param codeBefore Code to be added in front of the script. + * @param codeAfter Code to be added at the end of the script. + * @return file handler + */ + def asFile(codeBefore: String, codeAfter: String): File +} + +case class JavaScriptStringSource(source: String) extends JavaScriptSource { + lazy val tmpFile: File = Files.createTempFile("opal", ".js").toFile + + override def asString: String = source + + override def asFile(codeBefore: String, codeAfter: String): File = { + val code = codeBefore + source + codeAfter + val fos = new FileOutputStream(tmpFile) + fos.write(code.getBytes()) + fos.close() + tmpFile + } +} + +// TODO: Implement a way to read source files from disk and uncomment lines in LocalJSSourceFinder +case class JavaScriptFileSource(path: String) extends JavaScriptSource { + override def asString: String = throw new RuntimeException("Not implemented!") + + override def asFile(codeBefore: String, codeAfter: String): File = throw new RuntimeException("Not implemented!") +} diff --git a/OPAL/js/src/main/scala/org/opalj/js/LocalJSSourceFinder.scala b/OPAL/js/src/main/scala/org/opalj/js/LocalJSSourceFinder.scala new file mode 100644 index 0000000000..d50abb7ed8 --- /dev/null +++ b/OPAL/js/src/main/scala/org/opalj/js/LocalJSSourceFinder.scala @@ -0,0 +1,186 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.js + +import org.opalj.br.{Method, ObjectType} +import org.opalj.br.analyses.SomeProject +import org.opalj.collection.immutable.IntTrieSet +import org.opalj.tac.{AITACode, ASTNode, Assignment, Call, ComputeTACAIKey, Expr, ExprStmt, MethodCall, Stmt, TACMethodParameter, TACode} +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V +import org.opalj.tac.fpcf.analyses.ifds.{JavaIFDSProblem, JavaStatement} +import org.opalj.value.ValueInformation + +import scala.collection.mutable + +/** + * Uses the def-use information of TACAI to find the javascript sources passed + * to an ScriptEngine within method boundaries. + * + * @param p Project + */ +class LocalJSSourceFinder(val p: SomeProject) extends (JavaStatement => Set[JavaScriptSource]) { + final type TACAICode = TACode[TACMethodParameter, JavaIFDSProblem.V] + val tacaiKey: Method => AITACode[TACMethodParameter, ValueInformation] = p.get(ComputeTACAIKey) + + /* Maps a statement defining a JavaScript source to the instance. */ + val statementToJavaScriptSource: mutable.Map[Stmt[V], JavaScriptSource] = mutable.Map() + /* Maps the statement to search from to the resulting set of JavaScriptSources. */ + val jsSourceCache: mutable.Map[(JavaStatement, Expr[V]), Set[JavaScriptSource]] = mutable.Map() + + /** + * @see [[LocalJSSourceFinder.findJSSourceOnInvokeFunction()]] + */ + override def apply(stmt: JavaStatement): Set[JavaScriptSource] = findJSSourceOnInvokeFunction(stmt, JavaIFDSProblem.asCall(stmt.stmt).allParams.head.asVar) + + /** + * In case of a snippet like this: + * 1 ScriptEngine se = ...; + * 2 se.eval("JS Code"); + * 3 ((Invocable) se).invokeFunction("myFunction", args...); + * This functions finds "JS Code" given "se" at se.invokeFunction(). + * + * @param javaStmt statement to start with + * @param arg ScriptEngine variable + * @return javascript source code + */ + private def findJSSourceOnInvokeFunction(javaStmt: JavaStatement, arg: Expr[V]): Set[JavaScriptSource] = { + val decls = findCallOnObject(javaStmt.method, arg.asVar.definedBy, "getEngineByName") + + val maybeCached = jsSourceCache.get((javaStmt, arg)) + if (maybeCached.isDefined) + maybeCached.get + else + decls.flatMap(decl => { + val evals = findCallOnObject( + javaStmt.method, + decl.asAssignment.targetVar.usedBy, + "eval" + ) + val jsSources = evals.flatMap(eval => { + val evalCall = JavaIFDSProblem.asCall(eval) + varToJavaScriptSource(javaStmt.method, evalCall.params.head.asVar) + }) + jsSourceCache += (javaStmt, arg) -> jsSources + jsSources + }) + } + + /** + * Finds all definiton/use sites inside the method. + * + * @param method method to be searched in + * @param sites definition or use sites + * @return sites as JavaStatement + */ + private def searchStmts(method: Method, sites: IntTrieSet): Set[Stmt[JavaIFDSProblem.V]] = { + val taCode = tacaiKey(method) + sites.map(site => taCode.stmts.apply(site)) + } + + /** + * If stmt is a call, return it as a FunctionCall + * + * @param stmt Statement + * @return maybe a function call + */ + private def maybeCall(stmt: Stmt[JavaIFDSProblem.V]): Option[Call[JavaIFDSProblem.V]] = { + def isCall(node: ASTNode[JavaIFDSProblem.V]) = node match { + case expr: Expr[JavaIFDSProblem.V] => expr.isVirtualFunctionCall || expr.isStaticFunctionCall + case stmt: Stmt[JavaIFDSProblem.V] => (stmt.isNonVirtualMethodCall + || stmt.isVirtualMethodCall + || stmt.isStaticMethodCall) + } + + stmt match { + case exprStmt: ExprStmt[JavaIFDSProblem.V] if isCall(exprStmt.expr) => + Some(exprStmt.expr.asFunctionCall) + case assignStmt: Assignment[JavaIFDSProblem.V] if isCall(assignStmt.expr) => + Some(assignStmt.expr.asFunctionCall) + case call: MethodCall[JavaIFDSProblem.V] if isCall(stmt) => + Some(call) + case _ => None + } + } + + /** + * Finds instance method calls. + * + * @param method Method to search in. + * @param sites def/use sites + * @param methodName searched method name as string + * @return + */ + private def findCallOnObject(method: Method, sites: IntTrieSet, methodName: String): Set[Stmt[V]] = { + val stmts = searchStmts(method, sites) + stmts.map(stmt => maybeCall(stmt) match { + case Some(call) if call.name.equals(methodName) => Some(stmt) + case _ => None + }).filter(_.isDefined).map(_.get) + } + + /** + * Tries to resolve a variable either to a string constant or a file path containing the variable's value + * + * @param method method to be searched in + * @param variable variable of interest + * @return JavaScriptSource + */ + private def varToJavaScriptSource(method: Method, variable: JavaIFDSProblem.V): Set[JavaScriptSource] = { + val resultSet: mutable.Set[JavaScriptSource] = mutable.Set() + + // TODO: use a string analysis instead of 'Assignment with a.expr.isStringConst' here + + def findFileArg(sites: IntTrieSet): Unit = { + val calls = findCallOnObject(method, sites, "") + calls.foreach(init => { + val defSitesOfFileSrc = init.asInstanceMethodCall.params.head.asVar.definedBy + val defs = searchStmts(method, defSitesOfFileSrc) + defs.foreach { + /* new File("path/to/src"); */ + case a: Assignment[JavaIFDSProblem.V] if a.expr.isStringConst => + // resultSet.add(statementToJavaScriptSource.getOrElseUpdate(a, + // JavaScriptFileSource(a.expr.asStringConst.value))) + /* File constructor argument is no string constant */ + case _ => + } + }) + } + + def findFileReaderArg(sites: IntTrieSet): Unit = { + val calls = findCallOnObject(method, sites, "") + calls.foreach(init => { + val defSitesOfFileReaderSrc = init.asInstanceMethodCall.params.head.asVar.definedBy + val defs = searchStmts(method, defSitesOfFileReaderSrc) + defs.foreach { + /* FileReader fr = new FileReader(new File("path/to/src")); */ + case a: Assignment[JavaIFDSProblem.V] if a.expr.isStringConst => + // resultSet.add(statementToJavaScriptSource.getOrElseUpdate(a, + // JavaScriptFileSource(a.expr.asStringConst.value))) + /* new FileReader(new File(...)); */ + case a: Assignment[JavaIFDSProblem.V] if a.expr.isNew => + if (a.expr.asNew.tpe.isSubtypeOf(ObjectType("java/io/File"))(p.classHierarchy)) + findFileArg(a.targetVar.usedBy) + // Unknown argument + case _ => + } + }) + } + + val nextJStmts = searchStmts(method, variable.definedBy) + nextJStmts.foreach { + /* se.eval("function() ..."); */ + case a: Assignment[JavaIFDSProblem.V] if a.expr.isStringConst => + resultSet.add(statementToJavaScriptSource.getOrElseUpdate(a, JavaScriptStringSource(a.expr.asStringConst.value))) + case a: Assignment[JavaIFDSProblem.V] if a.expr.isNew => + val tpe: ObjectType = a.expr.asNew.tpe + /* se.eval(new FileReader(...)); */ + if (tpe.isSubtypeOf(ObjectType("java/io/FileReader"))(p.classHierarchy)) + findFileReaderArg(a.targetVar.usedBy) + /* se.eval(new File(...)); */ + else if (tpe.isSubtypeOf(ObjectType("java/io/File"))(p.classHierarchy)) + findFileArg(a.targetVar.usedBy) + case _ => + } + + resultSet.toSet + } +} diff --git a/OPAL/ll/Readme.md b/OPAL/ll/Readme.md new file mode 100644 index 0000000000..c48c1c83aa --- /dev/null +++ b/OPAL/ll/Readme.md @@ -0,0 +1,4 @@ +# Overview +The ***LLVM*** (LL) module provides tools to work with the llvm compiler infrastructure. + + diff --git a/OPAL/ll/build.sbt b/OPAL/ll/build.sbt new file mode 100644 index 0000000000..b511e98651 --- /dev/null +++ b/OPAL/ll/build.sbt @@ -0,0 +1 @@ +// build settings reside in the opal root build.sbt file diff --git a/OPAL/ll/src/main/resources/reference.conf b/OPAL/ll/src/main/resources/reference.conf new file mode 100644 index 0000000000..d1459a04b7 --- /dev/null +++ b/OPAL/ll/src/main/resources/reference.conf @@ -0,0 +1,5 @@ +org.opalj { + ifds { + debug = false, + } +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/LLVMProject.scala b/OPAL/ll/src/main/scala/org/opalj/ll/LLVMProject.scala new file mode 100644 index 0000000000..f299f70a5b --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/LLVMProject.scala @@ -0,0 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll + +import org.opalj.ll.llvm.{Module, Reader, value} + +class LLVMProject(val modules: Iterable[Module]) { + def functions: Iterable[value.Function] = + modules.flatMap(module => module.functions) + + def function(name: String): Option[value.Function] = + functions.find(_.name == name) +} + +object LLVMProject { + def apply(modules_paths: Iterable[String]): LLVMProject = { + val modules = modules_paths.map(path => Reader.readIR(path).get) + val project = new LLVMProject(modules) + project + } +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/LLVMProjectKey.scala b/OPAL/ll/src/main/scala/org/opalj/ll/LLVMProjectKey.scala new file mode 100644 index 0000000000..d4a344d585 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/LLVMProjectKey.scala @@ -0,0 +1,14 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package ll + +import org.opalj.br.analyses.{ProjectInformationKey, SomeProject} + +object LLVMProjectKey extends ProjectInformationKey[LLVMProject, Iterable[String]] { + override def requirements(project: SomeProject): Seq[ProjectInformationKey[Nothing, Nothing]] = Nil + + override def compute(project: SomeProject): LLVMProject = { + LLVMProject(project.getOrCreateProjectInformationKeyInitializationData(this, Iterable.empty)) + } + +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/SimplePurityAnalysis.scala b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/SimplePurityAnalysis.scala new file mode 100644 index 0000000000..07e430b044 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/SimplePurityAnalysis.scala @@ -0,0 +1,87 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.fpcf.analyses + +import org.opalj.br.analyses.{ProjectInformationKeys, SomeProject} +import org.opalj.br.fpcf.{BasicFPCFEagerAnalysisScheduler, FPCFAnalysis, FPCFAnalysisScheduler} +import org.opalj.fpcf._ +import org.opalj.ll.LLVMProjectKey +import org.opalj.ll.llvm.value.{Function, GlobalVariable, Store} + +sealed trait SimplePurityPropertyMetaInformation extends PropertyMetaInformation { + final type Self = SimplePurity +} + +sealed trait SimplePurity extends SimplePurityPropertyMetaInformation with OrderedProperty { + def meet(other: SimplePurity): SimplePurity = { + (this, other) match { + case (Pure, Pure) => Pure + case (_, _) => Impure + } + } + + override def checkIsEqualOrBetterThan(e: Entity, other: SimplePurity): Unit = { + if (meet(other) != other) { + throw new IllegalArgumentException(s"$e: impossible refinement: $other => $this") + } + } + + final def key: PropertyKey[SimplePurity] = SimplePurity.key +} + +case object Pure extends SimplePurity + +case object Impure extends SimplePurity + +object SimplePurity extends SimplePurityPropertyMetaInformation { + final val key: PropertyKey[SimplePurity] = PropertyKey.create( + "SimplePurity", + Impure + ) +} + +class SimplePurityAnalysis(val project: SomeProject) extends FPCFAnalysis { + def analyzeSimplePurity(function: Function): ProperPropertyComputationResult = { + function + .basicBlocks + .flatMap(_.instructions) + .foreach { + case instruction: Store => + instruction.dst match { + case _: GlobalVariable => + return Result(function, Impure) + case _ => () + } + case _ => () + } + Result(function, Pure) + } +} + +trait SimplePurityAnalysisScheduler extends FPCFAnalysisScheduler { + def derivedProperty: PropertyBounds = PropertyBounds.ub(SimplePurity) + + override def requiredProjectInformation: ProjectInformationKeys = Seq(LLVMProjectKey) + + override def uses: Set[PropertyBounds] = Set.empty // TODO: check this later +} + +object EagerSimplePurityAnalysis + extends SimplePurityAnalysisScheduler + with BasicFPCFEagerAnalysisScheduler { + override def derivesEagerly: Set[PropertyBounds] = Set(derivedProperty) + + override def derivesCollaboratively: Set[PropertyBounds] = Set.empty + + override def start( + project: SomeProject, + propertyStore: PropertyStore, + initData: InitializationData + ): FPCFAnalysis = { + val analysis = new SimplePurityAnalysis(project) + val llvm_project = project.get(LLVMProjectKey) + propertyStore.scheduleEagerComputationsForEntities(llvm_project.functions)( + analysis.analyzeSimplePurity + ) + analysis + } +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/Callable.scala b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/Callable.scala new file mode 100644 index 0000000000..d851bc3685 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/Callable.scala @@ -0,0 +1,19 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.fpcf.analyses.ifds +import org.opalj.br.Method +import org.opalj.ifds.Callable +import org.opalj.ll.llvm.value.Function + +abstract class NativeFunction extends Callable { + def name: String +} + +case class LLVMFunction(function: Function) extends NativeFunction { + override def name: String = function.name + override def signature: String = function.name // TODO: add signature +} + +case class JNIMethod(method: Method) extends NativeFunction { + override def name: String = method.name + override def signature: String = method.toString +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/JNICallUtil.scala b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/JNICallUtil.scala new file mode 100644 index 0000000000..ded110d5fb --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/JNICallUtil.scala @@ -0,0 +1,100 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.fpcf.analyses.ifds + +import org.opalj.br.analyses.DeclaredMethods +import org.opalj.ll.llvm.value.constant.{ConstantDataArray, GetElementPtrConst} +import org.opalj.ll.llvm.value.{Argument, Call, GetElementPtr, GlobalVariable, Load, Store, Value} +import org.opalj.ll.llvm.{PointerType, StructType} + +object JNICallUtil { + + /** + * Checks whether the call is a call to the JNI interface. + * This is done by the assumption that every such calls first parameter is a struct of type "struct.JNINativeInterface_" + */ + def isJNICall(call: Call): Boolean = call.calledFunctionType.params.headOption match { + case Some(firstParam) => + firstParam match { + case p1: PointerType => + p1.element match { + case p2: PointerType => + p2.element match { + case struct: StructType if struct.name == "struct.JNINativeInterface_" => + true + case other => false + } + case _ => false + } + case _ => false + } + case _ => false + } + + def resolve(call: Call)(implicit declaredMethods: DeclaredMethods): Set[_ <: NativeFunction] = resolveJNIFunction(call) match { + case Symbol("CallTypeMethod") => resolveMethodId(call.operand(2)) // methodID is the third parameter + case _ => Set() + } + + private def resolveJNIFunction(call: Call): Symbol = call.calledValue match { + case load: Load => + load.src match { + // https://docs.oracle.com/en/java/javase/13/docs/specs/jni/functions.html has the indices + case gep: GetElementPtr if gep.isConstant => gep.constants.tail.head match { + case 31 => Symbol("GetObjectClass") + case 33 => Symbol("GetMethodId") + case 49 | 61 => Symbol("CallTypeMethod") // CallIntMethod | CallVoidMethod + case index => throw new IllegalArgumentException(s"unknown JNI function index ${index}") + } + case _ => throw new IllegalArgumentException("unknown JNI load src") + } + case _ => throw new IllegalArgumentException("unknown JNI call argument") + } + + private def resolveMethodId(methodId: Value)(implicit declaredMethods: DeclaredMethods): Set[JNIMethod] = { + val sources = methodId.asInstanceOf[Load].src.users.toSeq.filter(_.isInstanceOf[Store]).map(_.asInstanceOf[Store].src) + sources.filter(_.isInstanceOf[Call]).map(_.asInstanceOf[Call]).map(call => { + if (resolveJNIFunction(call) != Symbol("GetMethodId")) throw new IllegalArgumentException("unexpected call") + if (!resolveClassIsThis(call.operand(1))) // class is the second parameter + throw new IllegalArgumentException("unexpected class argument") + + val functionName = call.function.name + if (!functionName.startsWith("Java_")) { + throw new IllegalArgumentException("unexpected function name") + } + val className = call.function.name.substring(5).split("_").head + val name = resolveString(call.operand(2)) // name is the third parameter + val signature = resolveString(call.operand(3)) // signature is the third parameter + findJavaMethods(className, name, signature) + }).flatten.toSet + } + + private def resolveString(name: Value): String = name match { + case gep: GetElementPtrConst => gep.base match { + case global: GlobalVariable => global.initializer match { + case stringData: ConstantDataArray => stringData.asString + } + } + } + + private def resolveClassIsThis(clazz: Value): Boolean = { + val sources = clazz.asInstanceOf[Load].src.users.toSeq.filter(_.isInstanceOf[Store]).map(_.asInstanceOf[Store].src) + sources.filter(_.isInstanceOf[Call]).map(_.asInstanceOf[Call]).forall(call => { + if (resolveJNIFunction(call) != Symbol("GetObjectClass")) throw new IllegalArgumentException("unexpected call") + resolveObjectIsThis(call.operand(1)) // object is the second parameter + }) + } + + private def resolveObjectIsThis(obj: Value): Boolean = { + val sources = obj.asInstanceOf[Load].src.users.toSeq.filter(_.isInstanceOf[Store]).map(_.asInstanceOf[Store].src) + sources.forall(_.isInstanceOf[Argument]) && sources.forall(_.asInstanceOf[Argument].index == 1) + } + + private def findJavaMethods(className: String, name: String, signature: String)(implicit declaredMethods: DeclaredMethods): Set[JNIMethod] = { + declaredMethods.declaredMethods.filter(declaredMethod => { + val classType = declaredMethod.declaringClassType + (classType.simpleName == className && + declaredMethod.name == name && + declaredMethod.descriptor.toJVMDescriptor == signature) + }).map(_.definedMethod).map(JNIMethod(_)).toSet + } +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/NativeForwardICFG.scala b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/NativeForwardICFG.scala new file mode 100644 index 0000000000..f3717520cd --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/NativeForwardICFG.scala @@ -0,0 +1,61 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.fpcf.analyses.ifds + +import org.opalj.br.analyses.{DeclaredMethodsKey, SomeProject} +import org.opalj.ifds.ICFG +import org.opalj.ll.llvm.value.{Call, Function, Instruction, Ret, Terminator} + +class NativeForwardICFG(project: SomeProject) extends ICFG[NativeFunction, LLVMStatement] { + implicit val declaredMethods = project.get(DeclaredMethodsKey) + + /** + * Determines the statements at which the analysis starts. + * + * @param callable The analyzed callable. + * @return The statements at which the analysis starts. + */ + override def startStatements(callable: NativeFunction): Set[LLVMStatement] = callable match { + case LLVMFunction(function) => { + if (function.basicBlockCount == 0) + throw new IllegalArgumentException(s"${callable} does not contain any basic blocks and likely should not be in scope of the analysis") + Set(LLVMStatement(function.entryBlock.firstInstruction)) + } + } + + /** + * Determines the statement, that will be analyzed after some other `statement`. + * + * @param statement The source statement. + * @return The successor statements + */ + override def nextStatements(statement: LLVMStatement): Set[LLVMStatement] = { + if (!statement.instruction.isTerminator) return Set(LLVMStatement(statement.instruction.next.get)) + statement.instruction.asInstanceOf[Instruction with Terminator].successors.map(LLVMStatement(_)).toSet + } + + /** + * Gets the set of all methods possibly called at some statement. + * + * @param statement The statement. + * @return All callables possibly called at the statement or None, if the statement does not + * contain a call. + */ + override def getCalleesIfCallStatement(statement: LLVMStatement): Option[collection.Set[_ <: NativeFunction]] = { + statement.instruction match { + case call: Call => Some(resolveCallee(call)) + case _ => None + } + } + + override def isExitStatement(statement: LLVMStatement): Boolean = statement.instruction match { + case Ret(_) => true + case _ => false + } + + private def resolveCallee(call: Call): Set[_ <: NativeFunction] = + if (call.calledValue.isInstanceOf[Function]) + Set(LLVMFunction(call.calledValue.asInstanceOf[Function])) + else if (JNICallUtil.isJNICall(call)) + JNICallUtil.resolve(call) + else Set() +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/NativeIFDSAnalysis.scala b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/NativeIFDSAnalysis.scala new file mode 100644 index 0000000000..264f0553d4 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/NativeIFDSAnalysis.scala @@ -0,0 +1,24 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ + +package org.opalj.ll.fpcf.analyses.ifds + +import org.opalj.br.analyses.{ProjectInformationKeys, SomeProject} +import org.opalj.ifds.{AbstractIFDSFact, IFDSAnalysis, IFDSAnalysisScheduler, IFDSProblem, IFDSPropertyMetaInformation} +import org.opalj.ll.LLVMProjectKey + +/** + * + * @param ifdsProblem + * @param propertyKey Provides the concrete property key that must be unique for every distinct concrete analysis and the lower bound for the IFDSProperty. + * @tparam IFDSFact + */ +class NativeIFDSAnalysis[IFDSFact <: AbstractIFDSFact]( + project: SomeProject, + ifdsProblem: IFDSProblem[IFDSFact, NativeFunction, LLVMStatement], + propertyKey: IFDSPropertyMetaInformation[LLVMStatement, IFDSFact] +) + extends IFDSAnalysis[IFDSFact, NativeFunction, LLVMStatement]()(project, ifdsProblem, propertyKey) + +abstract class NativeIFDSAnalysisScheduler[IFDSFact <: AbstractIFDSFact] extends IFDSAnalysisScheduler[IFDSFact, NativeFunction, LLVMStatement] { + override def requiredProjectInformation: ProjectInformationKeys = Seq(LLVMProjectKey) +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/NativeIFDSProblem.scala b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/NativeIFDSProblem.scala new file mode 100644 index 0000000000..2cf906c9f1 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/NativeIFDSProblem.scala @@ -0,0 +1,74 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.fpcf.analyses.ifds + +import org.opalj.br.Method +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.fpcf.{EOptionP, FinalEP, InterimEUBP, Property, PropertyKey, PropertyStore} +import org.opalj.ifds.Dependees.Getter +import org.opalj.ifds.{AbstractIFDSFact, IFDSProblem, IFDSProperty} +import org.opalj.ll.LLVMProjectKey +import org.opalj.tac.fpcf.analyses.ifds.JavaStatement + +abstract class NativeIFDSProblem[Fact <: AbstractIFDSFact, JavaFact <: AbstractIFDSFact](project: SomeProject) extends IFDSProblem[Fact, NativeFunction, LLVMStatement](new NativeForwardICFG(project)) { + final implicit val propertyStore: PropertyStore = project.get(PropertyStoreKey) + val llvmProject = project.get(LLVMProjectKey) + val javaPropertyKey: PropertyKey[Property] + + override def outsideAnalysisContext(callee: NativeFunction): Option[(LLVMStatement, LLVMStatement, Fact, Getter) => Set[Fact]] = callee match { + case LLVMFunction(function) => + function.basicBlockCount match { + case 0 => Some((_: LLVMStatement, _: LLVMStatement, in: Fact, _: Getter) => Set(in)) + case _ => None + } + case JNIMethod(method) => Some(handleJavaMethod(method)) + } + + private def handleJavaMethod(callee: Method)(call: LLVMStatement, successor: LLVMStatement, in: Fact, dependeesGetter: Getter): Set[Fact] = { + var result = Set.empty[Fact] + val entryFacts = javaCallFlow(call, callee, in) + for (entryFact <- entryFacts) { // ifds line 14 + val e = (callee, entryFact) + val exitFacts: Map[JavaStatement, Set[JavaFact]] = + dependeesGetter(e, javaPropertyKey).asInstanceOf[EOptionP[(JavaStatement, JavaFact), IFDSProperty[JavaStatement, JavaFact]]] match { + case ep: FinalEP[_, IFDSProperty[JavaStatement, JavaFact]] => + ep.p.flows + case ep: InterimEUBP[_, IFDSProperty[JavaStatement, JavaFact]] => + ep.ub.flows + case _ => + Map.empty + } + for { + (exitStatement, exitStatementFacts) <- exitFacts // ifds line 15.2 + exitStatementFact <- exitStatementFacts // ifds line 15.3 + } { + result ++= javaReturnFlow(exitStatement, exitStatementFact, call, in, successor) + } + } + result + } + + /** + * Computes the data flow for a call to start edge. + * + * @param call The analyzed call statement. + * @param callee The called method, for which the data flow shall be computed. + * @param in The fact which holds before the execution of the `call`. + * @param source The entity, which is analyzed. + * @return The facts, which hold after the execution of `statement` under the assumption that + * the facts in `in` held before `statement` and `statement` calls `callee`. + */ + protected def javaCallFlow( + call: LLVMStatement, + callee: Method, + in: Fact + ): Set[JavaFact] + + protected def javaReturnFlow( + exit: JavaStatement, + in: JavaFact, + call: LLVMStatement, + callFact: Fact, + successor: LLVMStatement + ): Set[Fact] +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/Statement.scala b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/Statement.scala new file mode 100644 index 0000000000..67bc8c2a3f --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/Statement.scala @@ -0,0 +1,18 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.fpcf.analyses.ifds + +import org.opalj.ifds.Statement +import org.opalj.ll.llvm.value.{BasicBlock, Instruction} + +/** + * A statement that is passed to the concrete analysis. + * + * @param instruction The LLVM instruction. + */ +case class LLVMStatement(instruction: Instruction) extends Statement[LLVMFunction, BasicBlock] { + def function: LLVMFunction = LLVMFunction(instruction.function) + def basicBlock: BasicBlock = instruction.parent + override def node: BasicBlock = basicBlock + override def callable: LLVMFunction = function + override def toString: String = s"${function.name}\n\t${instruction}\n\t${function}" +} \ No newline at end of file diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/taint/JavaForwardTaintAnalysis.scala b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/taint/JavaForwardTaintAnalysis.scala new file mode 100644 index 0000000000..9481f866f5 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/taint/JavaForwardTaintAnalysis.scala @@ -0,0 +1,246 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.fpcf.analyses.ifds.taint + +import org.opalj.br.Method +import org.opalj.br.analyses.{ProjectInformationKeys, SomeProject} +import org.opalj.fpcf._ +import org.opalj.ifds.Dependees.Getter +import org.opalj.ifds.{IFDSAnalysis, IFDSAnalysisScheduler, IFDSProperty, IFDSPropertyMetaInformation} +import org.opalj.ll.LLVMProjectKey +import org.opalj.ll.fpcf.analyses.ifds.{LLVMFunction, LLVMStatement} +import org.opalj.ll.fpcf.properties.NativeTaint +import org.opalj.ll.llvm.value.Ret +import org.opalj.tac.Assignment +import org.opalj.tac.fpcf.analyses.ifds.taint._ +import org.opalj.tac.fpcf.analyses.ifds.{JavaIFDSProblem, JavaMethod, JavaStatement} +import org.opalj.tac.fpcf.properties._ + +class SimpleJavaForwardTaintProblem(p: SomeProject) extends ForwardTaintProblem(p) { + val llvmProject = p.get(LLVMProjectKey) + + /** + * The analysis starts with all public methods in TaintAnalysisTestClass. + */ + override val entryPoints: Seq[(Method, TaintFact)] = for { + m <- p.allMethodsWithBody + } yield m -> TaintNullFact + + /** + * The sanitize method is a sanitizer. + */ + override protected def sanitizesReturnValue(callee: Method): Boolean = + callee.name == "sanitize" + + /** + * We do not sanitize parameters. + */ + override protected def sanitizesParameter(call: JavaStatement, in: TaintFact): Boolean = false + + /** + * Creates a new variable fact for the callee, if the source was called. + */ + override protected def createTaints(callee: Method, call: JavaStatement): Set[TaintFact] = + if (callee.name == "source") Set(Variable(call.index)) + else Set.empty + + /** + * Create a FlowFact, if sink is called with a tainted variable. + * Note, that sink does not accept array parameters. No need to handle them. + */ + override protected def createFlowFact( + callee: Method, + call: JavaStatement, + in: TaintFact + ): Option[FlowFact] = + if (callee.name == "sink" && in == Variable(-2)) + Some(FlowFact(Seq(JavaMethod(call.method), JavaMethod(callee)))) + else None + + // Multilingual additions here + override def outsideAnalysisContext(callee: Method): Option[OutsideAnalysisContextHandler] = { + def handleNativeMethod(call: JavaStatement, successor: JavaStatement, in: TaintFact, dependeesGetter: Getter): Set[TaintFact] = { + // https://docs.oracle.com/en/java/javase/13/docs/specs/jni/design.html#resolving-native-method-names + val calleeName = callee.name.map(c => c match { + case c if isAlphaNumeric(c) => c + case '_' => "_1" + case ';' => "_2" + case '[' => "_3" + case c => s"_${c.toInt.toHexString.reverse.padTo(4, '0').reverse}" + }).mkString + val nativeFunctionName = "Java_"+callee.classFile.fqn+"_"+calleeName + val function = LLVMFunction(llvmProject.function(nativeFunctionName).get) + var result = Set.empty[TaintFact] + val entryFacts = nativeCallFlow(call, function, in, callee) + for (entryFact <- entryFacts) { // ifds line 14 + val e = (function, entryFact) + val exitFacts: Map[LLVMStatement, Set[NativeTaintFact]] = + dependeesGetter(e, NativeTaint.key).asInstanceOf[EOptionP[(LLVMStatement, NativeTaintFact), IFDSProperty[LLVMStatement, NativeTaintFact]]] match { + case ep: FinalEP[_, IFDSProperty[LLVMStatement, NativeTaintFact]] => + ep.p.flows + case ep: InterimEUBP[_, IFDSProperty[LLVMStatement, NativeTaintFact]] => + ep.ub.flows + case _ => + Map.empty + } + for { + (exitStatement, exitStatementFacts) <- exitFacts // ifds line 15.2 + exitStatementFact <- exitStatementFacts // ifds line 15.3 + } { + result ++= nativeReturnFlow(exitStatement, exitStatementFact, call, in, callee, successor) + } + } + result + } + + if (callee.isNative) { + Some(handleNativeMethod _) + } else { + super.outsideAnalysisContext(callee) + } + } + + /** + * Computes the data flow for a call to start edge. + * + * @param call The analyzed call statement. + * @param callee The called method, for which the data flow shall be computed. + * @param in The fact which holds before the execution of the `call`. + * @param source The entity, which is analyzed. + * @return The facts, which hold after the execution of `statement` under the assumption that + * the facts in `in` held before `statement` and `statement` calls `callee`. + */ + private def nativeCallFlow( + call: JavaStatement, + callee: LLVMFunction, + in: TaintFact, + nativeCallee: Method + ): Set[NativeTaintFact] = { + val callObject = JavaIFDSProblem.asCall(call.stmt) + val allParams = callObject.allParams + val allParamsWithIndices = allParams.zipWithIndex + in match { + // Taint formal parameter if actual parameter is tainted + case Variable(index) => + allParamsWithIndices.flatMap { + case (param, paramIndex) if param.asVar.definedBy.contains(index) => + // TODO: this is passed + Some(NativeVariable(callee.function.argument(paramIndex + 1))) // offset JNIEnv + case _ => None // Nothing to do + }.toSet + + // Taint element of formal parameter if element of actual parameter is tainted + case ArrayElement(index, taintedIndex) => + allParamsWithIndices.flatMap { + case (param, paramIndex) if param.asVar.definedBy.contains(index) => + Some(NativeVariable(callee.function.argument(paramIndex + 1))) // offset JNIEnv + case _ => None // Nothing to do + }.toSet + + case InstanceField(index, declaredClass, taintedField) => + // Taint field of formal parameter if field of actual parameter is tainted + // Only if the formal parameter is of a type that may have that field! + allParamsWithIndices.flatMap { + case (param, paramIndex) if param.asVar.definedBy.contains(index) => + Some(JavaInstanceField(paramIndex + 1, declaredClass, taintedField)) // TODO subtype check + case _ => None // Nothing to do + }.toSet + + case StaticField(classType, fieldName) => Set(JavaStaticField(classType, fieldName)) + + case TaintNullFact => Set(NativeTaintNullFact) + + case _ => Set() // Nothing to do + + } + } + + /** + * Computes the data flow for an exit to return edge. + * + * @param call The statement, which called the `callee`. + * @param exit The statement, which terminated the `callee`. + * @param in The fact which holds before the execution of the `exit`. + * @return The facts, which hold after the execution of `exit` in the caller's context + * under the assumption that `in` held before the execution of `exit` and that + * `successor` will be executed next. + */ + private def nativeReturnFlow( + exit: LLVMStatement, + in: NativeTaintFact, + call: JavaStatement, + callFact: TaintFact, + nativeCallee: Method, + successor: JavaStatement + ): Set[TaintFact] = { + if (sanitizesReturnValue(nativeCallee)) return Set.empty + val callStatement = JavaIFDSProblem.asCall(call.stmt) + val allParams = callStatement.allParams + var flows: Set[TaintFact] = Set.empty + in match { + // Taint actual parameter if formal parameter is tainted + case JavaVariable(index) if index < 0 && index > -100 && JavaIFDSProblem.isRefTypeParam(nativeCallee, index) => + val param = allParams( + JavaIFDSProblem.switchParamAndVariableIndex(index, nativeCallee.isStatic) + ) + flows ++= param.asVar.definedBy.iterator.map(Variable) + + // Taint element of actual parameter if element of formal parameter is tainted + case JavaArrayElement(index, taintedIndex) if index < 0 && index > -100 => + val param = allParams( + JavaIFDSProblem.switchParamAndVariableIndex(index, nativeCallee.isStatic) + ) + flows ++= param.asVar.definedBy.iterator.map(ArrayElement(_, taintedIndex)) + + case JavaInstanceField(index, declClass, taintedField) if index < 0 && index > -10 => + // Taint field of actual parameter if field of formal parameter is tainted + val param = + allParams(JavaIFDSProblem.switchParamAndVariableIndex(index, nativeCallee.isStatic)) + param.asVar.definedBy.foreach { defSite => + flows += InstanceField(defSite, declClass, taintedField) + } + + case JavaStaticField(objectType, fieldName) => flows += StaticField(objectType, fieldName) + + // Track the call chain to the sink back + case NativeFlowFact(flow) if !flow.contains(JavaMethod(call.method)) => + flows += FlowFact(JavaMethod(call.method) +: flow) + case NativeTaintNullFact => flows += TaintNullFact + case _ => + } + + // Propagate taints of the return value + exit.instruction match { + case ret: Ret => { + in match { + case NativeVariable(value) if value == ret.value && call.stmt.astID == Assignment.ASTID => + flows += Variable(call.index) + // TODO + /*case ArrayElement(index, taintedIndex) if returnValueDefinedBy.contains(index) => + flows += ArrayElement(call.index, taintedIndex) + case InstanceField(index, declClass, taintedField) if returnValueDefinedBy.contains(index) => + flows += InstanceField(call.index, declClass, taintedField)*/ + case NativeTaintNullFact => + val taints = createTaints(nativeCallee, call) + if (taints.nonEmpty) flows ++= taints + case _ => // Nothing to do + } + } + case _ => + } + flows + } + + private def isAlphaNumeric(char: Char): Boolean = { + char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z' || char >= '0' && char <= '9' + } +} + +class SimpleJavaForwardTaintAnalysis(project: SomeProject) + extends IFDSAnalysis()(project, new SimpleJavaForwardTaintProblem(project), Taint) + +object JavaForwardTaintAnalysisScheduler extends IFDSAnalysisScheduler[TaintFact, Method, JavaStatement] { + override def init(p: SomeProject, ps: PropertyStore) = new SimpleJavaForwardTaintAnalysis(p) + override def property: IFDSPropertyMetaInformation[JavaStatement, TaintFact] = Taint + override def requiredProjectInformation: ProjectInformationKeys = Seq(LLVMProjectKey) + override val uses: Set[PropertyBounds] = Set(PropertyBounds.finalP(TACAI), PropertyBounds.ub(NativeTaint)) +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/taint/NativeForwardTaintAnalysis.scala b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/taint/NativeForwardTaintAnalysis.scala new file mode 100644 index 0000000000..cddb1c024c --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/taint/NativeForwardTaintAnalysis.scala @@ -0,0 +1,57 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.fpcf.analyses.ifds.taint + +import org.opalj.br.analyses.SomeProject +import org.opalj.fpcf.{PropertyBounds, PropertyKey, PropertyStore} +import org.opalj.ifds.IFDSPropertyMetaInformation +import org.opalj.ll.fpcf.analyses.ifds.{LLVMStatement, NativeFunction, NativeIFDSAnalysis, NativeIFDSAnalysisScheduler} +import org.opalj.ll.fpcf.properties.NativeTaint +import org.opalj.ll.llvm.value.Function +import org.opalj.tac.fpcf.properties.Taint + +class SimpleNativeForwardTaintProblem(p: SomeProject) extends NativeForwardTaintProblem(p) { + /** + * The analysis starts with all public methods in TaintAnalysisTestClass. + */ + override val entryPoints: Seq[(NativeFunction, NativeTaintFact)] = Seq.empty + override val javaPropertyKey: PropertyKey[Taint] = Taint.key + + /** + * The sanitize method is a sanitizer. + */ + override protected def sanitizesReturnValue(callee: NativeFunction): Boolean = + callee.name == "sanitize" + + /** + * We do not sanitize parameters. + */ + override protected def sanitizesParameter(call: LLVMStatement, in: NativeTaintFact): Boolean = false + + /** + * Creates a new variable fact for the callee, if the source was called. + */ + protected def createTaints(callee: Function, call: LLVMStatement): Set[NativeTaintFact] = + if (callee.name == "source") Set(NativeVariable(call.instruction)) + else Set.empty + + /** + * Create a FlowFact, if sink is called with a tainted variable. + * Note, that sink does not accept array parameters. No need to handle them. + */ + protected def createFlowFact( + callee: Function, + call: LLVMStatement, + in: Set[NativeTaintFact] + ): Option[NativeFlowFact] = + if (callee.name == "sink" && in.contains(JavaVariable(-2))) Some(NativeFlowFact(Seq(call.function))) + else None +} + +class SimpleNativeForwardTaintAnalysis(project: SomeProject) + extends NativeIFDSAnalysis(project, new SimpleNativeForwardTaintProblem(project), NativeTaint) + +object NativeForwardTaintAnalysisScheduler extends NativeIFDSAnalysisScheduler[NativeTaintFact] { + override def init(p: SomeProject, ps: PropertyStore) = new SimpleNativeForwardTaintAnalysis(p) + override def property: IFDSPropertyMetaInformation[LLVMStatement, NativeTaintFact] = NativeTaint + override val uses: Set[PropertyBounds] = Set() // ++ PropertyBounds.ub(Taint) TODO: we do not use the native taint yet +} \ No newline at end of file diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/taint/NativeForwardTaintProblem.scala b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/taint/NativeForwardTaintProblem.scala new file mode 100644 index 0000000000..199ecf1947 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/taint/NativeForwardTaintProblem.scala @@ -0,0 +1,215 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.fpcf.analyses.ifds.taint + +import org.opalj.br.Method +import org.opalj.br.analyses.SomeProject +import org.opalj.ll.fpcf.analyses.ifds.{LLVMFunction, LLVMStatement, NativeFunction, NativeIFDSProblem} +import org.opalj.ll.fpcf.analyses.ifds.JNIMethod +import org.opalj.ll.llvm.value.{Add, Alloca, BitCast, Call, GetElementPtr, Load, PHI, Ret, Store, Sub} + +import org.opalj.tac.fpcf.analyses.ifds.JavaStatement +import org.opalj.tac.fpcf.analyses.ifds.taint.TaintProblem +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem +import org.opalj.tac.fpcf.properties._ +import org.opalj.tac.ReturnValue + +abstract class NativeForwardTaintProblem(project: SomeProject) extends NativeIFDSProblem[NativeTaintFact, TaintFact](project) with TaintProblem[NativeFunction, LLVMStatement, NativeTaintFact] { + override def nullFact: NativeTaintFact = NativeTaintNullFact + + /** + * Computes the data flow for a normal statement. + * + * @param statement The analyzed statement. + * @param in The fact which holds before the execution of the `statement`. + * @param predecessor The predecessor of the analyzed `statement`, for which the data flow shall be + * computed. Used for phi statements to distinguish the flow. + * @return The facts, which hold after the execution of `statement` under the assumption + * that the facts in `in` held before `statement` and `successor` will be + * executed next. + */ + override def normalFlow(statement: LLVMStatement, in: NativeTaintFact, predecessor: Option[LLVMStatement]): Set[NativeTaintFact] = statement.instruction match { + case _: Alloca => Set(in) + case store: Store => in match { + case NativeVariable(value) if value == store.src => store.dst match { + case dst: Alloca => Set(in, NativeVariable(dst)) + case gep: GetElementPtr if gep.isConstant => Set(in, NativeArrayElement(gep.base, gep.constants)) + } + case NativeArrayElement(base, indices) if store.src == base => Set(in, NativeArrayElement(store.dst, indices)) + case NativeVariable(value) if value == store.dst => Set() + case _ => Set(in) + } + case load: Load => in match { + case NativeVariable(value) if value == load.src => Set(in, NativeVariable(load)) + case NativeArrayElement(base, indices) => load.src match { + case gep: GetElementPtr if gep.isConstant && gep.base == base && gep.constants == indices => Set(in, NativeVariable(load)) + case _ => Set(in, NativeArrayElement(load, indices)) + } + case _ => Set(in) + } + case add: Add => in match { + case NativeVariable(value) if value == add.op1 || value == add.op2 => Set(in, NativeVariable(add)) + case _ => Set(in) + } + case sub: Sub => in match { + case NativeVariable(value) if value == sub.op1 || value == sub.op2 => Set(in, NativeVariable(sub)) + case _ => Set(in) + } + case gep: GetElementPtr => in match { + case NativeVariable(value) if value == gep.base => Set(in, NativeVariable(gep)) + case NativeArrayElement(base, indices) if base == gep.base && gep.isZero => Set(in, NativeArrayElement(gep, indices)) + case _ => Set(in) + } + case bitcast: BitCast => in match { + case NativeVariable(value) if value == bitcast.operand(0) => Set(in, NativeVariable(bitcast)) + case _ => Set(in) + } + case _ => Set(in) + } + + /** + * Computes the data flow for a call to start edge. + * + * @param call The analyzed call statement. + * @param callee The called method, for which the data flow shall be computed. + * @param in The fact which holds before the execution of the `call`. + * @return The facts, which hold after the execution of `statement` under the assumption that + * the facts in `in` held before `statement` and `statement` calls `callee`. + */ + override def callFlow(call: LLVMStatement, callee: NativeFunction, in: NativeTaintFact): Set[NativeTaintFact] = callee match { + case LLVMFunction(callee) => + in match { + // Taint formal parameter if actual parameter is tainted + case NativeVariable(value) => call.instruction.asInstanceOf[Call].indexOfArgument(value) match { + case Some(index) => Set(NativeVariable(callee.argument(index))) + case None => Set() + } + // TODO pass other java taints + case NativeTaintNullFact => Set(in) + case NativeArrayElement(base, indices) => call.instruction.asInstanceOf[Call].indexOfArgument(base) match { + case Some(index) => Set(NativeArrayElement(callee.argument(index), indices)) + case None => Set() + } + case _ => Set() // Nothing to do + } + case _ => throw new RuntimeException("this case should be handled by outsideAnalysisContext") + } + + /** + * Computes the data flow for an exit to return edge. + * + * @param call The statement, which called the `callee`. + * @param exit The statement, which terminated the `callee`. + * @param in The fact which holds before the execution of the `exit`. + * @return The facts, which hold after the execution of `exit` in the caller's context + * under the assumption that `in` held before the execution of `exit` and that + * `successor` will be executed next. + */ + override def returnFlow(exit: LLVMStatement, in: NativeTaintFact, call: LLVMStatement, callFact: NativeTaintFact, successor: LLVMStatement): Set[NativeTaintFact] = { + val callee = exit.callable + var flows: Set[NativeTaintFact] = if (sanitizesReturnValue(callee)) Set.empty else in match { + case NativeVariable(value) => exit.instruction match { + case ret: Ret if ret.value == value => Set(NativeVariable(call.instruction)) + case _: Ret => Set() + case _ => Set() + } + case NativeTaintNullFact => Set(NativeTaintNullFact) + case NativeFlowFact(flow) if !flow.contains(call.function) => + Set(NativeFlowFact(call.function +: flow)) + case _ => Set() + } + if (exit.callable.name == "source") in match { + case NativeTaintNullFact => flows += NativeVariable(call.instruction) + } + if (exit.callable.name == "sink") in match { + case NativeVariable(value) if value == exit.callable.function.argument(0) => + flows += NativeFlowFact(Seq(call.callable, exit.callable)) + case _ => + } + flows + } + + /** + * Computes the data flow for a call to return edge. + * + * @param call The statement, which invoked the call. + * @param in The facts, which hold before the `call`. + * @return The facts, which hold after the call independently of what happens in the callee + * under the assumption that `in` held before `call`. + */ + override def callToReturnFlow(call: LLVMStatement, in: NativeTaintFact, successor: LLVMStatement): Set[NativeTaintFact] = Set(in) + + override def needsPredecessor(statement: LLVMStatement): Boolean = statement.instruction match { + case PHI(_) => true + case _ => false + } + + /** + * Computes the data flow for a call to start edge. + * + * @param call The analyzed call statement. + * @param callee The called method, for which the data flow shall be computed. + * @param in The fact which holds before the execution of the `call`. + * @param source The entity, which is analyzed. + * @return The facts, which hold after the execution of `statement` under the assumption that + * the facts in `in` held before `statement` and `statement` calls `callee`. + */ + override protected def javaCallFlow( + call: LLVMStatement, + callee: Method, + in: NativeTaintFact + ): Set[TaintFact] = + in match { + // Taint formal parameter if actual parameter is tainted + case NativeVariable(value) => call.instruction.asInstanceOf[Call].indexOfArgument(value) match { + case Some(index) => Set(Variable(JavaIFDSProblem.switchParamAndVariableIndex( + index - 2, + callee.isStatic + ))) + case None => Set() + } + // TODO pass other java taints + case NativeTaintNullFact => Set(TaintNullFact) + case _ => Set() // Nothing to do + } + + /** + * Computes the data flow for an exit to return edge. + * + * @param call The statement, which called the `callee`. + * @param exit The statement, which terminated the `callee`. + * @param in The fact which holds before the execution of the `exit`. + * @return The facts, which hold after the execution of `exit` in the caller's context + * under the assumption that `in` held before the execution of `exit` and that + * `successor` will be executed next. + */ + override protected def javaReturnFlow( + exit: JavaStatement, + in: TaintFact, + call: LLVMStatement, + callFact: NativeTaintFact, + successor: LLVMStatement + ): Set[NativeTaintFact] = { + val callee = exit.callable + if (sanitizesReturnValue(JNIMethod(callee))) return Set.empty + var flows: Set[NativeTaintFact] = Set.empty + in match { + case StaticField(classType, fieldName) => flows += JavaStaticField(classType, fieldName) + + // Track the call chain to the sink back + case FlowFact(flow) if !flow.contains(call.function) => + flows += NativeFlowFact(call.function +: flow) + case _ => + } + + // Propagate taints of the return value + if (exit.stmt.astID == ReturnValue.ASTID) { + val returnValueDefinedBy = exit.stmt.asReturnValue.expr.asVar.definedBy + in match { + case Variable(index) if returnValueDefinedBy.contains(index) => + flows += NativeVariable(call.instruction) + case _ => // Nothing to do + } + } + flows + } +} \ No newline at end of file diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/taint/NativeTaintProblem.scala b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/taint/NativeTaintProblem.scala new file mode 100644 index 0000000000..a0ee97d10b --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/analyses/ifds/taint/NativeTaintProblem.scala @@ -0,0 +1,58 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.fpcf.analyses.ifds.taint + +import org.opalj.br.ObjectType +import org.opalj.ifds.{AbstractIFDSNullFact, Callable, AbstractIFDSFact} +import org.opalj.ll.llvm.value.Value + +trait NativeTaintFact extends AbstractIFDSFact + +object NativeTaintNullFact extends NativeTaintFact with AbstractIFDSNullFact + +/** + * A tainted variable. + * + * @param index The variable's definition site. + */ +case class JavaVariable(index: Int) extends NativeTaintFact +case class NativeVariable(value: Value) extends NativeTaintFact + +/** + * A tainted array element. + * + * @param index The array's definition site. + * @param element The index of the tainted element in the array. + */ +case class JavaArrayElement(index: Int, element: Int) extends NativeTaintFact +case class NativeArrayElement(base: Value, indices: Iterable[Long]) extends NativeTaintFact + +/** + * A tainted static field. + * + * @param classType The field's class. + * @param fieldName The field's name. + */ +case class JavaStaticField(classType: ObjectType, fieldName: String) extends NativeTaintFact + +/** + * A tainted instance field. + * + * @param index The definition site of the field's value. + * @param classType The field's type. + * @param fieldName The field's value. + */ +case class JavaInstanceField(index: Int, classType: ObjectType, fieldName: String) extends NativeTaintFact + +/** + * A path of method calls, originating from the analyzed method, over which a tainted variable + * reaches the sink. + * + * @param flow A sequence of method calls, originating from but not including this method. + */ +case class NativeFlowFact(flow: Seq[Callable]) extends NativeTaintFact { + override val hashCode: Int = { + var r = 1 + flow.foreach(f => r = (r + f.hashCode()) * 31) + r + } +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/properties/Taint.scala b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/properties/Taint.scala new file mode 100644 index 0000000000..91f6ba4551 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/fpcf/properties/Taint.scala @@ -0,0 +1,25 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.fpcf.properties + +import org.opalj.fpcf.PropertyKey +import org.opalj.ifds.{IFDSProperty, IFDSPropertyMetaInformation} +import org.opalj.ll.fpcf.analyses.ifds.LLVMStatement +import org.opalj.ll.fpcf.analyses.ifds.taint.NativeTaintFact + +case class NativeTaint(flows: Map[LLVMStatement, Set[NativeTaintFact]], debugData: Map[LLVMStatement, Set[NativeTaintFact]] = Map.empty) extends IFDSProperty[LLVMStatement, NativeTaintFact] { + + override type Self = NativeTaint + override def create(result: Map[LLVMStatement, Set[NativeTaintFact]]): IFDSProperty[LLVMStatement, NativeTaintFact] = new NativeTaint(result) + override def create(result: Map[LLVMStatement, Set[NativeTaintFact]], debugData: Map[LLVMStatement, Set[NativeTaintFact]]): IFDSProperty[LLVMStatement, NativeTaintFact] = new NativeTaint(result, debugData) + + override def key: PropertyKey[NativeTaint] = NativeTaint.key +} + +object NativeTaint extends IFDSPropertyMetaInformation[LLVMStatement, NativeTaintFact] { + + override type Self = NativeTaint + override def create(result: Map[LLVMStatement, Set[NativeTaintFact]]): IFDSProperty[LLVMStatement, NativeTaintFact] = new NativeTaint(result) + override def create(result: Map[LLVMStatement, Set[NativeTaintFact]], debugData: Map[LLVMStatement, Set[NativeTaintFact]]): IFDSProperty[LLVMStatement, NativeTaintFact] = new NativeTaint(result, debugData) + + val key: PropertyKey[NativeTaint] = PropertyKey.create("NativeTaint", new NativeTaint(Map.empty)) +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/Module.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/Module.scala new file mode 100644 index 0000000000..24a7b87d6d --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/Module.scala @@ -0,0 +1,37 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.llvm + +import org.bytedeco.llvm.LLVM.{LLVMModuleRef, LLVMValueRef} +import org.bytedeco.llvm.global.LLVM.{LLVMDisposeMessage, LLVMGetFirstFunction, LLVMGetNamedFunction, LLVMGetNextFunction, LLVMPrintModuleToString} +import org.opalj.ll.llvm.value.{Value, Function} + +case class Module(ref: LLVMModuleRef) { + def functions: FunctionIterator = { + new FunctionIterator(LLVMGetFirstFunction(ref)) + } + + def repr: String = { + val bytePointer = LLVMPrintModuleToString(ref) + val string = bytePointer.getString + LLVMDisposeMessage(bytePointer) + string + } + + def function(name: String): Function = + Value(LLVMGetNamedFunction(ref, name)) match { + case None => + throw new IllegalArgumentException("Unknown function '"+name+"'") + case Some(function: Function) => function + case Some(_) => throw new IllegalStateException("Expected LLVMGetNamedFunction to return a Function ref") + } +} + +class FunctionIterator(var ref: LLVMValueRef) extends Iterator[Function] { + override def hasNext: Boolean = ref != null + + override def next(): Function = { + val function = Function(ref) + this.ref = LLVMGetNextFunction(ref) + function + } +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/Reader.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/Reader.scala new file mode 100644 index 0000000000..f84d937233 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/Reader.scala @@ -0,0 +1,33 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ + +package org.opalj.ll.llvm + +import org.bytedeco.javacpp.BytePointer +import org.bytedeco.llvm.LLVM.{LLVMContextRef, LLVMMemoryBufferRef, LLVMModuleRef} +import org.bytedeco.llvm.global.LLVM.{ + LLVMContextCreate, + LLVMCreateMemoryBufferWithContentsOfFile, + LLVMDisposeMessage, + LLVMParseIRInContext +} + +object Reader { + def readIR(path: String): Option[Module] = { + val file_buffer: LLVMMemoryBufferRef = new LLVMMemoryBufferRef() + val path_pointer: BytePointer = new BytePointer(path) + val out_message: BytePointer = new BytePointer() + if (LLVMCreateMemoryBufferWithContentsOfFile(path_pointer, file_buffer, out_message) != 0) { + System.err.println("Failed to load file: "+out_message.getString) + LLVMDisposeMessage(out_message) + return None + } + val context: LLVMContextRef = LLVMContextCreate() + val module: LLVMModuleRef = new LLVMModuleRef() + if (LLVMParseIRInContext(context, file_buffer, module, out_message) != 0) { + println("Failed to parse file: "+out_message) + LLVMDisposeMessage(out_message) + return None + } + Some(Module(module)) + } +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/Type.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/Type.scala new file mode 100644 index 0000000000..f21753bb24 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/Type.scala @@ -0,0 +1,116 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.llvm + +import org.bytedeco.javacpp.PointerPointer +import org.bytedeco.llvm.LLVM.LLVMTypeRef +import org.bytedeco.llvm.global.LLVM._ + +object Type { + def apply(ref: LLVMTypeRef): Type = { + LLVMGetTypeKind(ref) match { + case LLVMVoidTypeKind => VoidType(ref) + case LLVMHalfTypeKind => HalfType(ref) + case LLVMFloatTypeKind => FloatType(ref) + case LLVMDoubleTypeKind => DoubleType(ref) + case LLVMX86_FP80TypeKind => X86_FP80Type(ref) + case LLVMFP128TypeKind => FP128Type(ref) + case LLVMPPC_FP128TypeKind => PPC_FP128Type(ref) + case LLVMLabelTypeKind => LabelType(ref) + case LLVMIntegerTypeKind => IntegerType(ref) + case LLVMFunctionTypeKind => FunctionType(ref) + case LLVMStructTypeKind => StructType(ref) + case LLVMArrayTypeKind => ArrayType(ref) + case LLVMPointerTypeKind => PointerType(ref) + case LLVMVectorTypeKind => VectorType(ref) + case LLVMMetadataTypeKind => MetadataType(ref) + case LLVMX86_MMXTypeKind => X86_MMXType(ref) + case LLVMTokenTypeKind => TokenType(ref) + case LLVMScalableVectorTypeKind => ScalableVectorType(ref) + case LLVMBFloatTypeKind => FloatType(ref) + case typeKind => throw new IllegalArgumentException("unknown type kind: "+typeKind) + } + } +} + +sealed abstract class Type(ref: LLVMTypeRef) { + def repr: String = { + val bytePointer = LLVMPrintTypeToString(ref) + val string = bytePointer.getString + LLVMDisposeMessage(bytePointer) + string + } + + override def toString: String = s"Type(${repr})" + + def isSized: Boolean = intToBool(LLVMTypeIsSized(ref)) +} + +trait SequentialType { + val ref: LLVMTypeRef + + def element: Type = Type(LLVMGetElementType(ref)) +} + +/** type with no size */ +case class VoidType(ref: LLVMTypeRef) extends Type(ref) +/** 16 bit floating point type */ +case class HalfType(ref: LLVMTypeRef) extends Type(ref) +/** 32 bit floating point type */ +case class FloatType(ref: LLVMTypeRef) extends Type(ref) +/** 64 bit floating point type */ +case class DoubleType(ref: LLVMTypeRef) extends Type(ref) +/** 80 bit floating point type (X87) */ +case class X86_FP80Type(ref: LLVMTypeRef) extends Type(ref) +/** 128 bit floating point type (112-bit mantissa) */ +case class FP128Type(ref: LLVMTypeRef) extends Type(ref) +/** 128 bit floating point type (two 64-bits) */ +case class PPC_FP128Type(ref: LLVMTypeRef) extends Type(ref) +/** Labels */ +case class LabelType(ref: LLVMTypeRef) extends Type(ref) +/** Arbitrary bit width integers */ +case class IntegerType(ref: LLVMTypeRef) extends Type(ref) +/** Functions */ +case class FunctionType(ref: LLVMTypeRef) extends Type(ref) { + def returnType: Type = Type(LLVMGetReturnType(ref)) + def isVarArg: Boolean = intToBool(LLVMIsFunctionVarArg(ref)) + + def paramCount: Int = LLVMCountParamTypes(ref) + def params: Iterable[Type] = { + val result = new PointerPointer[LLVMTypeRef](paramCount.toLong) + LLVMGetParamTypes(ref, result) + (0.toLong until paramCount.toLong).map(result.get(_)).map(p => Type(new LLVMTypeRef(p))) + } +} +/** Structures */ +case class StructType(ref: LLVMTypeRef) extends Type(ref) { + def name: String = LLVMGetStructName(ref).getString + def elementCount: Int = LLVMCountStructElementTypes(ref) + def elementAtIndex(i: Int) = { + assert(i < elementCount) + Type(LLVMStructGetTypeAtIndex(ref, i)) + } + def elements: Iterable[Type] = (0 until elementCount).map(elementAtIndex(_)) + def isPacked: Boolean = intToBool(LLVMIsPackedStruct(ref)) + def isOpaque: Boolean = intToBool(LLVMIsOpaqueStruct(ref)) + def isLiteral: Boolean = intToBool(LLVMIsLiteralStruct(ref)) +} +/** Arrays */ +case class ArrayType(ref: LLVMTypeRef) extends Type(ref) with SequentialType { + def length: Int = LLVMGetArrayLength(ref) +} +/** Pointers */ +case class PointerType(ref: LLVMTypeRef) extends Type(ref) with SequentialType +/** Fixed width SIMD vector type */ +case class VectorType(ref: LLVMTypeRef) extends Type(ref) with SequentialType { + def size: Int = LLVMGetVectorSize(ref) +} +/** Metadata */ +case class MetadataType(ref: LLVMTypeRef) extends Type(ref) +/** X86 MMX */ +case class X86_MMXType(ref: LLVMTypeRef) extends Type(ref) +/** Tokens */ +case class TokenType(ref: LLVMTypeRef) extends Type(ref) +/** Scalable SIMD vector type */ +case class ScalableVectorType(ref: LLVMTypeRef) extends Type(ref) +/** 16 bit brain floating point type */ +case class BFloatType(ref: LLVMTypeRef) extends Type(ref) diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/package.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/package.scala new file mode 100644 index 0000000000..253baa543b --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/package.scala @@ -0,0 +1,10 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll + +package object llvm { + def intToBool(i: Int): Boolean = i match { + case 0 => false + case 1 => true + case _ => throw new IllegalArgumentException(s"${i} is not a valid Boolean") + } +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Argument.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Argument.scala new file mode 100644 index 0000000000..9c0521a754 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Argument.scala @@ -0,0 +1,16 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.llvm.value + +import org.bytedeco.llvm.LLVM.LLVMValueRef +import org.bytedeco.llvm.global.LLVM.{LLVMGetParamParent, LLVMGetValueKind, LLVMArgumentValueKind} +import org.opalj.ll.llvm.value + +case class Argument(ref: LLVMValueRef, index: Int) extends Value(ref) { + assert(LLVMGetValueKind(ref) == LLVMArgumentValueKind, "ref has to be an argument") + + def parent(): value.Function = value.Function(LLVMGetParamParent(ref)) +} + +object Argument { + def apply(ref: LLVMValueRef): Argument = value.Function(LLVMGetParamParent(ref)).arguments.find(_.ref == ref).get +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/BasicBlock.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/BasicBlock.scala new file mode 100644 index 0000000000..a6b3cf7bf2 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/BasicBlock.scala @@ -0,0 +1,69 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.llvm.value + +import org.bytedeco.llvm.LLVM.{LLVMBasicBlockRef, LLVMValueRef} +import org.bytedeco.llvm.global.LLVM._ +import org.opalj.graphs.Node + +case class BasicBlock(block_ref: LLVMBasicBlockRef) + extends Value(LLVMBasicBlockAsValue(block_ref)) + with Node { + def parent = Function(LLVMGetBasicBlockParent(block_ref)) + def instructions: InstructionIterator = new InstructionIterator(LLVMGetFirstInstruction(block_ref)) + def firstInstruction: Instruction = Instruction(LLVMGetFirstInstruction(block_ref)) + def lastInstruction: Instruction = Instruction(LLVMGetLastInstruction(block_ref)) + + def terminator: Option[Instruction with Terminator] = { + OptionalInstruction(LLVMGetBasicBlockTerminator(block_ref)) match { + case Some(terminator) => { + assert(terminator.isTerminator) + Some(terminator.asInstanceOf[Instruction with Terminator]) + } + case None => None + } + } + + def blockName(): String = LLVMGetBasicBlockName(block_ref).getString + + /** + * Returns a human readable representation (HRR) of this node. + */ + override def toHRR: Option[String] = { + Some(name) //TODO: maybe add more info + } + + /** + * An identifier that uniquely identifies this node in the graph to which this + * node belongs. By default two nodes are considered equal if they have the same + * unique id. + */ + override def nodeId: Int = { + block_ref.hashCode + } + + /** + * Returns `true` if this node has successor nodes. + */ + override def hasSuccessors: Boolean = terminator match { + case Some(t) => t.hasSuccessors + case None => false + } + + /** + * Applies the given function for each successor node. + */ + override def foreachSuccessor(f: Node => Unit): Unit = terminator match { + case Some(t) => t.foreachSuccessor(f) + case None => + } +} + +class InstructionIterator(var ref: LLVMValueRef) extends Iterator[Instruction] { + override def hasNext: Boolean = LLVMGetNextInstruction(ref) != null + + override def next(): Instruction = { + val instruction = Instruction(ref) + this.ref = LLVMGetNextInstruction(ref) + instruction + } +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Function.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Function.scala new file mode 100644 index 0000000000..16b13deaed --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Function.scala @@ -0,0 +1,68 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.llvm.value + +import org.bytedeco.llvm.LLVM.{LLVMBasicBlockRef, LLVMValueRef} +import org.bytedeco.llvm.global.LLVM._ +import org.opalj.io.writeAndOpen + +case class Function(ref: LLVMValueRef) extends Value(ref) { + assert(LLVMGetValueKind(ref) == LLVMFunctionValueKind, "ref has to be a function") + + def basicBlocks: BasicBlockIterator = { + new BasicBlockIterator(LLVMGetFirstBasicBlock(ref)) + } + def basicBlockCount: Int = LLVMCountBasicBlocks(ref) + + def arguments: ArgumentIterator = { + new ArgumentIterator(LLVMGetFirstParam(ref)) + } + def argumentCount: Int = LLVMCountParams(ref) + def argument(index: Int): Argument = { + assert(index < argumentCount) + Argument(LLVMGetParam(ref, index), index) + } + + def entryBlock: BasicBlock = { + if (basicBlockCount == 0) throw new IllegalStateException("this function does not contain any basic block and may not be defined") + BasicBlock(LLVMGetEntryBasicBlock(ref)) + } + + def viewCFG(): Unit = { + val cfg_dot = org.opalj.graphs.toDot(Set(entryBlock)) + writeAndOpen(cfg_dot, name+"-CFG", ".gv") + } + + def viewLLVMCFG(include_content: Boolean = true): Unit = { + if (include_content) { + LLVMViewFunctionCFG(ref) + } else { + LLVMViewFunctionCFGOnly(ref) + } + } + + override def toString: String = { + s"Function(${name}(${arguments.map(_.typ.repr).mkString(", ")}))" + } +} + +class BasicBlockIterator(var ref: LLVMBasicBlockRef) extends Iterator[BasicBlock] { + override def hasNext: Boolean = ref != null + + override def next(): BasicBlock = { + val basicBlock = BasicBlock(ref) + this.ref = LLVMGetNextBasicBlock(ref) + basicBlock + } +} + +class ArgumentIterator(var ref: LLVMValueRef) extends Iterator[Argument] { + override def hasNext: Boolean = ref != null + var index = 0 + + override def next(): Argument = { + val argument = Argument(ref, index) + this.ref = LLVMGetNextParam(ref) + index += 1 + argument + } +} \ No newline at end of file diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/GlobalVariable.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/GlobalVariable.scala new file mode 100644 index 0000000000..5939f33590 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/GlobalVariable.scala @@ -0,0 +1,9 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.llvm.value + +import org.bytedeco.llvm.LLVM.LLVMValueRef +import org.bytedeco.llvm.global.LLVM.LLVMGetInitializer + +case class GlobalVariable(ref: LLVMValueRef) extends Value(ref: LLVMValueRef) { + def initializer: Value = Value(LLVMGetInitializer(ref)).get +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Instruction.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Instruction.scala new file mode 100644 index 0000000000..85e12e9b3b --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Instruction.scala @@ -0,0 +1,213 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.llvm.value + +import org.bytedeco.llvm.LLVM.LLVMValueRef +import org.bytedeco.llvm.global.LLVM._ +import org.opalj.ll.llvm.value.constant.ConstantIntValue +import org.opalj.ll.llvm.{FunctionType, Type} + +object OptionalInstruction { + def apply(ref: LLVMValueRef): Option[Instruction] = { + if (ref.isNull) return None + Some(Instruction(ref)) + } +} + +object Instruction { + def apply(ref: LLVMValueRef): Instruction = { + assert(ref != null && !ref.isNull(), "ref may not be null") + assert(LLVMGetValueKind(ref) == LLVMInstructionValueKind, "ref has to be an instruction") + LLVMGetInstructionOpcode(ref) match { + case LLVMRet => Ret(ref) + case LLVMBr => Br(ref) + case LLVMSwitch => Switch(ref) + case LLVMIndirectBr => IndirectBr(ref) + case LLVMInvoke => Invoke(ref) + case LLVMUnreachable => Unreachable(ref) + case LLVMCallBr => CallBr(ref) + case LLVMFNeg => FNeg(ref) + case LLVMAdd => Add(ref) + case LLVMFAdd => FAdd(ref) + case LLVMSub => Sub(ref) + case LLVMFSub => FSub(ref) + case LLVMMul => Mul(ref) + case LLVMFMul => FMul(ref) + case LLVMUDiv => UDiv(ref) + case LLVMSDiv => SDiv(ref) + case LLVMFDiv => FDiv(ref) + case LLVMURem => URem(ref) + case LLVMSRem => SRem(ref) + case LLVMFRem => FRem(ref) + case LLVMShl => Shl(ref) + case LLVMLShr => LShr(ref) + case LLVMAShr => AShr(ref) + case LLVMAnd => And(ref) + case LLVMOr => Or(ref) + case LLVMXor => Xor(ref) + case LLVMAlloca => Alloca(ref) + case LLVMLoad => Load(ref) + case LLVMStore => Store(ref) + case LLVMGetElementPtr => GetElementPtr(ref) + case LLVMTrunc => Trunc(ref) + case LLVMZExt => ZExt(ref) + case LLVMSExt => SExt(ref) + case LLVMFPToUI => FPToUI(ref) + case LLVMFPToSI => FPToSI(ref) + case LLVMUIToFP => UIToFP(ref) + case LLVMSIToFP => SIToFP(ref) + case LLVMFPTrunc => FPTrunc(ref) + case LLVMFPExt => FPExt(ref) + case LLVMPtrToInt => PtrToInt(ref) + case LLVMIntToPtr => IntToPtr(ref) + case LLVMBitCast => BitCast(ref) + case LLVMAddrSpaceCast => AddrSpaceCast(ref) + case LLVMICmp => ICmp(ref) + case LLVMFCmp => FCmp(ref) + case LLVMPHI => PHI(ref) + case LLVMCall => Call(ref) + case LLVMSelect => Select(ref) + case LLVMUserOp1 => UserOp1(ref) + case LLVMUserOp2 => UserOp2(ref) + case LLVMVAArg => VAArg(ref) + case LLVMExtractElement => ExtractElement(ref) + case LLVMInsertElement => InsertElement(ref) + case LLVMShuffleVector => ShuffleVector(ref) + case LLVMExtractValue => ExtractValue(ref) + case LLVMInsertValue => InsertValue(ref) + case LLVMFreeze => Freeze(ref) + case LLVMFence => Fence(ref) + case LLVMAtomicCmpXchg => AtomicCmpXchg(ref) + case LLVMAtomicRMW => AtomicRMW(ref) + case LLVMResume => Resume(ref) + case LLVMLandingPad => LandingPad(ref) + case LLVMCleanupRet => CleanupRet(ref) + case LLVMCatchRet => CatchRet(ref) + case LLVMCatchPad => CatchPad(ref) + case LLVMCleanupPad => CleanupPad(ref) + case LLVMCatchSwitch => CatchSwitch(ref) + case opCode => throw new IllegalArgumentException("unknown instruction opcode: "+opCode) + } + } +} + +trait Terminator { + val ref: LLVMValueRef + def numSuccessors: Int = LLVMGetNumSuccessors(ref) + def hasSuccessors: Boolean = numSuccessors > 0 + def getSuccessor(i: Int) = BasicBlock(LLVMGetSuccessor(ref, i)) + def foreachSuccessor(f: BasicBlock => Unit): Unit = + (0 to numSuccessors - 1).foreach(i => f(getSuccessor(i))) + def successors: Seq[Instruction] = + (0 to numSuccessors - 1).map(i => getSuccessor(i).firstInstruction) +} + +sealed abstract class Instruction(ref: LLVMValueRef) extends User(ref) { + def isTerminator: Boolean = LLVMIsATerminatorInst(ref) != null + def parent: BasicBlock = BasicBlock(LLVMGetInstructionParent(ref)) + def function: Function = parent.parent + def next: Option[Instruction] = OptionalInstruction(LLVMGetNextInstruction(ref)) + + override def toString: String = { + s"${this.getClass.getSimpleName}(${repr})" + } +} + +case class Ret(ref: LLVMValueRef) extends Instruction(ref) with Terminator { + def value: Value = operand(0) +} +case class Br(ref: LLVMValueRef) extends Instruction(ref) with Terminator +case class Switch(ref: LLVMValueRef) extends Instruction(ref) with Terminator +case class IndirectBr(ref: LLVMValueRef) extends Instruction(ref) with Terminator +case class Invoke(ref: LLVMValueRef) extends Instruction(ref) with Terminator +case class Unreachable(ref: LLVMValueRef) extends Instruction(ref) with Terminator +case class CallBr(ref: LLVMValueRef) extends Instruction(ref) with Terminator +case class FNeg(ref: LLVMValueRef) extends Instruction(ref) +case class Add(ref: LLVMValueRef) extends Instruction(ref) { + def op1: Value = operand(0) + def op2: Value = operand(1) +} +case class FAdd(ref: LLVMValueRef) extends Instruction(ref) +case class Sub(ref: LLVMValueRef) extends Instruction(ref) { + def op1: Value = operand(0) + def op2: Value = operand(1) +} +case class FSub(ref: LLVMValueRef) extends Instruction(ref) +case class Mul(ref: LLVMValueRef) extends Instruction(ref) +case class FMul(ref: LLVMValueRef) extends Instruction(ref) +case class UDiv(ref: LLVMValueRef) extends Instruction(ref) +case class SDiv(ref: LLVMValueRef) extends Instruction(ref) +case class FDiv(ref: LLVMValueRef) extends Instruction(ref) +case class URem(ref: LLVMValueRef) extends Instruction(ref) +case class SRem(ref: LLVMValueRef) extends Instruction(ref) +case class FRem(ref: LLVMValueRef) extends Instruction(ref) +case class Shl(ref: LLVMValueRef) extends Instruction(ref) +case class LShr(ref: LLVMValueRef) extends Instruction(ref) +case class AShr(ref: LLVMValueRef) extends Instruction(ref) +case class And(ref: LLVMValueRef) extends Instruction(ref) +case class Or(ref: LLVMValueRef) extends Instruction(ref) +case class Xor(ref: LLVMValueRef) extends Instruction(ref) +case class Alloca(ref: LLVMValueRef) extends Instruction(ref) { + def allocatedType: Type = Type(LLVMGetAllocatedType(ref)) +} +case class Load(ref: LLVMValueRef) extends Instruction(ref) { + def src: Value = operand(0) +} +case class Store(ref: LLVMValueRef) extends Instruction(ref) { + def src: Value = operand(0) + def dst: Value = operand(1) +} +case class GetElementPtr(ref: LLVMValueRef) extends Instruction(ref) { + def base: Value = operand(0) + def isConstant = (1 until numOperands).forall(operand(_).isInstanceOf[ConstantIntValue]) + def constants = (1 until numOperands).map(operand(_).asInstanceOf[ConstantIntValue].signExtendedValue) + def isZero = isConstant && constants.forall(_ == 0) + + def numIndices: Int = LLVMGetNumIndices(ref) + def indices: Iterable[Int] = LLVMGetIndices(ref).asBuffer().array() +} +case class Trunc(ref: LLVMValueRef) extends Instruction(ref) +case class ZExt(ref: LLVMValueRef) extends Instruction(ref) +case class SExt(ref: LLVMValueRef) extends Instruction(ref) +case class FPToUI(ref: LLVMValueRef) extends Instruction(ref) +case class FPToSI(ref: LLVMValueRef) extends Instruction(ref) +case class UIToFP(ref: LLVMValueRef) extends Instruction(ref) +case class SIToFP(ref: LLVMValueRef) extends Instruction(ref) +case class FPTrunc(ref: LLVMValueRef) extends Instruction(ref) +case class FPExt(ref: LLVMValueRef) extends Instruction(ref) +case class PtrToInt(ref: LLVMValueRef) extends Instruction(ref) +case class IntToPtr(ref: LLVMValueRef) extends Instruction(ref) +case class BitCast(ref: LLVMValueRef) extends Instruction(ref) +case class AddrSpaceCast(ref: LLVMValueRef) extends Instruction(ref) +case class ICmp(ref: LLVMValueRef) extends Instruction(ref) +case class FCmp(ref: LLVMValueRef) extends Instruction(ref) +case class PHI(ref: LLVMValueRef) extends Instruction(ref) +case class Call(ref: LLVMValueRef) extends Instruction(ref) { + def calledValue: Value = Value(LLVMGetCalledValue(ref)).get // corresponds to last operand + def calledFunctionType: FunctionType = Type(LLVMGetCalledFunctionType(ref)).asInstanceOf[FunctionType] + def indexOfArgument(argument: Value): Option[Int] = { + for (i <- 0 to numOperands) + if (operand(i) == argument) return Some(i) + None + } + def numArgOperands: Int = LLVMGetNumArgOperands(ref) +} +case class Select(ref: LLVMValueRef) extends Instruction(ref) +case class UserOp1(ref: LLVMValueRef) extends Instruction(ref) +case class UserOp2(ref: LLVMValueRef) extends Instruction(ref) +case class VAArg(ref: LLVMValueRef) extends Instruction(ref) +case class ExtractElement(ref: LLVMValueRef) extends Instruction(ref) +case class InsertElement(ref: LLVMValueRef) extends Instruction(ref) +case class ShuffleVector(ref: LLVMValueRef) extends Instruction(ref) +case class ExtractValue(ref: LLVMValueRef) extends Instruction(ref) +case class InsertValue(ref: LLVMValueRef) extends Instruction(ref) +case class Freeze(ref: LLVMValueRef) extends Instruction(ref) +case class Fence(ref: LLVMValueRef) extends Instruction(ref) +case class AtomicCmpXchg(ref: LLVMValueRef) extends Instruction(ref) +case class AtomicRMW(ref: LLVMValueRef) extends Instruction(ref) +case class Resume(ref: LLVMValueRef) extends Instruction(ref) with Terminator +case class LandingPad(ref: LLVMValueRef) extends Instruction(ref) +case class CleanupRet(ref: LLVMValueRef) extends Instruction(ref) with Terminator +case class CatchRet(ref: LLVMValueRef) extends Instruction(ref) with Terminator +case class CatchPad(ref: LLVMValueRef) extends Instruction(ref) +case class CleanupPad(ref: LLVMValueRef) extends Instruction(ref) +case class CatchSwitch(ref: LLVMValueRef) extends Instruction(ref) with Terminator diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Use.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Use.scala new file mode 100644 index 0000000000..4cf208eb0f --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Use.scala @@ -0,0 +1,10 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.llvm.value + +import org.bytedeco.llvm.LLVM.LLVMUseRef +import org.bytedeco.llvm.global.LLVM.{LLVMGetUsedValue, LLVMGetUser} + +case class Use(ref: LLVMUseRef) { + def value: Value = Value(LLVMGetUsedValue(ref)).get + def user: Value = Value(LLVMGetUser(ref)).get +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/User.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/User.scala new file mode 100644 index 0000000000..ec9bc7fd60 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/User.scala @@ -0,0 +1,18 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.llvm +package value + +import org.bytedeco.llvm.LLVM.LLVMValueRef +import org.bytedeco.llvm.global.LLVM._ + +class User(ref: LLVMValueRef) extends Value(ref) { + def numOperands: Int = LLVMGetNumOperands(ref) + def operand(index: Int): Value = { + assert(index < numOperands) + Value(LLVMGetOperand(ref, index)).get + } + def operandUse(index: Int): UsesIterator = { + assert(index < numOperands) + new UsesIterator(LLVMGetOperandUse(ref, index)) + } +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Value.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Value.scala new file mode 100644 index 0000000000..3e347a5e32 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/Value.scala @@ -0,0 +1,79 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.llvm +package value + +import org.bytedeco.llvm.LLVM.{LLVMUseRef, LLVMValueRef} +import org.bytedeco.llvm.global.LLVM._ +import org.opalj.ll.llvm.value.constant.{ConstantDataArray, ConstantDataVector, ConstantExpression, ConstantIntValue} + +class Value(ref: LLVMValueRef) { + def repr: String = { + val bytePointer = LLVMPrintValueToString(ref) + val string = bytePointer.getString + LLVMDisposeMessage(bytePointer) + string + } + + def name: String = { + LLVMGetValueName(ref).getString + } + + def typ: Type = Type(LLVMTypeOf(ref)) // because type is a keyword + + val address = ref.address + override def equals(other: Any): Boolean = + other.isInstanceOf[Value] && address == other.asInstanceOf[Value].address + + override def toString: String = { + s"${getClass.getSimpleName}(${repr})" + } + + def uses: UsesIterator = new UsesIterator(LLVMGetFirstUse(ref)) + def users: Iterator[Value] = uses.map(_.user) +} + +object Value { + def apply(ref: LLVMValueRef): Option[Value] = { + if (ref == null) return None + if (ref.isNull) return None + Some(LLVMGetValueKind(ref) match { + case LLVMArgumentValueKind => Argument(ref) + case LLVMBasicBlockValueKind => BasicBlock(LLVMValueAsBasicBlock(ref)) + //LLVMMemoryUseValueKind + //LLVMMemoryDefValueKind + //LLVMMemoryPhiValueKind + case LLVMFunctionValueKind => Function(ref) + //LLVMGlobalAliasValueKind + //LLVMGlobalIFuncValueKind + case LLVMGlobalVariableValueKind => GlobalVariable(ref) + //LLVMBlockAddressValueKind + case LLVMConstantExprValueKind => ConstantExpression(ref) + //LLVMConstantArrayValueKind + //LLVMConstantStructValueKind + //LLVMConstantVectorValueKind + //LLVMUndefValueValueKind + //LLVMConstantAggregateZeroValueKind + case LLVMConstantDataArrayValueKind => ConstantDataArray(ref) + case LLVMConstantDataVectorValueKind => ConstantDataVector(ref) + case LLVMConstantIntValueKind => ConstantIntValue(ref) + //LLVMConstantFPValueKind + //LLVMConstantPointerNullValueKind + //LLVMConstantTokenNoneValueKind + //LLVMMetadataAsValueValueKind + //LLVMInlineAsmValueKind + case LLVMInstructionValueKind => Instruction(ref) + //LLVMPoisonValueValueKind + case valueKind => throw new IllegalArgumentException("unknown valueKind: "+valueKind) + }) + } +} + +class UsesIterator(var ref: LLVMUseRef) extends Iterator[Use] { + override def hasNext: Boolean = ref != null + + override def next(): Use = { + val use = value.Use(ref) + this.ref = LLVMGetNextUse(ref) + use + } +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/constant/ConstantDataSequential.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/constant/ConstantDataSequential.scala new file mode 100644 index 0000000000..59c1e63ffc --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/constant/ConstantDataSequential.scala @@ -0,0 +1,16 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ + +package org.opalj.ll.llvm +package value +package constant + +import org.bytedeco.llvm.LLVM.LLVMValueRef +import org.bytedeco.llvm.global.LLVM.LLVMGetAsString +import org.bytedeco.javacpp.SizeTPointer + +abstract class ConstantDataSequential(ref: LLVMValueRef) extends User(ref) { + def asString: String = LLVMGetAsString(ref, new SizeTPointer(1)).getString +} + +case class ConstantDataArray(ref: LLVMValueRef) extends ConstantDataSequential(ref) +case class ConstantDataVector(ref: LLVMValueRef) extends ConstantDataSequential(ref) diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/constant/ConstantExpression.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/constant/ConstantExpression.scala new file mode 100644 index 0000000000..2da9696b8c --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/constant/ConstantExpression.scala @@ -0,0 +1,159 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.llvm.value +package constant + +import org.bytedeco.llvm.LLVM.LLVMValueRef +import org.bytedeco.llvm.global.LLVM._ +import org.opalj.ll.llvm.value.User + +object ConstantExpression { + def apply(ref: LLVMValueRef): ConstantExpression = { + assert(ref != null && !ref.isNull(), "ref may not be null") + assert(LLVMGetValueKind(ref) == LLVMConstantExprValueKind, "ref has to be an instruction") + LLVMGetConstOpcode(ref) match { + // case LLVMRet => RetConst(ref) + // case LLVMBr => BrConst(ref) + // case LLVMSwitch => SwitchConst(ref) + // case LLVMIndirectBr => IndirectBrConst(ref) + // case LLVMInvoke => InvokeConst(ref) + // case LLVMUnreachable => UnreachableConst(ref) + // case LLVMCallBr => CallBrConst(ref) + // case LLVMFNeg => FNegConst(ref) + // case LLVMAdd => AddConst(ref) + // case LLVMFAdd => FAddConst(ref) + // case LLVMSub => SubConst(ref) + // case LLVMFSub => FSubConst(ref) + // case LLVMMul => MulConst(ref) + // case LLVMFMul => FMulConst(ref) + // case LLVMUDiv => UDivConst(ref) + // case LLVMSDiv => SDivConst(ref) + // case LLVMFDiv => FDivConst(ref) + // case LLVMURem => URemConst(ref) + // case LLVMSRem => SRemConst(ref) + // case LLVMFRem => FRemConst(ref) + // case LLVMShl => ShlConst(ref) + // case LLVMLShr => LShrConst(ref) + // case LLVMAShr => AShrConst(ref) + // case LLVMAnd => AndConst(ref) + // case LLVMOr => OrConst(ref) + // case LLVMXor => XorConst(ref) + // case LLVMAlloca => AllocaConst(ref) + // case LLVMLoad => LoadConst(ref) + // case LLVMStore => StoreConst(ref) + case LLVMGetElementPtr => GetElementPtrConst(ref) + // case LLVMTrunc => TruncConst(ref) + // case LLVMZExt => ZExtConst(ref) + // case LLVMSExt => SExtConst(ref) + // case LLVMFPToUI => FPToUIConst(ref) + // case LLVMFPToSI => FPToSIConst(ref) + // case LLVMUIToFP => UIToFPConst(ref) + // case LLVMSIToFP => SIToFPConst(ref) + // case LLVMFPTrunc => FPTruncConst(ref) + // case LLVMFPExt => FPExtConst(ref) + // case LLVMPtrToInt => PtrToIntConst(ref) + // case LLVMIntToPtr => IntToPtrConst(ref) + // case LLVMBitCast => BitCastConst(ref) + // case LLVMAddrSpaceCast => AddrSpaceCastConst(ref) + // case LLVMICmp => ICmpConst(ref) + // case LLVMFCmp => FCmpConst(ref) + // case LLVMPHI => PHIConst(ref) + // case LLVMCall => CallConst(ref) + // case LLVMSelect => SelectConst(ref) + // case LLVMUserOp1 => UserOp1Const(ref) + // case LLVMUserOp2 => UserOp2Const(ref) + // case LLVMVAArg => VAArgConst(ref) + // case LLVMExtractElement => ExtractElementConst(ref) + // case LLVMInsertElement => InsertElementConst(ref) + // case LLVMShuffleVector => ShuffleVectorConst(ref) + // case LLVMExtractValue => ExtractValueConst(ref) + // case LLVMInsertValue => InsertValueConst(ref) + // case LLVMFreeze => FreezeConst(ref) + // case LLVMFence => FenceConst(ref) + // case LLVMAtomicCmpXchg => AtomicCmpXchgConst(ref) + // case LLVMAtomicRMW => AtomicRMWConst(ref) + // case LLVMResume => ResumeConst(ref) + // case LLVMLandingPad => LandingPadConst(ref) + // case LLVMCleanupRet => CleanupRetConst(ref) + // case LLVMCatchRet => CatchRetConst(ref) + // case LLVMCatchPad => CatchPadConst(ref) + // case LLVMCleanupPad => CleanupPadConst(ref) + // case LLVMCatchSwitch => CatchSwitchConst(ref) + case opCode => throw new IllegalArgumentException("unknown instruction opcode: "+opCode) + } + } +} + +sealed abstract class ConstantExpression(ref: LLVMValueRef) extends User(ref) + +// // case class RetConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class BrConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class SwitchConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class IndirectBrConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class InvokeConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class UnreachableConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class CallBrConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class FNegConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class AddConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class FAddConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class SubConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class FSubConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class MulConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class FMulConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class UDivConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class SDivConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class FDivConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class URemConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class SRemConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class FRemConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class ShlConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class LShrConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class AShrConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class AndConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class OrConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class XorConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class AllocaConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class LoadConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class StoreConst(ref: LLVMValueRef) extends ConstantExpression(ref) +case class GetElementPtrConst(ref: LLVMValueRef) extends ConstantExpression(ref) { + def base: Value = operand(0) + def isConstant = (1 until numOperands).forall(operand(_).isInstanceOf[ConstantIntValue]) + def constants = (1 until numOperands).map(operand(_).asInstanceOf[ConstantIntValue].signExtendedValue) + def isZero = isConstant && constants.forall(_ == 0) +} +// case class TruncConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class ZExtConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class SExtConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class FPToUIConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class FPToSIConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class UIToFPConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class SIToFPConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class FPTruncConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class FPExtConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class PtrToIntConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class IntToPtrConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class BitCastConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class AddrSpaceCastConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class ICmpConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class FCmpConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class PHIConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class CallConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class SelectConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class UserOp1Const(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class UserOp2Const(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class VAArgConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class ExtractElementConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class InsertElementConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class ShuffleVectorConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class ExtractValueConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class InsertValueConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class FreezeConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class FenceConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class AtomicCmpXchgConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class AtomicRMWConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class ResumeConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class LandingPadConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class CleanupRetConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class CatchRetConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class CatchPadConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class CleanupPadConst(ref: LLVMValueRef) extends ConstantExpression(ref) +// case class CatchSwitchConst(ref: LLVMValueRef) extends ConstantExpression(ref) diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/constant/ScalarConstants.scala b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/constant/ScalarConstants.scala new file mode 100644 index 0000000000..6d74d224a6 --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/llvm/value/constant/ScalarConstants.scala @@ -0,0 +1,12 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.ll.llvm.value +package constant + +import org.bytedeco.llvm.LLVM.LLVMValueRef +import org.bytedeco.llvm.global.LLVM.{LLVMConstIntGetSExtValue, LLVMConstIntGetZExtValue} +import org.opalj.ll.llvm.value.User + +case class ConstantIntValue(ref: LLVMValueRef) extends User(ref) { + def zeroExtendedValue: Long = LLVMConstIntGetZExtValue(ref) + def signExtendedValue: Long = LLVMConstIntGetSExtValue(ref) +} diff --git a/OPAL/ll/src/main/scala/org/opalj/ll/package.scala b/OPAL/ll/src/main/scala/org/opalj/ll/package.scala new file mode 100644 index 0000000000..c910eb56ee --- /dev/null +++ b/OPAL/ll/src/main/scala/org/opalj/ll/package.scala @@ -0,0 +1,31 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj + +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import org.opalj.log.LogContext +import org.opalj.log.GlobalLogContext +import org.opalj.log.OPALLogger.info + +package object ll { + + final val FrameworkName = "OPAL LLVM" + + { + implicit val logContext: LogContext = GlobalLogContext + try { + assert(false) // <= test whether assertions are turned on or off... + info(FrameworkName, "Production Build") + } catch { + case _: AssertionError => info(FrameworkName, "Development Build with Assertions") + } + } + + // We want to make sure that the class loader is used which potentially can + // find the config files; the libraries (e.g., Typesafe Config) may have + // been loaded using the parent class loader and, hence, may not be able to + // find the config files at all. + val BaseConfig: Config = ConfigFactory.load(this.getClass.getClassLoader) + + final val ConfigKeyPrefix = "org.opalj.ll." +} diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKind.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKind.scala index 497459e79f..ac8d388950 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKind.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKind.scala @@ -8,7 +8,7 @@ package org.opalj.fpcf * are generally only to be used if lower bounds cannot be computed or a very extensive and * are never of interest to any potential client. E.g., in case of an IFDS analysis, * computing the lower bound is not meaningful; in case of a call graph analysis, the lower - * bound is usually either prohibitively expensive or is not usefull to any analysis. + * bound is usually either prohibitively expensive or is not useful to any analysis. */ trait PropertyKind extends Any /* we now have a universal trait */ { diff --git a/OPAL/tac/lib/heros-trunk.jar b/OPAL/tac/lib/heros-trunk.jar new file mode 100644 index 0000000000..647855491f Binary files /dev/null and b/OPAL/tac/lib/heros-trunk.jar differ diff --git a/OPAL/tac/src/main/resources/summaries/java.io.BufferedInputStream.xml b/OPAL/tac/src/main/resources/summaries/java.io.BufferedInputStream.xml new file mode 100644 index 0000000000..85006bb4c7 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.BufferedInputStream.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.io.BufferedOutputStream.xml b/OPAL/tac/src/main/resources/summaries/java.io.BufferedOutputStream.xml new file mode 100644 index 0000000000..35013f071d --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.BufferedOutputStream.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.io.BufferedReader.xml b/OPAL/tac/src/main/resources/summaries/java.io.BufferedReader.xml new file mode 100644 index 0000000000..0f399e3716 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.BufferedReader.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.io.BufferedWriter.xml b/OPAL/tac/src/main/resources/summaries/java.io.BufferedWriter.xml new file mode 100644 index 0000000000..c37bd77c3e --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.BufferedWriter.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.io.ByteArrayInputStream.xml b/OPAL/tac/src/main/resources/summaries/java.io.ByteArrayInputStream.xml new file mode 100644 index 0000000000..b3a7d61d72 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.ByteArrayInputStream.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.io.ByteArrayOutputStream.xml b/OPAL/tac/src/main/resources/summaries/java.io.ByteArrayOutputStream.xml new file mode 100644 index 0000000000..30c97061e7 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.ByteArrayOutputStream.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.io.CharArrayReader.xml b/OPAL/tac/src/main/resources/summaries/java.io.CharArrayReader.xml new file mode 100644 index 0000000000..45efab1df1 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.CharArrayReader.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.io.CharArrayWriter.xml b/OPAL/tac/src/main/resources/summaries/java.io.CharArrayWriter.xml new file mode 100644 index 0000000000..39152be6aa --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.CharArrayWriter.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.io.DataInputStream.xml b/OPAL/tac/src/main/resources/summaries/java.io.DataInputStream.xml new file mode 100644 index 0000000000..dc5cc5574b --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.DataInputStream.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.io.DataOutputStream.xml b/OPAL/tac/src/main/resources/summaries/java.io.DataOutputStream.xml new file mode 100644 index 0000000000..bfe755710f --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.DataOutputStream.xml @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.io.File.xml b/OPAL/tac/src/main/resources/summaries/java.io.File.xml new file mode 100644 index 0000000000..2bb42b87c4 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.File.xml @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.io.FileInputStream.xml b/OPAL/tac/src/main/resources/summaries/java.io.FileInputStream.xml new file mode 100644 index 0000000000..746c9df3b6 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.FileInputStream.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.io.FileOutputStream.xml b/OPAL/tac/src/main/resources/summaries/java.io.FileOutputStream.xml new file mode 100644 index 0000000000..222c0f437c --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.FileOutputStream.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.io.FilterInputStream.xml b/OPAL/tac/src/main/resources/summaries/java.io.FilterInputStream.xml new file mode 100644 index 0000000000..b839b17c4d --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.FilterInputStream.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.io.InputStream.xml b/OPAL/tac/src/main/resources/summaries/java.io.InputStream.xml new file mode 100644 index 0000000000..4e2cab0b43 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.InputStream.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.io.InputStreamReader.xml b/OPAL/tac/src/main/resources/summaries/java.io.InputStreamReader.xml new file mode 100644 index 0000000000..5929805c49 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.InputStreamReader.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.io.LineNumberReader.xml b/OPAL/tac/src/main/resources/summaries/java.io.LineNumberReader.xml new file mode 100644 index 0000000000..dcccb2b32a --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.LineNumberReader.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.io.ObjectInputStream.xml b/OPAL/tac/src/main/resources/summaries/java.io.ObjectInputStream.xml new file mode 100644 index 0000000000..449c3cc737 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.ObjectInputStream.xml @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.io.ObjectOutputStream.xml b/OPAL/tac/src/main/resources/summaries/java.io.ObjectOutputStream.xml new file mode 100644 index 0000000000..82ece10632 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.ObjectOutputStream.xml @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.io.OutputStream.xml b/OPAL/tac/src/main/resources/summaries/java.io.OutputStream.xml new file mode 100644 index 0000000000..363d1aa088 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.OutputStream.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.io.OutputStreamWriter.xml b/OPAL/tac/src/main/resources/summaries/java.io.OutputStreamWriter.xml new file mode 100644 index 0000000000..9e2dbfccfd --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.OutputStreamWriter.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.io.PrintStream.xml b/OPAL/tac/src/main/resources/summaries/java.io.PrintStream.xml new file mode 100644 index 0000000000..27f76ff312 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.PrintStream.xml @@ -0,0 +1,351 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.io.PrintWriter.xml b/OPAL/tac/src/main/resources/summaries/java.io.PrintWriter.xml new file mode 100644 index 0000000000..136f9cf2c8 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.PrintWriter.xml @@ -0,0 +1,372 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.io.StringReader.xml b/OPAL/tac/src/main/resources/summaries/java.io.StringReader.xml new file mode 100644 index 0000000000..e184bd1f43 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.StringReader.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.io.StringWriter.xml b/OPAL/tac/src/main/resources/summaries/java.io.StringWriter.xml new file mode 100644 index 0000000000..6533577544 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.io.StringWriter.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Appendable.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Appendable.xml new file mode 100644 index 0000000000..8722eaf5fb --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Appendable.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Boolean.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Boolean.xml new file mode 100644 index 0000000000..57385073fb --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Boolean.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Byte.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Byte.xml new file mode 100644 index 0000000000..28ef000a4c --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Byte.xml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.CharSequence.xml b/OPAL/tac/src/main/resources/summaries/java.lang.CharSequence.xml new file mode 100644 index 0000000000..a0733d5ba2 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.CharSequence.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Character.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Character.xml new file mode 100644 index 0000000000..5b78cb91ca --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Character.xml @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Class.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Class.xml new file mode 100644 index 0000000000..c8d1bc4880 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Class.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Double.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Double.xml new file mode 100644 index 0000000000..a6e3563336 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Double.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Exception.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Exception.xml new file mode 100644 index 0000000000..6553a7d777 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Exception.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Float.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Float.xml new file mode 100644 index 0000000000..7d3de1a922 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Float.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Integer.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Integer.xml new file mode 100644 index 0000000000..bb61e88527 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Integer.xml @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Long.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Long.xml new file mode 100644 index 0000000000..012e50d8c6 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Long.xml @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Math.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Math.xml new file mode 100644 index 0000000000..0de3d2420f --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Math.xml @@ -0,0 +1,681 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.NullPointerException.xml b/OPAL/tac/src/main/resources/summaries/java.lang.NullPointerException.xml new file mode 100644 index 0000000000..1724c68718 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.NullPointerException.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Object.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Object.xml new file mode 100644 index 0000000000..8afcc0f453 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Object.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.ProcessBuilder.xml b/OPAL/tac/src/main/resources/summaries/java.lang.ProcessBuilder.xml new file mode 100644 index 0000000000..3a1cd57cb3 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.ProcessBuilder.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.RuntimeException.xml b/OPAL/tac/src/main/resources/summaries/java.lang.RuntimeException.xml new file mode 100644 index 0000000000..a5f0f11ca6 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.RuntimeException.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Short.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Short.xml new file mode 100644 index 0000000000..0176ec7840 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Short.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.String.xml b/OPAL/tac/src/main/resources/summaries/java.lang.String.xml new file mode 100644 index 0000000000..2d88c24acf --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.String.xml @@ -0,0 +1,458 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.StringBuffer.xml b/OPAL/tac/src/main/resources/summaries/java.lang.StringBuffer.xml new file mode 100644 index 0000000000..45d17e936d --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.StringBuffer.xml @@ -0,0 +1,468 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.StringBuilder.xml b/OPAL/tac/src/main/resources/summaries/java.lang.StringBuilder.xml new file mode 100644 index 0000000000..0f0a227963 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.StringBuilder.xml @@ -0,0 +1,438 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.System.xml b/OPAL/tac/src/main/resources/summaries/java.lang.System.xml new file mode 100644 index 0000000000..2370e3f149 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.System.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Thread.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Thread.xml new file mode 100644 index 0000000000..22bb351ae1 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Thread.xml @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.ThreadLocal.xml b/OPAL/tac/src/main/resources/summaries/java.lang.ThreadLocal.xml new file mode 100644 index 0000000000..bd35c46a82 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.ThreadLocal.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.lang.Throwable.xml b/OPAL/tac/src/main/resources/summaries/java.lang.Throwable.xml new file mode 100644 index 0000000000..fea8e12f90 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.lang.Throwable.xml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.math.BigDecimal.xml b/OPAL/tac/src/main/resources/summaries/java.math.BigDecimal.xml new file mode 100644 index 0000000000..d51734a762 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.math.BigDecimal.xml @@ -0,0 +1,881 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.math.BigInteger.xml b/OPAL/tac/src/main/resources/summaries/java.math.BigInteger.xml new file mode 100644 index 0000000000..845dc12bf3 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.math.BigInteger.xml @@ -0,0 +1,506 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.net.Proxy.xml b/OPAL/tac/src/main/resources/summaries/java.net.Proxy.xml new file mode 100644 index 0000000000..091862e579 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.net.Proxy.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.net.URI.xml b/OPAL/tac/src/main/resources/summaries/java.net.URI.xml new file mode 100644 index 0000000000..dd33751420 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.net.URI.xml @@ -0,0 +1,537 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.net.URL.xml b/OPAL/tac/src/main/resources/summaries/java.net.URL.xml new file mode 100644 index 0000000000..dc8775f4a7 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.net.URL.xml @@ -0,0 +1,567 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.net.URLConnection.xml b/OPAL/tac/src/main/resources/summaries/java.net.URLConnection.xml new file mode 100644 index 0000000000..9aa6357ce0 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.net.URLConnection.xml @@ -0,0 +1,392 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.net.URLDecoder.xml b/OPAL/tac/src/main/resources/summaries/java.net.URLDecoder.xml new file mode 100644 index 0000000000..d77cded909 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.net.URLDecoder.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.net.URLEncoder.xml b/OPAL/tac/src/main/resources/summaries/java.net.URLEncoder.xml new file mode 100644 index 0000000000..d130acfc8a --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.net.URLEncoder.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.nio.ByteBuffer.xml b/OPAL/tac/src/main/resources/summaries/java.nio.ByteBuffer.xml new file mode 100644 index 0000000000..753ea64d0c --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.nio.ByteBuffer.xml @@ -0,0 +1,453 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.nio.CharBuffer.xml b/OPAL/tac/src/main/resources/summaries/java.nio.CharBuffer.xml new file mode 100644 index 0000000000..9c7728ad57 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.nio.CharBuffer.xml @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.nio.DoubleBuffer.xml b/OPAL/tac/src/main/resources/summaries/java.nio.DoubleBuffer.xml new file mode 100644 index 0000000000..a78282ed02 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.nio.DoubleBuffer.xml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.nio.FloatBuffer.xml b/OPAL/tac/src/main/resources/summaries/java.nio.FloatBuffer.xml new file mode 100644 index 0000000000..e3aca493ca --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.nio.FloatBuffer.xml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.nio.IntBuffer.xml b/OPAL/tac/src/main/resources/summaries/java.nio.IntBuffer.xml new file mode 100644 index 0000000000..bf5c7898fa --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.nio.IntBuffer.xml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.nio.LongBuffer.xml b/OPAL/tac/src/main/resources/summaries/java.nio.LongBuffer.xml new file mode 100644 index 0000000000..112b3db88a --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.nio.LongBuffer.xml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.nio.ShortBuffer.xml b/OPAL/tac/src/main/resources/summaries/java.nio.ShortBuffer.xml new file mode 100644 index 0000000000..920dab843a --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.nio.ShortBuffer.xml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.nio.file.Path.xml b/OPAL/tac/src/main/resources/summaries/java.nio.file.Path.xml new file mode 100644 index 0000000000..fdaa153c4c --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.nio.file.Path.xml @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.sql.ResultSet.xml b/OPAL/tac/src/main/resources/summaries/java.sql.ResultSet.xml new file mode 100644 index 0000000000..a15e7d66f7 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.sql.ResultSet.xml @@ -0,0 +1,679 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.ArrayList.xml b/OPAL/tac/src/main/resources/summaries/java.util.ArrayList.xml new file mode 100644 index 0000000000..e7dde39081 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.ArrayList.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Arrays.xml b/OPAL/tac/src/main/resources/summaries/java.util.Arrays.xml new file mode 100644 index 0000000000..932381331a --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Arrays.xml @@ -0,0 +1,390 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Collection.xml b/OPAL/tac/src/main/resources/summaries/java.util.Collection.xml new file mode 100644 index 0000000000..b2a317642d --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Collection.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Collections.xml b/OPAL/tac/src/main/resources/summaries/java.util.Collections.xml new file mode 100644 index 0000000000..e5487db2c0 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Collections.xml @@ -0,0 +1,285 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Deque.xml b/OPAL/tac/src/main/resources/summaries/java.util.Deque.xml new file mode 100644 index 0000000000..2152c0907d --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Deque.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Enumeration.xml b/OPAL/tac/src/main/resources/summaries/java.util.Enumeration.xml new file mode 100644 index 0000000000..c2ba0c98de --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Enumeration.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.HashMap.xml b/OPAL/tac/src/main/resources/summaries/java.util.HashMap.xml new file mode 100644 index 0000000000..88cea68fae --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.HashMap.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.HashSet.xml b/OPAL/tac/src/main/resources/summaries/java.util.HashSet.xml new file mode 100644 index 0000000000..9c2ef3caa1 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.HashSet.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Iterator.xml b/OPAL/tac/src/main/resources/summaries/java.util.Iterator.xml new file mode 100644 index 0000000000..7d3b86873e --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Iterator.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.LinkedList.xml b/OPAL/tac/src/main/resources/summaries/java.util.LinkedList.xml new file mode 100644 index 0000000000..1836f7fc49 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.LinkedList.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.List.xml b/OPAL/tac/src/main/resources/summaries/java.util.List.xml new file mode 100644 index 0000000000..30e60c489d --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.List.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.ListIterator.xml b/OPAL/tac/src/main/resources/summaries/java.util.ListIterator.xml new file mode 100644 index 0000000000..92b09f5709 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.ListIterator.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Map.xml b/OPAL/tac/src/main/resources/summaries/java.util.Map.xml new file mode 100644 index 0000000000..6016ada266 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Map.xml @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Optional.xml b/OPAL/tac/src/main/resources/summaries/java.util.Optional.xml new file mode 100644 index 0000000000..697393bd5e --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Optional.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.OptionalDouble.xml b/OPAL/tac/src/main/resources/summaries/java.util.OptionalDouble.xml new file mode 100644 index 0000000000..0eccfa8399 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.OptionalDouble.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.OptionalInt.xml b/OPAL/tac/src/main/resources/summaries/java.util.OptionalInt.xml new file mode 100644 index 0000000000..dbc4c35bfb --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.OptionalInt.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.OptionalLong.xml b/OPAL/tac/src/main/resources/summaries/java.util.OptionalLong.xml new file mode 100644 index 0000000000..ef67d6c000 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.OptionalLong.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Properties.xml b/OPAL/tac/src/main/resources/summaries/java.util.Properties.xml new file mode 100644 index 0000000000..8b050f0a8e --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Properties.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Queue.xml b/OPAL/tac/src/main/resources/summaries/java.util.Queue.xml new file mode 100644 index 0000000000..3c141003a6 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Queue.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Random.xml b/OPAL/tac/src/main/resources/summaries/java.util.Random.xml new file mode 100644 index 0000000000..24022e232d --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Random.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Scanner.xml b/OPAL/tac/src/main/resources/summaries/java.util.Scanner.xml new file mode 100644 index 0000000000..169ddc3e3c --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Scanner.xml @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Set.xml b/OPAL/tac/src/main/resources/summaries/java.util.Set.xml new file mode 100644 index 0000000000..5d230b3b4b --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Set.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Stack.xml b/OPAL/tac/src/main/resources/summaries/java.util.Stack.xml new file mode 100644 index 0000000000..7ac9e30642 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Stack.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.StringTokenizer.xml b/OPAL/tac/src/main/resources/summaries/java.util.StringTokenizer.xml new file mode 100644 index 0000000000..8168807949 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.StringTokenizer.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.Vector.xml b/OPAL/tac/src/main/resources/summaries/java.util.Vector.xml new file mode 100644 index 0000000000..d4357a2093 --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.Vector.xml @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OPAL/tac/src/main/resources/summaries/java.util.regex.Matcher.xml b/OPAL/tac/src/main/resources/summaries/java.util.regex.Matcher.xml new file mode 100644 index 0000000000..16ed09a60b --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.regex.Matcher.xml @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/resources/summaries/java.util.regex.Pattern.xml b/OPAL/tac/src/main/resources/summaries/java.util.regex.Pattern.xml new file mode 100644 index 0000000000..d2b3d255ac --- /dev/null +++ b/OPAL/tac/src/main/resources/summaries/java.util.regex.Pattern.xml @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/AbstractIFDSAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/AbstractIFDSAnalysis.scala deleted file mode 100644 index f8df667a74..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/AbstractIFDSAnalysis.scala +++ /dev/null @@ -1,942 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package analyses - -import scala.annotation.tailrec - -import scala.collection.{Set => SomeSet} -import scala.collection.mutable - -import org.opalj.fpcf.EOptionP -import org.opalj.fpcf.EPK -import org.opalj.fpcf.FinalE -import org.opalj.fpcf.FinalEP -import org.opalj.fpcf.FinalP -import org.opalj.fpcf.InterimEUBP -import org.opalj.fpcf.InterimResult -import org.opalj.fpcf.ProperPropertyComputationResult -import org.opalj.fpcf.PropertyBounds -import org.opalj.fpcf.PropertyStore -import org.opalj.fpcf.Result -import org.opalj.fpcf.SomeEOptionP -import org.opalj.fpcf.SomeEPS -import org.opalj.value.ValueInformation -import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler -import org.opalj.br.Method -import org.opalj.br.ObjectType -import org.opalj.br.analyses.DeclaredMethods -import org.opalj.br.analyses.DeclaredMethodsKey -import org.opalj.br.analyses.SomeProject -import org.opalj.br.cfg.BasicBlock -import org.opalj.br.cfg.CFG -import org.opalj.br.cfg.CFGNode -import org.opalj.br.fpcf.FPCFAnalysis -import org.opalj.br.DeclaredMethod -import org.opalj.br.DefinedMethod -import org.opalj.br.analyses.ProjectInformationKeys -import org.opalj.br.fpcf.properties.Context -import org.opalj.tac.cg.TypeProviderKey -import org.opalj.tac.fpcf.properties.cg.Callees -import org.opalj.tac.fpcf.analyses.AbstractIFDSAnalysis.V -import org.opalj.tac.fpcf.analyses.cg.TypeProvider -import org.opalj.tac.fpcf.properties.IFDSProperty -import org.opalj.tac.fpcf.properties.IFDSPropertyMetaInformation -import org.opalj.tac.fpcf.properties.TACAI -import org.opalj.tac.fpcf.properties.TheTACAI - -/** - * The super type of all IFDS facts. - */ -trait AbstractIFDSFact - -/** - * The super type of all null facts. - */ -trait AbstractIFDSNullFact extends AbstractIFDSFact - -/** - * A framework for IFDS analyses. - * - * @tparam IFDSFact The type of flow facts the concrete analysis wants to track - * @author Dominik Helm - * @author Mario Trageser - */ -abstract class AbstractIFDSAnalysis[IFDSFact <: AbstractIFDSFact] extends FPCFAnalysis { - - /** - * Provides the concrete property key that must be unique for every distinct concrete analysis - * and the lower bound for the IFDSProperty. - */ - val propertyKey: IFDSPropertyMetaInformation[IFDSFact] - - /** - * Creates an IFDSProperty containing the result of this analysis. - * - * @param result Maps each exit statement to the facts valid after the exit statement. - * @return An IFDSProperty containing the `result`. - */ - def createPropertyValue(result: Map[Statement, Set[IFDSFact]]): IFDSProperty[IFDSFact] - - /** - * Computes the DataFlowFacts valid after statement `statement` on the CFG edge to statement `succ` - * if the DataFlowFacts `in` held before `statement`. - */ - - /** - * Computes the data flow for a normal statement. - * - * @param statement The analyzed statement. - * @param successor The successor of the analyzed `statement`, to which the data flow is considered. - * @param in Some facts valid before the execution of the `statement`. - * @return The facts valid after the execution of `statement` - * under the assumption that `in` held before `statement` and `successor` will be executed next. - */ - def normalFlow(statement: Statement, successor: Statement, in: Set[IFDSFact]): Set[IFDSFact] - - /** - * Computes the data flow for a call to start edge. - * - * @param call The analyzed call statement. - * @param calleeContext The called method. - * @param in Some facts valid before the execution of the `call`. - * @return The facts valid after the execution of `statement` under the assumption that `in` held before `statement` and `statement` calls `callee`. - */ - def callFlow( - call: Statement, calleeContext: Context, in: Set[IFDSFact] - ): Set[IFDSFact] - - /** - * Computes the data flow for a exit to return edge. - * - * @param call The statement, which called the `callee`. - * @param calleeContext The method called by `call`. - * @param exit The statement, which terminated the `calle`. - * @param successor The statement of the caller, which will be executed after the `callee` returned. - * @param in Some facts valid before the execution of the `exit`. - * @return The facts valid after the execution of `exit` in the caller's context - * under the assumption that `in` held before the execution of `exit` and that `successor` will be executed next. - */ - def returnFlow( - call: Statement, - calleeContext: Context, - exit: Statement, - successor: Statement, - in: Set[IFDSFact] - ): Set[IFDSFact] - - /** - * Computes the data flow for a call to return edge. - * - * @param call The statement, which invoked the call. - * @param successor The statement, which will be executed after the call. - * @param in Some facts valid before the `call`. - * @return The facts valid after the call independently of what happens in the callee under the assumption that `in` held before `call`. - */ - def callToReturnFlow( - call: Statement, successor: Statement, in: Set[IFDSFact] - ): Set[IFDSFact] - - /** - * Computes the data flow for a summary edge of a native method call. - * - * @param call The statement, which invoked the call. - * @param calleeContext The method, called by `call`. - * @param successor The statement, which will be executed after the call. - * @param in Some facts valid before the `call`. - * @return The facts valid after the call, excluding the call-to-return flow. - */ - def nativeCall(call: Statement, calleeContext: Context, successor: Statement, in: Set[IFDSFact]): Set[IFDSFact] - - /** - * All declared methods in the project. - */ - final protected[this] implicit val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) - - /** - * The entry points of this analysis. - */ - val entryPoints: Map[DeclaredMethod, IFDSFact] - - /** - * The state of the analysis. For each method and source fact, there is a separate state. - * - * @param declaringClass The class defining the analyzed `method`. - * @param method The analyzed method. - * @param source A fact, that holds at the beginning of `method`. - * @param code The code of `method`. - * @param cfg The control glow graph of `method`. - * @param pendingIfdsCallSites Maps callees of the analyzed `method` together with their input facts - * to the basic block and statement index of the call site(s). - * @param pendingIfdsDependees Maps callees of the analyzed `method` together with their input facts to the intermediate result of their IFDS analysis. - * Only contains method-fact-pairs, for which this analysis is waiting for a result. - * @param pendingCgCallSites The basic blocks containing call sites, for which the analysis is still waiting for the call graph result. - * @param cgDependency If present, the analysis is waiting for the `method`'s call graph. - * @param incomingFacts Maps each basic block to the data flow facts valid at its first statement. - * @param outgoingFacts Maps each basic block and successor node to the data flow facts valid at the beginning of the node. - */ - class State( - val declaringClass: ObjectType, - val context: Context, - val source: (Context, IFDSFact), - val code: Array[Stmt[V]], - val cfg: CFG[Stmt[V], TACStmts[V]], - var pendingIfdsCallSites: Map[(Context, IFDSFact), Set[(BasicBlock, Int)]], - var pendingIfdsDependees: Map[(Context, IFDSFact), EOptionP[(Context, IFDSFact), IFDSProperty[IFDSFact]]] = Map.empty, - var pendingCgCallSites: Set[BasicBlock] = Set.empty, - var cgDependency: Option[SomeEOptionP] = None, - var incomingFacts: Map[BasicBlock, Set[IFDSFact]] = Map.empty, - var outgoingFacts: Map[BasicBlock, Map[CFGNode, Set[IFDSFact]]] = Map.empty - ) - - implicit val typeProvider: TypeProvider = project.get(TypeProviderKey) - - /** - * Performs an IFDS analysis for a method-fact-pair. - * - * @param entity The method-fact-pair that will be analyzed. - * @return An IFDS property mapping from exit statements to the data flow facts valid after these exit statements. - * Returns an interim result, if the TAC or call graph of this method or the IFDS analysis for a callee is still pending. - */ - def performAnalysis(entity: (Context, IFDSFact)): ProperPropertyComputationResult = { - val (context, sourceFact) = entity - - val declaredMethod = context.method - - // The analysis can only handle single defined methods - // If a method is not single defined, this analysis assumes that it does not create any facts. - if (!declaredMethod.hasSingleDefinedMethod) - return Result(entity, createPropertyValue(Map.empty)); - - val method = declaredMethod.definedMethod - val declaringClass: ObjectType = method.classFile.thisType - - // Fetch the method's three address code. If it is not present, return an empty interim result. - val (code, cfg) = propertyStore(method, TACAI.key) match { - case FinalP(TheTACAI(tac)) => (tac.stmts, tac.cfg) - - case epk: EPK[Method, TACAI] => - return InterimResult.forUB( - entity, - createPropertyValue(Map.empty), - Set(epk), - _ => performAnalysis(entity) - ); - - case tac => - throw new UnknownError(s"can't handle intermediate TACs ($tac)"); - } - - // Start processing at the start of the cfg with the given source fact - implicit val state: State = - new State(declaringClass, context, entity, code, cfg, Map(entity -> Set.empty)) - val start = cfg.startBlock - state.incomingFacts += start -> Set(sourceFact) - process(mutable.Queue((start, Set(sourceFact), None, None, None))) - createResult() - } - - /** - * Analyzes a queue of BasicBlocks. - * - * @param worklist A queue of the following elements: - * bb The basic block that will be analyzed. - * in New data flow facts found to hold at the beginning of the basic block. - * calleeWithUpdateIndex If the basic block is analyzed because there is new information for a callee, this is the call site's index. - * calleeWithUpdate If the basic block is analyzed because there is new information for a callee, this is the callee. - * calleeWithUpdateFact If the basic block is analyzed because there is new information for a callee with a specific input fact, - * this is the input fact. - */ - def process( - worklist: mutable.Queue[(BasicBlock, Set[IFDSFact], Option[Int], Option[Context], Option[IFDSFact])] - )( - implicit - state: State - ): Unit = { - while (worklist.nonEmpty) { - val (basicBlock, in, calleeWithUpdateIndex, calleeWithUpdate, calleeWithUpdateFact) = - worklist.dequeue() - val oldOut = state.outgoingFacts.getOrElse(basicBlock, Map.empty) - val nextOut = - analyzeBasicBlock(basicBlock, in, calleeWithUpdateIndex, calleeWithUpdate, calleeWithUpdateFact) - val allOut = mergeMaps(oldOut, nextOut) - state.outgoingFacts = state.outgoingFacts.updated(basicBlock, allOut) - - for (successor <- basicBlock.successors) { - if (successor.isExitNode) { - // Re-analyze recursive call sites with the same input fact. - val nextOutSuccessors = nextOut.get(successor) - if (nextOutSuccessors.isDefined && nextOutSuccessors.get.nonEmpty) { - val oldOutSuccessors = oldOut.get(successor) - if (oldOutSuccessors.isEmpty || - nextOutSuccessors.get.exists(nos => !oldOutSuccessors.get.contains(nos))) { - val source = state.source - reAnalyzeCalls(state.pendingIfdsCallSites(source), source._1, Some(source._2)) - } - } - // if ((nextOut.getOrElse(successor, Set.empty) -- oldOut.getOrElse(successor, Set.empty)).nonEmpty) { - // val source = state.source - // reAnalyzeCalls(state.pendingIfdsCallSites(source), source._1.definedMethod, Some(source._2)) - // } - } else { - val actualSuccessor = - (if (successor.isBasicBlock) { - successor - } else { - // skip CatchNodes jump to their handler BasicBlock - successor.successors.head - }).asBasicBlock - - val nextIn = nextOut.getOrElse(actualSuccessor, Set.empty) - val oldIn = state.incomingFacts.getOrElse(actualSuccessor, Set.empty) - val mergedIn = if (nextIn.size > oldIn.size) nextIn ++ oldIn else oldIn ++ nextIn - val newIn = nextIn -- oldIn - state.incomingFacts = state.incomingFacts.updated(actualSuccessor, mergedIn) - /* - * Only process the successor with new facts. - * It is analyzed at least one time because of the null fact. - */ - if (newIn.nonEmpty) { - worklist.enqueue((actualSuccessor, newIn, None, None, None)) - } - } - } - } - } - - /** - * Collects the facts valid at an exit node based on the current results. - * - * @param exit The exit node. - * @return A map, mapping from each predecessor of the `exit` node to the facts valid at the `exit` node - * under the assumption that the predecessor was executed before. - */ - def collectResult(exit: CFGNode)(implicit state: State): Map[Statement, Set[IFDSFact]] = { - var result = Map.empty[Statement, Set[IFDSFact]] - exit.predecessors foreach { predecessor => - if (predecessor.isBasicBlock) { - val basicBlock = predecessor.asBasicBlock - // FIXME ... replace flatMap...isDefined by something that doesn't create intermediate data-structures - if (state.outgoingFacts.get(basicBlock).flatMap(_.get(exit)).isDefined) { - val lastIndex = basicBlock.endPC - val stmt = Statement(state.context, basicBlock, state.code(lastIndex), lastIndex, state.code, state.cfg) - result += stmt -> state.outgoingFacts(basicBlock)(exit) - } - } - } - result - } - - /** - * Creates the current (intermediate) result for the analysis. - * - * @return A result containing a map, which maps each exit statement to the facts valid after the statement, based on the current results. - * If the analysis is still waiting for its method's TAC or call graph or the IFDS of another method, an interim result will be returned. - * - */ - def createResult()(implicit state: State): ProperPropertyComputationResult = { - val propertyValue = createPropertyValue(mergeMaps( - collectResult(state.cfg.normalReturnNode), - collectResult(state.cfg.abnormalReturnNode) - )) - - var dependees: Set[SomeEOptionP] = state.pendingIfdsDependees.valuesIterator.toSet - // In the follwing, we really want to avoid useless copying of dependees: - if (state.cgDependency.isDefined) { - if (dependees.isEmpty) { - dependees = Set(state.cgDependency.get) - } else { - // We only implement what is required by the propery store/interface - new Iterable[SomeEOptionP] { - override def iterator: Iterator[SomeEOptionP] = { - // This method is actually not called by the property store... - Iterator.single(state.cgDependency.get) ++ dependees.iterator - } - override def foreach[U](f: SomeEOptionP => U): Unit = { - f(state.cgDependency.get) - dependees.foreach(f) - } - override def size: Int = dependees.size + 1 - override def isEmpty = false - } - } - } - - if (dependees.isEmpty) { - Result(state.source, propertyValue) - } else { - InterimResult.forUB(state.source, propertyValue, dependees, propertyUpdate) - } - } - - /** - * Called, when the call graph for this method or an IFDSProperty for another method was computed. - * Re-analyzes the relevant parts of this method and returns the new analysis result. - * - * @param eps The new property value. - * @return The new (interim) result of this analysis. - */ - def propertyUpdate(eps: SomeEPS)( - implicit - state: State - ): ProperPropertyComputationResult = { - (eps: @unchecked) match { - case FinalE(e: (Context, IFDSFact) @unchecked) => reAnalyzeCalls(state.pendingIfdsCallSites(e), e._1, Some(e._2)) - - case interimEUBP @ InterimEUBP(e: (Context, IFDSFact) @unchecked, ub: IFDSProperty[IFDSFact @unchecked]) => - if (ub.flows.values.forall(_.isInstanceOf[AbstractIFDSNullFact])) { - // Do not re-analyze the caller if we only get the null fact. - // Update the pendingIfdsDependee entry to the new interim result. - state.pendingIfdsDependees += e -> interimEUBP.asInstanceOf[EOptionP[(Context, IFDSFact), IFDSProperty[IFDSFact]]] - } else reAnalyzeCalls(state.pendingIfdsCallSites(e), e._1, Some(e._2)) - - case FinalEP(_: DefinedMethod, _: Callees) => - reAnalyzebasicBlocks(state.pendingCgCallSites) - - case InterimEUBP(_: DefinedMethod, _: Callees) => - reAnalyzebasicBlocks(state.pendingCgCallSites) - } - - createResult() - } - - /** - * Computes for one basic block the facts valid on each CFG edge leaving the block if `sources` hold before the block. - * - * @param basicBlock The basic block, that will be analyzed. - * @param in The facts, that hold before the block. - * @param calleeWithUpdateIndex If the basic block is analyzed because there is new information for a callee, this is the call site's index. - * @param calleeWithUpdate If the basic block is analyzed because there is new information for a callee, this is the callee. - * @param calleeWithUpdateFact If the basic block is analyzed because there is new information for a callee with a specific input fact, - * this is the input fact. - * @return A map, mapping each successor node to its input facts. Instead of catch nodes, this map contains their handler nodes. - */ - def analyzeBasicBlock( - basicBlock: BasicBlock, - in: Set[IFDSFact], - calleeWithUpdateIndex: Option[Int], - calleeWithUpdate: Option[Context], - calleeWithUpdateFact: Option[IFDSFact] - )( - implicit - state: State - ): Map[CFGNode, Set[IFDSFact]] = { - - /* - * Collects information about a statement. - * - * @param index The statement's index. - * @return A tuple of the following elements: - * statement: The statement at `index`. - * calees: The methods possibly called at this statement, if it contains a call. - * If `index` equals `calleeWithUpdateIndex`, only `calleeWithUpdate` will be returned. - * calleeFact: If `index` equals `calleeWithUpdateIndex`, only `calleeWithUpdateFact` will be returned, None otherwise. - */ - def collectInformation( - index: Int - ): (Statement, Option[SomeSet[Context]], Option[IFDSFact]) = { - val stmt = state.code(index) - val statement = Statement(state.context, basicBlock, stmt, index, state.code, state.cfg) - val calleesO = - if (calleeWithUpdateIndex.contains(index)) calleeWithUpdate.map(Set(_)) else getCalleesIfCallStatement(basicBlock, index) - val calleeFact = if (calleeWithUpdateIndex.contains(index)) calleeWithUpdateFact else None - (statement, calleesO, calleeFact) - } - - var flows: Set[IFDSFact] = in - var index = basicBlock.startPC - - // Iterate over all statements but the last one, only keeping the resulting DataFlowFacts. - while (index < basicBlock.endPC) { - val (statement, calleesO, calleeFact) = collectInformation(index) - flows = if (calleesO.isEmpty) { - val successor = - Statement(state.context, basicBlock, state.code(index + 1), index + 1, state.code, state.cfg) - normalFlow(statement, successor, flows) - } else - // Inside a basic block, we only have one successor --> Take the head - handleCall(basicBlock, statement, calleesO.get, flows, calleeFact).values.head - index += 1 - } - - // Analyze the last statement for each possible successor statement. - val (statement, calleesO, callFact) = collectInformation(basicBlock.endPC) - var result: Map[CFGNode, Set[IFDSFact]] = - if (calleesO.isEmpty) { - var result: Map[CFGNode, Set[IFDSFact]] = Map.empty - for (node <- basicBlock.successors) { - result += node -> normalFlow(statement, firstStatement(node), flows) - } - result - } else { - handleCall(basicBlock, statement, calleesO.get, flows, callFact).map(entry => entry._1.node -> entry._2) - } - - // Propagate the null fact. - result = result.map(result => result._1 -> (propagateNullFact(in, result._2))) - result - } - - /** - * Retrieves the expression of an assignment or expression statement. - * - * @param statement The statement. Must be an Assignment or ExprStmt. - * @return The statement's expression. - */ - def getExpression(statement: Stmt[V]): Expr[V] = statement.astID match { - case Assignment.ASTID => statement.asAssignment.expr - case ExprStmt.ASTID => statement.asExprStmt.expr - case _ => throw new UnknownError("Unexpected statement") - } - - /** - * Gets the set of all methods possibly called at some statement. - * - * @param basicBlock The basic block containing the statement. - * @param index The statement's index. - * @return All methods possibly called at the statement index or None, if the statement does not contain a call. - */ - def getCalleesIfCallStatement(basicBlock: BasicBlock, index: Int)(implicit state: State): Option[SomeSet[Context]] = { - val statement = state.code(index) - val pc = statement.pc - statement.astID match { - case StaticMethodCall.ASTID | NonVirtualMethodCall.ASTID | VirtualMethodCall.ASTID => Some(getCallees(basicBlock, pc)) - case Assignment.ASTID | ExprStmt.ASTID => getExpression(statement).astID match { - case StaticFunctionCall.ASTID | NonVirtualFunctionCall.ASTID | VirtualFunctionCall.ASTID => Some(getCallees(basicBlock, pc)) - case _ => None - } - case _ => None - } - } - - /** - * Gets the set of all methods possibly called at some call statement. - * - * @param basicBlock The basic block containing the call. - * @param pc The call's program counter. - * @return All methods possibly called at the statement index. - */ - def getCallees( - basicBlock: BasicBlock, pc: Int - )(implicit state: State): SomeSet[Context] = { - val ep = propertyStore(state.context.method, Callees.key) - ep match { - case FinalEP(_, p) => - state.cgDependency = None - state.pendingCgCallSites -= basicBlock - p.callees(state.source._1, pc).toSet - case InterimEUBP(_, p) => - addCgDependency(basicBlock, ep) - p.callees(state.source._1, pc).toSet - case _ => - addCgDependency(basicBlock, ep) - Set.empty - } - } - - /** - * Maps some declared methods to their defined methods. - * - * @param declaredMethods Some declared methods. - * @return All defined methods of `declaredMethods`. - */ - def definedMethods(declaredMethods: Iterator[Context]): SomeSet[Method] = { - val result = scala.collection.mutable.Set.empty[Method] - declaredMethods.map(_.method).filter { declaredMethod => - declaredMethod.hasSingleDefinedMethod || declaredMethod.hasMultipleDefinedMethods - }.foreach { declaredMethod => - declaredMethod.foreachDefinedMethod(defineMethod => result.add(defineMethod)) - } - result - } - - /** - * Sets the cgDependency to `ep` and adds the `basicBlock` to the pending cg call sites. - * - * @param basicBlock The basic block, which will be added to the pending cg call sites. - * @param ep The result of the call graph analysis. - */ - def addCgDependency(basicBlock: BasicBlock, ep: EOptionP[DeclaredMethod, Callees])(implicit state: State): Unit = { - state.cgDependency = Some(ep) - state.pendingCgCallSites += basicBlock - } - - /** - * Re-analyzes some basic blocks. - * - * @param basicBlocks The basic blocks, that will be re-analyzed. - */ - def reAnalyzebasicBlocks(basicBlocks: Set[BasicBlock])(implicit state: State): Unit = { - val queue: mutable.Queue[(BasicBlock, Set[IFDSFact], Option[Int], Option[Context], Option[IFDSFact])] = mutable.Queue.empty - for (bb <- basicBlocks) - queue.enqueue((bb, state.incomingFacts(bb), None, None, None)) - process(queue) - } - - /** - * Re-analyzes some call sites with respect to one specific callee. - * - * @param callSites The call sites, which are analyzed. - * @param callee The callee, which will be considered at the `callSites`. - * @param fact If defined, the `callee` will only be analyzed for this fact. - */ - def reAnalyzeCalls( - callSites: Set[(BasicBlock, Int)], - calleeContext: Context, - fact: Option[IFDSFact] - )(implicit state: State): Unit = { - val queue: mutable.Queue[(BasicBlock, Set[IFDSFact], Option[Int], Option[Context], Option[IFDSFact])] = - mutable.Queue.empty - for ((block, index) <- callSites) - queue.enqueue( - ( - block, - state.incomingFacts(block), - Some(index), - Some(calleeContext), - fact - ) - ) - process(queue) - } - - /** - * Processes a statement with a call. - * - * @param basicBlock The basic block that contains the statement - * @param call The call statement. - * @param callees All possible callees of the call. - * @param in The facts valid before the call statement. - * @param calleeWithUpdateFact If present, the `callees` will only be analyzed with this fact instead of the facts returned by callFlow. - * @return A map, mapping from each successor statement of the `call` to the facts valid at their start. - */ - def handleCall( - basicBlock: BasicBlock, - call: Statement, - callees: SomeSet[Context], - in: Set[IFDSFact], - calleeWithUpdateFact: Option[IFDSFact] - )( - implicit - state: State - ): Map[Statement, Set[IFDSFact]] = { - val successors = successorStatements(call, basicBlock) - // Facts valid at the start of each successor - var summaryEdges: Map[Statement, Set[IFDSFact]] = Map.empty - - // If calleeWithUpdateFact is present, this means that the basic block already has been analyzed with the `in` facts. - if (calleeWithUpdateFact.isEmpty) - for (successor <- successors) { - summaryEdges += successor -> propagateNullFact(in, callToReturnFlow(call, successor, in)) - } - - for (callee <- callees) { - if (callee.method.definedMethod.isNative) { - // We cannot analyze native methods. Let the concrete analysis decide what to do. - for { - successor <- successors - } { - summaryEdges += successor -> (summaryEdges(successor) ++ nativeCall(call, callee, successor, in)) - } - } else { - val callToStart = - if (calleeWithUpdateFact.isDefined) calleeWithUpdateFact.toSet - else propagateNullFact(in, callFlow(call, callee, in)) - var allNewExitFacts: Map[Statement, Set[IFDSFact]] = Map.empty - // Collect exit facts for each input fact separately - for (fact <- callToStart) { - /* - * If this is a recursive call with the same input facts, we assume that the call only produces the facts that are already known. - * The call site is added to `pendingIfdsCallSites`, so that it will be re-evaluated if new output facts become known for the input fact. - */ - if ((callee eq state.context) && fact == state.source._2) { - // FIXME Get rid of "getOrElse(...,Set.empty)" due to its potentially very BAD performance - val newDependee = - state.pendingIfdsCallSites.getOrElse(state.source, Set.empty) + ((basicBlock, call.index)) - state.pendingIfdsCallSites = state.pendingIfdsCallSites.updated(state.source, newDependee) - allNewExitFacts = mergeMaps( - allNewExitFacts, - mergeMaps( - collectResult(state.cfg.normalReturnNode), - collectResult(state.cfg.abnormalReturnNode) - ) - ) - } else { - val e = (callee, fact) - val callFlows = propertyStore(e, propertyKey.key) - .asInstanceOf[EOptionP[(Context, IFDSFact), IFDSProperty[IFDSFact]]] - val oldValue = state.pendingIfdsDependees.get(e) - val oldExitFacts: Map[Statement, Set[IFDSFact]] = oldValue match { - case Some(ep: InterimEUBP[_, IFDSProperty[IFDSFact]]) => ep.ub.flows - case _ => Map.empty - } - val exitFacts: Map[Statement, Set[IFDSFact]] = callFlows match { - case ep: FinalEP[_, IFDSProperty[IFDSFact]] => - // FIXME Get rid of "getOrElse(...,Set.empty)" due to its potentially very BAD performance - val newDependee = - state.pendingIfdsCallSites.getOrElse(e, Set.empty) - ((basicBlock, call.index)) - state.pendingIfdsCallSites = state.pendingIfdsCallSites.updated(e, newDependee) - state.pendingIfdsDependees -= e - ep.p.flows - case ep: InterimEUBP[_, IFDSProperty[IFDSFact]] => - /* - * Add the call site to `pendingIfdsCallSites` and `pendingIfdsDependees` and - * continue with the facts in the interim result for now. When the analysis for the - * callee finishes, the analysis for this call site will be triggered again. - */ - addIfdsDependee(e, callFlows, basicBlock, call.index) - ep.ub.flows - case _ => - addIfdsDependee(e, callFlows, basicBlock, call.index) - Map.empty - } - // Only process new facts that are not in `oldExitFacts` - allNewExitFacts = mergeMaps(allNewExitFacts, mapDifference(exitFacts, oldExitFacts)) - /* - * If new exit facts were discovered for the callee-fact-pair, all call sites depending on this pair have to be re-evaluated. - * oldValue is undefined if the callee-fact pair has not been queried before or returned a FinalEP. - */ - if (oldValue.isDefined && oldExitFacts != exitFacts) { - reAnalyzeCalls(state.pendingIfdsCallSites(e), e._1, Some(e._2)) - } - } - } - - //Create exit to return facts. At first for normal returns, then for abnormal returns. - for { - successor <- successors - if successor.node.isBasicBlock || successor.node.isNormalReturnExitNode - exitStatement <- allNewExitFacts.keys - if exitStatement.stmt.astID == Return.ASTID || exitStatement.stmt.astID == ReturnValue.ASTID - } { - // FIXME Get rid of "getOrElse(...,Set.empty)" due to its potentially very BAD performance - summaryEdges += successor -> (summaryEdges.getOrElse(successor, Set.empty[IFDSFact]) ++ - returnFlow(call, callee, exitStatement, successor, allNewExitFacts.getOrElse(exitStatement, Set.empty))) - } - for { - successor <- successors - if successor.node.isCatchNode || successor.node.isAbnormalReturnExitNode - exitStatement <- allNewExitFacts.keys - if exitStatement.stmt.astID != Return.ASTID && exitStatement.stmt.astID != ReturnValue.ASTID - } { - // FIXME Get rid of "getOrElse(...,Set.empty)" due to its potentially very BAD performance - summaryEdges += successor -> (summaryEdges.getOrElse(successor, Set.empty[IFDSFact]) ++ - returnFlow(call, callee, exitStatement, successor, allNewExitFacts.getOrElse(exitStatement, Set.empty))) - } - } - } - summaryEdges - } - - /** - * Determines the successor statements for one source statement. - * - * @param statement The source statement. - * @param basicBlock The basic block containing the source statement. - * @return All successors of `statement`. - */ - def successorStatements( - statement: Statement, - basicBlock: BasicBlock - )( - implicit - state: State - ): Set[Statement] = { - val index = statement.index - if (index == basicBlock.endPC) { - for (successorBlock <- basicBlock.successors) yield firstStatement(successorBlock) - } else { - val nextIndex = index + 1 - Set(Statement(statement.context, basicBlock, statement.code(nextIndex), nextIndex, statement.code, statement.cfg)) - } - } - - /** - * Adds a method-fact-pair as to the IFDS call sites and dependees. - * - * @param entity The method-fact-pair. - * @param calleeProperty The property, that was returned for `entity`. - * @param callBB The basic block of the call site. - * @param callIndex The index of the call site. - */ - def addIfdsDependee( - entity: (Context, IFDSFact), - calleeProperty: EOptionP[(Context, IFDSFact), IFDSProperty[IFDSFact]], - callBB: BasicBlock, - callIndex: Int - )( - implicit - state: State - ): Unit = { - val callSites = state.pendingIfdsCallSites - state.pendingIfdsCallSites = callSites.updated(entity, callSites.getOrElse(entity, Set.empty) + ((callBB, callIndex))) - state.pendingIfdsDependees += entity -> calleeProperty - } - - /** - * If `from` contains a null fact, it will be added to `to`. - * - * @param from The set, which may contain the null fact initially. - * @param to The set, to which the null fact may be added. - * @return `to` with the null fact added, if it is contained in `from`. - */ - def propagateNullFact(from: Set[IFDSFact], to: Set[IFDSFact]): Set[IFDSFact] = { - val nullFact = from.find(_.isInstanceOf[AbstractIFDSNullFact]) - if (nullFact.isDefined) to + nullFact.get - else to - } - - /** - * Merges two maps that have sets as values. - * - * @param map1 The first map. - * @param map2 The second map. - * @return A map containing the keys of both maps. Each key is mapped to the union of both maps' values. - */ - def mergeMaps[S, T](map1: Map[S, Set[T]], map2: Map[S, Set[T]]): Map[S, Set[T]] = { - var result = map1 - for ((key, values) <- map2) { - result.get(key) match { - case Some(resultValues) => - if (resultValues.size > values.size) - result = result.updated(key, resultValues ++ values) - else - result = result.updated(key, values ++ resultValues) - case None => - result = result.updated(key, values) - } - } - result - } - - /** - * Computes the difference of two maps that have sets as their values. - * - * @param minuend The map, from which elements will be removed. - * @param subtrahend The map, whose elements will be removed from `minuend`. - * @return A map, containing the keys and values of `minuend`. - * The values of the result only contain those elements not present in `subtrahend` for the same key. - */ - def mapDifference[S, T](minuend: Map[S, Set[T]], subtrahend: Map[S, Set[T]]): Map[S, Set[T]] = { - var result = minuend - for ((key, values) <- subtrahend) { - result = result.updated(key, result(key) -- values) - } - result - } - - /** - * Gets the call for a statement that contains a call. - * - * @param statement The statement. - * @return The call contained in `statement`. - */ - protected[this] def asCall(statement: Stmt[V]): Call[V] = statement.astID match { - case Assignment.ASTID => statement.asAssignment.expr.asFunctionCall - case ExprStmt.ASTID => statement.asExprStmt.expr.asFunctionCall - case _ => statement.asMethodCall - } - - /** - * Gets the first statement of a cfg node. - * - * @param node The node, for which the first statement will be retrieved. - * @return If the `node` is a basic block, its first statement will be returned. - * If it is a catch node, the first statement of its handler will be returned. - * If it is an exit node an artificial statement without code will be returned. - */ - @tailrec - private def firstStatement(node: CFGNode)(implicit state: State): Statement = { - if (node.isBasicBlock) { - val index = node.asBasicBlock.startPC - Statement(state.context, node, state.code(index), index, state.code, state.cfg) - } else if (node.isCatchNode) { - firstStatement(node.successors.head) - } else if (node.isExitNode) { - Statement(state.context, node, null, 0, state.code, state.cfg) - } else throw new IllegalArgumentException(s"Unknown node type: $node") - } -} - -/** - * A statement that is passed to the concrete analysis. - * - * @param context The method containing the statement. - * @param node The basic block containing the statement. - * @param stmt The TAC statement. - * @param index The index of the Statement in the code. - * @param code The method's TAC code. - * @param cfg The method's CFG. - */ -case class Statement( - context: Context, - node: CFGNode, - stmt: Stmt[V], - index: Int, - code: Array[Stmt[V]], - cfg: CFG[Stmt[V], TACStmts[V]] -) { - - override def hashCode(): Int = context.hashCode() * 31 + index - - override def equals(o: Any): Boolean = { - o match { - case s: Statement => s.index == index && s.context == context - case _ => false - } - } - - override def toString: String = s"${context.method.definedMethod.toJava}" - -} - -object AbstractIFDSAnalysis { - - /** - * The type of the TAC domain. - */ - type V = DUVar[ValueInformation] -} - -abstract class IFDSAnalysis[IFDSFact <: AbstractIFDSFact] extends FPCFLazyAnalysisScheduler { - - final override type InitializationData = AbstractIFDSAnalysis[IFDSFact] - - def property: IFDSPropertyMetaInformation[IFDSFact] - - final override def derivesLazily: Some[PropertyBounds] = Some(PropertyBounds.ub(property)) - - override def requiredProjectInformation: ProjectInformationKeys = - Seq(DeclaredMethodsKey, TypeProviderKey) - - override val uses: Set[PropertyBounds] = Set(PropertyBounds.finalP(TACAI)) - - override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} - - /** - * Registers the analysis as a lazy computation, that is, the method - * will call `ProperytStore.scheduleLazyComputation`. - */ - final override def register( - p: SomeProject, - ps: PropertyStore, - analysis: AbstractIFDSAnalysis[IFDSFact] - ): FPCFAnalysis = { - ps.registerLazyPropertyComputation(property.key, analysis.performAnalysis) - analysis - } - - override def afterPhaseScheduling(ps: PropertyStore, analysis: FPCFAnalysis): Unit = { - val ifdsAnalysis = analysis.asInstanceOf[AbstractIFDSAnalysis[IFDSFact]] - for (e <- ifdsAnalysis.entryPoints) { ps.force(e, ifdsAnalysis.propertyKey.key) } - } - - override def afterPhaseCompletion( - p: SomeProject, - ps: PropertyStore, - analysis: FPCFAnalysis - ): Unit = {} - -} - diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/SerializationRelatedCallsAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/SerializationRelatedCallsAnalysis.scala index e6ae0cb7a4..78144c8fc8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/SerializationRelatedCallsAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/SerializationRelatedCallsAnalysis.scala @@ -84,7 +84,7 @@ class OOSWriteObjectAnalysis private[analyses] ( val parameters = Seq(receiverOption.flatMap(os => persistentUVar(os.asVar))) implicit val state: CGState[ContextType] = new CGState[ContextType]( - callerContext, FinalEP(callerContext.method.definedMethod, TheTACAI(tac)), + callerContext, FinalEP(callerContext.method.definedMethod, TheTACAI(tac)) ) handleOOSWriteObject(callerContext, param, pc, receiver, parameters, indirectCalls) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/Callable.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/Callable.scala new file mode 100644 index 0000000000..bc6f608ebe --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/Callable.scala @@ -0,0 +1,10 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds + +import org.opalj.br.Method +import org.opalj.ifds.Callable + +case class JavaMethod(method: Method) extends Callable { + override def name: String = method.name + override def signature: String = method.toJava +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/EvaluationRunner.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/EvaluationRunner.scala new file mode 100644 index 0000000000..5a93c5e49d --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/EvaluationRunner.scala @@ -0,0 +1,192 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds + +import java.io.File +import java.io.PrintWriter +import scala.language.existentials +import com.typesafe.config.ConfigValueFactory +import org.opalj.bytecode +import org.opalj.ifds.Statistics +import org.opalj.ifds.IFDSAnalysis +import org.opalj.ifds.IFDSAnalysisScheduler + +import org.opalj.util.Milliseconds +import org.opalj.util.PerformanceEvaluation.time +import org.opalj.fpcf.FinalEP +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.seq.PKESequentialPropertyStore +import org.opalj.br.fpcf.FPCFAnalysesManagerKey +import org.opalj.br.DeclaredMethod +import org.opalj.br.analyses.Project +import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.ai.domain.l0.PrimitiveTACAIDomain +import org.opalj.ai.domain.l2 +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey +import org.opalj.tac.cg.RTACallGraphKey +import org.opalj.tac.fpcf.properties.cg.Callers + +abstract class EvaluationRunner { + + protected def analysisClass: IFDSAnalysisScheduler[_, _, _] + + protected def printAnalysisResults(analysis: IFDSAnalysis[_, _, _], ps: PropertyStore): Unit = () + + protected def run( + debug: Boolean, + useL2: Boolean, + delay: Boolean, + evalSchedulingStrategies: Boolean, + evaluationFile: Option[File] + ): Unit = { + + if (debug) { + PropertyStore.updateDebug(true) + } + + def evalProject(p: SomeProject): (Milliseconds, Statistics, Option[Object]) = { + if (useL2) { + p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { + case None => Set(classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[_]]) + case Some(requirements) => + requirements + classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] + } + } else { + p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { + case None => Set(classOf[PrimitiveTACAIDomain]) + case Some(requirements) => requirements + classOf[PrimitiveTACAIDomain] + } + } + + val ps = p.get(PropertyStoreKey) + var analysisTime: Milliseconds = Milliseconds.None + p.get(RTACallGraphKey) + println("Start: "+new java.util.Date) + org.opalj.util.gc() + val analysis = + time { + p.get(FPCFAnalysesManagerKey).runAll(analysisClass)._2 + }(t => analysisTime = t.toMilliseconds).collect { + case (_, a: IFDSAnalysis[_, _, _]) => a + }.head + + printAnalysisResults(analysis, ps) + println(s"The analysis took $analysisTime.") + println( + ps.statistics.iterator + .map(_.toString()) + .toList + .sorted + .mkString("PropertyStore Statistics:\n\t", "\n\t", "\n") + ) + ( + analysisTime, + analysis.statistics, + additionalEvaluationResult(analysis) + ) + } + + val p = Project(bytecode.RTJar) + + if (delay) { + println("Sleeping for three seconds.") + Thread.sleep(3000) + } + + if (evalSchedulingStrategies) { + val results = for { + i <- 1 to EvaluationRunner.NUM_EXECUTIONS_EVAL_SCHEDULING_STRATEGIES + strategy <- PKESequentialPropertyStore.Strategies + } yield { + println(s"Round: $i - $strategy") + val strategyValue = ConfigValueFactory.fromAnyRef(strategy) + val newConfig = + p.config.withValue(PKESequentialPropertyStore.TasksManagerKey, strategyValue) + val evaluationResult = evalProject(Project.recreate(p, newConfig)) + org.opalj.util.gc() + (i, strategy, evaluationResult._1, evaluationResult._2) + } + println(results.mkString("AllResults:\n\t", "\n\t", "\n")) + if (evaluationFile.nonEmpty) { + val pw = new PrintWriter(evaluationFile.get) + PKESequentialPropertyStore.Strategies.foreach { strategy => + val strategyResults = results.filter(_._2 == strategy) + val averageTime = strategyResults.map(_._3.timeSpan).sum / strategyResults.size + val (normalFlow, callToStart, exitToReturn, callToReturn) = + computeAverageStatistics(strategyResults.map(_._4)) + pw.println(s"Strategy $strategy:") + pw.println(s"Average time: ${averageTime}ms") + pw.println(s"Average calls of normalFlow: $normalFlow") + pw.println(s"Average calls of callToStart: $callToStart") + pw.println(s"Average calls of exitToReturn: $exitToReturn") + pw.println(s"Average calls of callToReturn: $callToReturn") + pw.println() + } + pw.close() + } + } else { + var times = Seq.empty[Milliseconds] + var statistics = Seq.empty[Statistics] + var additionalEvaluationResults = Seq.empty[Object] + for { + _ <- 1 to EvaluationRunner.NUM_EXECUTIONS + } { + val evaluationResult = evalProject(Project.recreate(p)) + val additionalEvaluationResult = evaluationResult._3 + times :+= evaluationResult._1 + statistics :+= evaluationResult._2 + if (additionalEvaluationResult.isDefined) + additionalEvaluationResults :+= additionalEvaluationResult.get + } + if (evaluationFile.nonEmpty) { + val (normalFlow, callFlow, returnFlow, callToReturnFlow) = computeAverageStatistics( + statistics + ) + val time = times.map(_.timeSpan).sum / times.size + val pw = new PrintWriter(evaluationFile.get) + pw.println(s"Average time: ${time}ms") + pw.println(s"Average calls of normalFlow: $normalFlow") + pw.println(s"Average calls of callFlow: $callFlow") + pw.println(s"Average calls of returnFlow: $returnFlow") + pw.println(s"Average calls of callToReturnFlow: $callToReturnFlow") + if (additionalEvaluationResults.nonEmpty) + writeAdditionalEvaluationResultsToFile(pw, additionalEvaluationResults) + pw.close() + } + } + } + + protected def additionalEvaluationResult(analysis: IFDSAnalysis[_, _, _]): Option[Object] = None + + protected def writeAdditionalEvaluationResultsToFile( + writer: PrintWriter, + additionalEvaluationResults: Seq[Object] + ): Unit = {} + + protected def canBeCalledFromOutside( + method: DeclaredMethod, + propertyStore: PropertyStore + ): Boolean = + propertyStore(method, Callers.key) match { + // This is the case, if the method may be called from outside the library. + case FinalEP(_, p: Callers) => p.hasCallersWithUnknownContext + case _ => + throw new IllegalStateException( + "call graph mut be computed before the analysis starts" + ) + } + + private def computeAverageStatistics(statistics: Seq[Statistics]): (Int, Int, Int, Int) = { + val length = statistics.length + val normalFlow = statistics.map(_.normalFlow).sum / length + val callFlow = statistics.map(_.callFlow).sum / length + val returnFlow = statistics.map(_.returnFlow).sum / length + val callToReturnFlow = statistics.map(_.callToReturnFlow).sum / length + (normalFlow, callFlow, returnFlow, callToReturnFlow) + } +} + +object EvaluationRunner { + var NUM_EXECUTIONS = 10 + var NUM_EXECUTIONS_EVAL_SCHEDULING_STRATEGIES = 2 +} \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/ForwardICFG.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/ForwardICFG.scala new file mode 100644 index 0000000000..082b4901f8 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/ForwardICFG.scala @@ -0,0 +1,116 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds + +import org.opalj.br.{DeclaredMethod, Method} +import org.opalj.br.analyses.{DeclaredMethods, DeclaredMethodsKey, SomeProject} +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.fpcf.{FinalEP, PropertyStore} +import org.opalj.ifds.ICFG +import org.opalj.tac.cg.TypeProviderKey +import org.opalj.tac.{Assignment, DUVar, Expr, ExprStmt, LazyDetachedTACAIKey, NonVirtualFunctionCall, NonVirtualMethodCall, StaticFunctionCall, StaticMethodCall, Stmt, TACMethodParameter, TACode, VirtualFunctionCall, VirtualMethodCall} +import org.opalj.tac.fpcf.analyses.cg.TypeProvider +import org.opalj.tac.fpcf.properties.cg.Callees +import org.opalj.value.ValueInformation + +class ForwardICFG(implicit project: SomeProject) + extends ICFG[Method, JavaStatement] { + val tacai: Method => TACode[TACMethodParameter, DUVar[ValueInformation]] = project.get(LazyDetachedTACAIKey) + val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) + implicit val propertyStore: PropertyStore = project.get(PropertyStoreKey) + implicit val typeProvider: TypeProvider = project.get(TypeProviderKey) + + /** + * Determines the statements at which the analysis starts. + * + * @param callable The analyzed callable. + * @return The statements at which the analysis starts. + */ + override def startStatements(callable: Method): Set[JavaStatement] = { + val TACode(_, code, _, cfg, _) = tacai(callable) + Set(JavaStatement(callable, 0, code, cfg)) + } + + /** + * Determines the statement, that will be analyzed after some other `statement`. + * + * @param statement The source statement. + * @return The successor statements + */ + override def nextStatements(statement: JavaStatement): Set[JavaStatement] = { + statement.cfg + .successors(statement.index) + .map { index => JavaStatement(statement, index) } + } + + /** + * Gets the set of all methods possibly called at some statement. + * + * @param statement The statement. + * @return All callables possibly called at the statement or None, if the statement does not + * contain a call. + */ + override def getCalleesIfCallStatement(statement: JavaStatement): Option[collection.Set[Method]] = + statement.stmt.astID match { + case StaticMethodCall.ASTID | NonVirtualMethodCall.ASTID | VirtualMethodCall.ASTID => + Some(getCallees(statement)) + case Assignment.ASTID | ExprStmt.ASTID => + getExpression(statement.stmt).astID match { + case StaticFunctionCall.ASTID | NonVirtualFunctionCall.ASTID | VirtualFunctionCall.ASTID => + Some(getCallees(statement)) + case _ => None + } + case _ => None + } + + /** + * Retrieves the expression of an assignment or expression statement. + * + * @param statement The statement. Must be an Assignment or ExprStmt. + * @return The statement's expression. + */ + private def getExpression(statement: Stmt[_]): Expr[_] = statement.astID match { + case Assignment.ASTID => statement.asAssignment.expr + case ExprStmt.ASTID => statement.asExprStmt.expr + case _ => throw new UnknownError("Unexpected statement") + } + + private def getCallees(statement: JavaStatement): collection.Set[Method] = { + val pc = statement.stmt.pc + val caller = declaredMethods(statement.callable) + val ep = propertyStore(caller, Callees.key) + ep match { + case FinalEP(_, p) => definedMethods(p.directCallees(typeProvider.newContext(caller), pc).map(_.method)) + case _ => + throw new IllegalStateException( + "call graph must be computed before the analysis starts" + ) + } + } + + override def isExitStatement(statement: JavaStatement): Boolean = { + statement.index == statement.node.asBasicBlock.endPC && + statement.node.successors.exists(_.isExitNode) + } + + /** + * Maps some declared methods to their defined methods. + * + * @param declaredMethods Some declared methods. + * @return All defined methods of `declaredMethods`. + */ + private def definedMethods(declaredMethods: Iterator[DeclaredMethod]): collection.Set[Method] = { + val result = scala.collection.mutable.Set.empty[Method] + declaredMethods + .filter( + declaredMethod => + declaredMethod.hasSingleDefinedMethod || + declaredMethod.hasMultipleDefinedMethods + ) + .foreach( + declaredMethod => + declaredMethod + .foreachDefinedMethod(defineMethod => result.add(defineMethod)) + ) + result + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/IFDSBasedVariableTypeAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/IFDSBasedVariableTypeAnalysis.scala new file mode 100644 index 0000000000..3189b06fe3 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/IFDSBasedVariableTypeAnalysis.scala @@ -0,0 +1,106 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds + +import org.opalj.br.analyses.SomeProject +import org.opalj.fpcf.{PropertyBounds, PropertyKey, PropertyStore} +import org.opalj.ifds.{IFDSProperty, IFDSPropertyMetaInformation} + +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.cg.Callers +import java.io.{File, PrintWriter} + +import org.opalj.ifds.IFDSAnalysis +import org.opalj.ifds.IFDSAnalysisScheduler +import org.opalj.ifds.Statistics + +import org.opalj.br.Method +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.tac.cg.TypeProviderKey + +/** + * A variable type analysis implemented as an IFDS analysis. + * In contrast to an ordinary variable type analysis, which also determines types of fields, + * this analysis only determines the types of local variables. + * The subsuming taint can be mixed in to enable subsuming. + * + * @param project The analyzed project. + * @author Mario Trageser + */ +class IFDSBasedVariableTypeAnalysis(project: SomeProject, subsumeFacts: Boolean = false) + extends IFDSAnalysis()(project, new VariableTypeProblem(project, subsumeFacts), VTAResult) + +class IFDSBasedVariableTypeAnalysisScheduler(subsumeFacts: Boolean = false) extends IFDSAnalysisScheduler[VTAFact, Method, JavaStatement] { + override def init(p: SomeProject, ps: PropertyStore) = new IFDSBasedVariableTypeAnalysis(p, subsumeFacts) + override def property: IFDSPropertyMetaInformation[JavaStatement, VTAFact] = VTAResult + override val uses: Set[PropertyBounds] = Set(PropertyBounds.finalP(TACAI), PropertyBounds.finalP(Callers)) + override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey, TypeProviderKey, PropertyStoreKey) +} + +/** + * The IFDSProperty for this analysis. + */ +case class VTAResult(flows: Map[JavaStatement, Set[VTAFact]], debugData: Map[JavaStatement, Set[VTAFact]] = Map.empty) extends IFDSProperty[JavaStatement, VTAFact] { + + override type Self = VTAResult + override def create(result: Map[JavaStatement, Set[VTAFact]]): IFDSProperty[JavaStatement, VTAFact] = new VTAResult(result) + override def create(result: Map[JavaStatement, Set[VTAFact]], debugData: Map[JavaStatement, Set[VTAFact]]): IFDSProperty[JavaStatement, VTAFact] = new VTAResult(result, debugData) + + override def key: PropertyKey[VTAResult] = VTAResult.key +} + +object VTAResult extends IFDSPropertyMetaInformation[JavaStatement, VTAFact] { + + override type Self = VTAResult + override def create(result: Map[JavaStatement, Set[VTAFact]]): IFDSProperty[JavaStatement, VTAFact] = new VTAResult(result) + override def create(result: Map[JavaStatement, Set[VTAFact]], debugData: Map[JavaStatement, Set[VTAFact]]): IFDSProperty[JavaStatement, VTAFact] = new VTAResult(result, debugData) + + val key: PropertyKey[VTAResult] = PropertyKey.create("VTAnew", new VTAResult(Map.empty)) +} + +class IFDSBasedVariableTypeAnalysisRunner(subsumeFacts: Boolean = false) extends EvaluationRunner { + + override def analysisClass: IFDSBasedVariableTypeAnalysisScheduler = new IFDSBasedVariableTypeAnalysisScheduler(subsumeFacts) + + override protected def additionalEvaluationResult( + analysis: IFDSAnalysis[_, _, _] + ): Option[Object] = + if (analysis.ifdsProblem.subsumeFacts) Some(analysis.statistics) else None + + override protected def writeAdditionalEvaluationResultsToFile( + writer: PrintWriter, + additionalEvaluationResults: Seq[Object] + ): Unit = { + val numberOfSubsumptions = additionalEvaluationResults.map(_.asInstanceOf[Statistics]) + val length = additionalEvaluationResults.length + val tries = numberOfSubsumptions.map(_.subsumeTries).sum / length + val successes = numberOfSubsumptions.map(_.subsumptions).sum / length + writer.println(s"Average tries to subsume: $tries") + writer.println(s"Average successful subsumes: $successes") + } +} + +object IFDSBasedVariableTypeAnalysisRunner { + def main(args: Array[String]): Unit = { + if (args.contains("--help")) { + println("Potential parameters:") + println(" -seq (to use the SequentialPropertyStore)") + println(" -l2 (to use the l2 domain instead of the default l1 domain)") + println(" -delay (for a three seconds delay before the taint flow analysis is started)") + println(" -debug (for debugging mode in the property store)") + println(" -evalSchedulingStrategies (evaluates all available scheduling strategies)") + println(" -subsumeFacts (enables subsuming)") + println(" -f (Stores the average runtime to this file)") + } else { + val fileIndex = args.indexOf("-f") + new IFDSBasedVariableTypeAnalysisRunner(args.contains("-subsumeFacts")).run( + args.contains("-debug"), + args.contains("-l2"), + args.contains("-delay"), + args.contains("-evalSchedulingStrategies"), + if (fileIndex >= 0) Some(new File(args(fileIndex + 1))) else None + ) + } + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/JavaIFDSProblem.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/JavaIFDSProblem.scala new file mode 100644 index 0000000000..a869c5ff52 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/JavaIFDSProblem.scala @@ -0,0 +1,128 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds + +import org.opalj.br.Method +import org.opalj.br.analyses.SomeProject +import org.opalj.br.cfg.{CFG, CFGNode} +import org.opalj.ifds.Dependees.Getter +import org.opalj.ifds.{AbstractIFDSFact, IFDSProblem, Statement} +import org.opalj.tac.{Assignment, Call, DUVar, Expr, ExprStmt, Return, ReturnValue, Stmt, TACStmts} +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V +import org.opalj.value.ValueInformation + +/** + * A statement that is passed to the concrete analysis. + * + * @param method The method containing the statement. + * @param index The index of the Statement in the code. + * @param code The method's TAC code. + * @param cfg The method's CFG. + */ +case class JavaStatement( + method: Method, + index: Int, + code: Array[Stmt[V]], + cfg: CFG[Stmt[V], TACStmts[V]] +) extends Statement[Method, CFGNode] { + + override def hashCode(): Int = method.hashCode() * 31 + index + + override def equals(o: Any): Boolean = o match { + case s: JavaStatement => s.index == index && s.method == method + case _ => false + } + + override def toString: String = s"${method.signatureToJava(false)}[${index}]\n\t${stmt}\n\t${method.toJava}" + override def callable: Method = method + override def node: CFGNode = cfg.bb(index) + def stmt: Stmt[V] = code(index) +} + +object JavaStatement { + def apply(referenceStatement: JavaStatement, newIndex: Int): JavaStatement = + JavaStatement(referenceStatement.method, newIndex, referenceStatement.code, referenceStatement.cfg) +} + +abstract class JavaIFDSProblem[Fact <: AbstractIFDSFact](project: SomeProject) + extends IFDSProblem[Fact, Method, JavaStatement](new ForwardICFG()(project)) { + + override def needsPredecessor(statement: JavaStatement): Boolean = false + + /** + * Checks if the return flow is actually possible from the given exit statement to the given successor. + * This is used to filter flows of exceptions into normal code without being caught + * + * @param exit the exit statement of the returning method + * @param successor the successor statement of the call within the callee function + * @return whether successor might actually be the next statement after the exit statement + */ + protected def isPossibleReturnFlow(exit: JavaStatement, successor: JavaStatement): Boolean = { + (successor.node.isBasicBlock || successor.node.isNormalReturnExitNode) && + (exit.stmt.astID == Return.ASTID || exit.stmt.astID == ReturnValue.ASTID) || + (successor.node.isCatchNode || successor.node.isAbnormalReturnExitNode) && + (exit.stmt.astID != Return.ASTID && exit.stmt.astID != ReturnValue.ASTID) + } + + override def outsideAnalysisContext(callee: Method): Option[(JavaStatement, JavaStatement, Fact, Getter) => Set[Fact]] = callee.body.isDefined match { + case true => None + case false => Some((_: JavaStatement, _: JavaStatement, in: Fact, _: Getter) => Set(in)) + } +} + +object JavaIFDSProblem { + /** + * The type of the TAC domain. + */ + type V = DUVar[ValueInformation] + + /** + * Converts the index of a method's formal parameter to its tac index in the method's scope and + * vice versa. + * + * @param index The index of a formal parameter in the parameter list or of a variable. + * @param isStaticMethod States, whether the method is static. + * @return A tac index if a parameter index was passed or a parameter index if a tac index was + * passed. + */ + def switchParamAndVariableIndex(index: Int, isStaticMethod: Boolean): Int = + (if (isStaticMethod) -2 else -1) - index + + /** + * Gets the call object for a statement that contains a call. + * + * @param call The call statement. + * @return The call object for `call`. + */ + def asCall(call: Stmt[V]): Call[V] = call.astID match { + case Assignment.ASTID => call.asAssignment.expr.asFunctionCall + case ExprStmt.ASTID => call.asExprStmt.expr.asFunctionCall + case _ => call.asMethodCall + } + + /** + * Checks whether the callee's formal parameter is of a reference type. + */ + def isRefTypeParam(callee: Method, index: Int): Boolean = + if (index == -1) true + else { + val parameterOffset = if (callee.isStatic) 0 else 1 + callee.descriptor.parameterType( + switchParamAndVariableIndex(index, callee.isStatic) + - parameterOffset + ).isReferenceType + } + + val NO_MATCH = 256 + def getParameterIndex(allParamsWithIndex: Seq[(Expr[JavaIFDSProblem.V], Int)], index: Int): Int = { + getParameterIndex(allParamsWithIndex, index, isStaticMethod = false) + } + + def getParameterIndex(allParamsWithIndex: Seq[(Expr[JavaIFDSProblem.V], Int)], index: Int, isStaticMethod: Boolean): Int = { + allParamsWithIndex.find { + case (param, paramI) => param.asVar.definedBy.contains(index) + } match { + case Some((param, paramI)) => JavaIFDSProblem.switchParamAndVariableIndex(paramI, isStaticMethod) + case None => NO_MATCH + } + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/VariableTypeProblem.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/VariableTypeProblem.scala new file mode 100644 index 0000000000..399d46b729 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/VariableTypeProblem.scala @@ -0,0 +1,357 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds + +import org.opalj.br._ +import org.opalj.br.analyses.SomeProject +import org.opalj.collection.immutable.EmptyIntTrieSet +import org.opalj.ifds.AbstractIFDSFact + +import org.opalj.tac.fpcf.analyses.ifds.{JavaIFDSProblem => NewJavaIFDSProblem} +import org.opalj.tac._ +import org.opalj.value.ValueInformation +import scala.annotation.tailrec + +import org.opalj.ifds.AbstractIFDSNullFact +import org.opalj.ifds.Dependees.Getter + +import org.opalj.fpcf.FinalEP +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.tac.fpcf.properties.cg.Callers + +trait VTAFact extends AbstractIFDSFact +case object VTANullFact extends VTAFact with AbstractIFDSNullFact + +/** + * A possible run time type of a variable. + * + * @param definedBy The variable's definition site. + * @param t The variable's type. + * @param upperBound True, if the variable's type could also be every subtype of `t`. + */ +case class VariableType(definedBy: Int, t: ReferenceType, upperBound: Boolean) extends VTAFact { + + /** + * If this VariableType is an upper bound, it subsumes every subtype. + */ + override def subsumes(other: AbstractIFDSFact, project: SomeProject): Boolean = { + if (upperBound) other match { + case VariableType(definedByOther, tOther, _) if definedBy == definedByOther && t.isObjectType && tOther.isObjectType => + project.classHierarchy.isSubtypeOf(tOther.asObjectType, t.asObjectType) + case _ => false + } + else false + } +} + +/** + * A possible run time type of the receiver of a call. + * + * @param line The line of the call. + * @param t The callee's type. + * @param upperBound True, if the callee's type could also be every subtype of `t`. + */ +case class CalleeType(line: Int, t: ReferenceType, upperBound: Boolean) extends VTAFact { + + /** + * If this CalleeType is an upper bound, it subsumes every subtype. + */ + override def subsumes(other: AbstractIFDSFact, project: SomeProject): Boolean = { + if (upperBound) other match { + case CalleeType(lineOther, tOther, _) if line == lineOther && t.isObjectType && tOther.isObjectType => + tOther.asObjectType.isSubtypeOf(t.asObjectType)(project.classHierarchy) + case _ => false + } + else false + } +} + +class VariableTypeProblem(project: SomeProject, override val subsumeFacts: Boolean = false) extends JavaIFDSProblem[VTAFact](project) { + val propertyStore = project.get(PropertyStoreKey) + val declaredMethods = project.get(DeclaredMethodsKey) + + override def nullFact: VTAFact = VTANullFact + + /** + * The analysis starts with all public methods in java.lang or org.opalj. + */ + override def entryPoints: Seq[(Method, VTAFact)] = { + project.allProjectClassFiles + .filter(classInsideAnalysisContext) + .flatMap(classFile => classFile.methods) + .filter(isEntryPoint) + .flatMap(entryPointsForMethod) + } + + /** + * If a new object is instantiated and assigned to a variable or array, a new ValueType will be + * created for the assignment's target. + * If there is an assignment of a variable or array element, a new VariableType will be + * created for the assignment's target with the source's type. + * If there is a field read, a new VariableType will be created with the field's declared type. + */ + override def normalFlow( + statement: JavaStatement, + in: VTAFact, + predecessor: Option[JavaStatement] + ): Set[VTAFact] = { + val inSet = Set(in) + val stmt = statement.stmt + stmt.astID match { + case Assignment.ASTID => + // Add facts for the assigned variable. + inSet ++ newFacts(statement.method, statement.stmt.asAssignment.expr, statement.index, in) + case ArrayStore.ASTID => + /* +* Add facts for the array store, like it was a variable assignment. +* By doing so, we only want to get the variable's type. +* Then, we change the definedBy-index to the one of the array and wrap the variable's +* type with an array type. +* Note, that an array type may have at most 255 dimensions. +*/ + val flow = scala.collection.mutable.Set.empty[VTAFact] + flow ++= inSet + newFacts(statement.method, stmt.asArrayStore.value, statement.index, in).foreach { + case VariableType(_, t, upperBound) if !(t.isArrayType && t.asArrayType.dimensions <= 254) => + stmt.asArrayStore.arrayRef.asVar.definedBy + .foreach(flow += VariableType(_, ArrayType(t), upperBound)) + case _ => // Nothing to do + } + flow.toSet + // If the statement is neither an assignment, nor an array store, we just propagate our facts. + case _ => inSet + } + } + + /** + * For each variable, which can be passed as an argument to the call, a new VariableType is + * created for the callee context. + */ + override def callFlow( + call: JavaStatement, + callee: Method, + in: VTAFact + ): Set[VTAFact] = { + val inSet = Set(in) + val callObject = JavaIFDSProblem.asCall(call.stmt) + val allParams = callObject.allParams + // Iterate over all input facts and over all parameters of the call. + val flow = scala.collection.mutable.Set.empty[VTAFact] + inSet.foreach { + case VariableType(definedBy, t, upperBound) => + allParams.iterator.zipWithIndex.foreach { + /* +* We are only interested in a pair of a variable type and a parameter, if the +* variable and the parameter can refer to the same object. +*/ + case (parameter, parameterIndex) if parameter.asVar.definedBy.contains(definedBy) => + // If this is the case, create a new fact for the method's formal parameter. + flow += VariableType( + NewJavaIFDSProblem + .switchParamAndVariableIndex(parameterIndex, callee.isStatic), + t, + upperBound + ) + case _ => // Nothing to do + } + case _ => // Nothing to do + } + flow.toSet + } + + /** + * If the call is an instance call, new CalleeTypes will be created for the call, one for each + * VariableType, which could be the call's target. + */ + override def callToReturnFlow( + call: JavaStatement, + in: VTAFact, + successor: JavaStatement + ): Set[VTAFact] = { + val inSet = Set(in) + // Check, to which variables the callee may refer + val calleeDefinitionSites = JavaIFDSProblem.asCall(call.stmt).receiverOption + .map(callee => callee.asVar.definedBy) + .getOrElse(EmptyIntTrieSet) + val calleeTypeFacts = inSet.collect { + // If we know the variable's type, we also know on which type the call is performed. + case VariableType(index, t, upperBound) if calleeDefinitionSites.contains(index) => + CalleeType(call.index, t, upperBound) + } + if (inSet.size >= calleeTypeFacts.size) inSet ++ calleeTypeFacts + else calleeTypeFacts ++ inSet + } + + /** + * If the call returns a value which is assigned to a variable, a new VariableType will be + * created in the caller context with the returned variable's type. + */ + override def returnFlow( + exit: JavaStatement, + in: VTAFact, + call: JavaStatement, + callFact: VTAFact, + successor: JavaStatement + ): Set[VTAFact] = + // We only create a new fact, if the call returns a value, which is assigned to a variable. + if (exit.stmt.astID == ReturnValue.ASTID && call.stmt.astID == Assignment.ASTID) { + val inSet = Set(in) + val returnValue = exit.stmt.asReturnValue.expr.asVar + inSet.collect { + // If we know the type of the return value, we create a fact for the assigned variable. + case VariableType(definedBy, t, upperBound) if returnValue.definedBy.contains(definedBy) => + VariableType(call.index, t, upperBound) + } + } else Set.empty + + /** + * Only methods in java.lang and org.opalj are inside the analysis context. + * + * @param callee The callee. + * @return True, if the callee is inside the analysis context. + */ + override def outsideAnalysisContext(callee: Method): Option[OutsideAnalysisContextHandler] = + if (classInsideAnalysisContext(callee.classFile) && + super.outsideAnalysisContext(callee).isEmpty) + None + else { + Some(((call: JavaStatement, successor: JavaStatement, in: VTAFact, getter: Getter) => { + val returnType = callee.descriptor.returnType + if (call.stmt.astID == Assignment.ASTID && returnType.isReferenceType) { + Set(VariableType(call.index, returnType.asReferenceType, upperBound = true)) + } else Set.empty[VTAFact] + }): OutsideAnalysisContextHandler) + } + + /** + * When `normalFlow` reaches an assignment or array store, this method computes the new facts + * created by the statement. + * + * @param expression The source expression of the assignment or array store. + * @param statementIndex The statement's index. + * @param in The facts, which hold before the statement. + * @return The new facts created by the statement. + */ + private def newFacts( + method: Method, + expression: Expr[DUVar[ValueInformation]], + statementIndex: Int, + in: VTAFact + ): Iterator[VariableType] = { + val inSet = Set(in) + expression.astID match { + case New.ASTID => + inSet.iterator.collect { + // When a constructor is called, we always know the exact type. + case VTANullFact => + VariableType(statementIndex, expression.asNew.tpe, upperBound = false) + } + case Var.ASTID => + inSet.iterator.collect { + // When we know the source type, we also know the type of the assigned variable. + case VariableType(index, t, upperBound) if expression.asVar.definedBy.contains(index) => + VariableType(statementIndex, t, upperBound) + } + case ArrayLoad.ASTID => + inSet.iterator.collect { + // When we know the array's type, we also know the type of the loaded element. + case VariableType(index, t, upperBound) if isArrayOfObjectType(t) && + expression.asArrayLoad.arrayRef.asVar.definedBy.contains(index) => + VariableType(statementIndex, t.asArrayType.elementType.asReferenceType, upperBound) + } + case GetField.ASTID | GetStatic.ASTID => + val t = expression.asFieldRead.declaredFieldType + /* + * We do not track field types. So we must assume, that it contains any subtype of its + * compile time type. + */ + if (t.isReferenceType) + Iterator(VariableType(statementIndex, t.asReferenceType, upperBound = true)) + else Iterator.empty + case _ => Iterator.empty + } + } + + /** + * Checks, if some type is an array type containing an object type. + * + * @param t The type to be checked. + * @param includeObjectType If true, this method also returns true if `t` is an object type + * itself. + * + * @return True, if `t` is an array type of an object type. + */ + @tailrec private def isArrayOfObjectType( + t: FieldType, + includeObjectType: Boolean = false + ): Boolean = { + if (t.isArrayType) isArrayOfObjectType(t.asArrayType.elementType, includeObjectType = true) + else if (t.isObjectType && includeObjectType) true + else false + } + + /** + * Checks, if a class is inside the analysis context. + * By default, that are the packages java.lang and org.opalj. + * + * @param classFile The class, which is checked. + * @return True, if the class is inside the analysis context. + */ + private def classInsideAnalysisContext(classFile: ClassFile): Boolean = { + val fqn = classFile.fqn + fqn.startsWith("java/lang") || fqn.startsWith("org/opalj/fpcf/fixtures/vta") + } + + /** + * Checks, if a method is an entry point of this analysis. + * + * @param method The method to be checked. + * @return True, if this method is an entry point of the analysis. + */ + private def isEntryPoint(method: Method): Boolean = { + method.body.isDefined && canBeCalledFromOutside(method) + } + + /** + * Checks, if some `method` can be called from outside the library. + * The call graph must be computed, before this method may be invoked. + * + * @param method The method, which may be callable from outside. + * @return True, if `method` can be called from outside the library. + */ + private def canBeCalledFromOutside(method: Method): Boolean = { + val FinalEP(_, callers) = propertyStore(declaredMethods(method), Callers.key) + callers.hasCallersWithUnknownContext + } + + /** + * For an entry point method, this method computes all pairs (`method`, inputFact) where + * inputFact is a VariableType for one of the method's parameter with its compile time type as + * an upper bound. + * + * @param method The entry point method. + * + * @return All pairs (`method`, inputFact) where inputFact is a VariableType for one of the + * method's parameter with its compile time type as an upper bound. + */ + private def entryPointsForMethod(method: Method): Seq[(Method, VTAFact)] = { + // Iterate over all parameters, which have a reference type. + (method.descriptor.parameterTypes.zipWithIndex.collect { + case (t, index) if t.isReferenceType => + /* +* Create a fact for the parameter, which says, that the parameter may have any +* subtype of its compile time type. +*/ + VariableType( + NewJavaIFDSProblem.switchParamAndVariableIndex(index, method.isStatic), + t.asReferenceType, + upperBound = true + ) + /* +* In IFDS problems, we must always also analyze the null fact, because it creates the facts, +* which hold independently of other source facts. +* Map the input facts, in which we are interested, to a pair of the method and the fact. +*/ + } :+ VTANullFact).map(fact => (method, fact)) + } +} \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/AbstractIFDSAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/AbstractIFDSAnalysis.scala new file mode 100644 index 0000000000..f5ede93bed --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/AbstractIFDSAnalysis.scala @@ -0,0 +1,1085 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.old + +import com.typesafe.config.ConfigValueFactory +import org.opalj.ai.domain.l0.PrimitiveTACAIDomain +import org.opalj.ai.domain.l2 +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey +import org.opalj.br.analyses.{DeclaredMethods, DeclaredMethodsKey, Project, ProjectInformationKeys, SomeProject} +import org.opalj.br.cfg.{BasicBlock, CFG, CFGNode} +import org.opalj.br.fpcf.{FPCFAnalysesManagerKey, FPCFAnalysis, FPCFLazyAnalysisScheduler, PropertyStoreKey} +import org.opalj.br.{DeclaredMethod, DefinedMethod, Method, ObjectType} +import org.opalj.bytecode +import org.opalj.fpcf._ +import org.opalj.fpcf.seq.PKESequentialPropertyStore +import org.opalj.ifds.old.{IFDSProblem, NumberOfCalls, Subsumable} +import org.opalj.ifds.{AbstractIFDSFact, IFDSProperty, IFDSPropertyMetaInformation, Statement} +import org.opalj.tac.cg.{RTACallGraphKey, TypeProviderKey} +import org.opalj.tac.fpcf.analyses.cg.TypeProvider +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V +import org.opalj.tac.fpcf.analyses.ifds.{JavaStatement, old} +import org.opalj.tac.fpcf.properties.cg.{Callees, Callers} +import org.opalj.tac.fpcf.properties.{TACAI, TheTACAI} +import org.opalj.tac._ +import org.opalj.util.Milliseconds +import org.opalj.util.PerformanceEvaluation.time + +import java.io.{File, PrintWriter} +import javax.swing.JOptionPane +import scala.collection.{mutable, Set => SomeSet} + +/** + * + * @param ifdsProblem + * @param propertyKey Provides the concrete property key that must be unique for every distinct concrete analysis and the lower bound for the IFDSProperty. + * @tparam IFDSFact + */ +abstract class AbstractIFDSAnalysis[IFDSFact <: AbstractIFDSFact]( + val ifdsProblem: IFDSProblem[IFDSFact, DeclaredMethod, DeclaredMethodJavaStatement, CFGNode], + val propertyKey: IFDSPropertyMetaInformation[DeclaredMethodJavaStatement, IFDSFact] +) extends FPCFAnalysis + with Subsumable[DeclaredMethodJavaStatement, IFDSFact] { + + /** + * All declared methods in the project. + */ + implicit final protected val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) + + implicit final protected val typeProvider: TypeProvider = project.get(TypeProviderKey) + + /** + * Counts, how many times the abstract methods were called. + */ + var numberOfCalls = new NumberOfCalls() + + /** + * Counts, how many input facts are passed to callbacks. + */ + var sumOfInputFactsForCallbacks = 0L + + /** + * The state of the analysis. For each method and source fact, there is a separate state. + * + * @param declaringClass The class defining the analyzed `method`. + * @param method The analyzed method. + * @param source The input fact, for which the `method` is analyzed. + * @param code The code of the `method`. + * @param cfg The control flow graph of the `method`. + * @param pendingIfdsCallSites Maps callees of the analyzed `method` together with their input + * facts to the basic block and statement index, at which they may + * be called. + * @param pendingIfdsDependees Maps callees of the analyzed `method` together with their input + * facts to the intermediate result of their IFDS analysis. + * Only contains method-fact-pairs, for which this analysis is + * waiting for a final result. + * @param pendingCgCallSites The basic blocks containing call sites, for which the analysis is + * waiting for the final call graph result. + * @param incomingFacts Maps each basic block to the data flow facts valid at its first + * statement. + * @param outgoingFacts Maps each basic block and successor node to the data flow facts valid at + * the beginning of the successor. + */ + protected class State( + val declaringClass: ObjectType, + val method: Method, + val source: (DeclaredMethod, IFDSFact), + val code: Array[Stmt[V]], + val cfg: CFG[Stmt[V], TACStmts[V]], + var pendingIfdsCallSites: Map[(DeclaredMethod, IFDSFact), Set[(BasicBlock, Int)]], + var pendingTacCallSites: Map[DeclaredMethod, Set[BasicBlock]] = Map.empty, + var pendingIfdsDependees: Map[(DeclaredMethod, IFDSFact), EOptionP[(DeclaredMethod, IFDSFact), IFDSProperty[DeclaredMethodJavaStatement, IFDSFact]]] = Map.empty, + var pendingTacDependees: Map[Method, EOptionP[Method, TACAI]] = Map.empty, + var pendingCgCallSites: Set[BasicBlock] = Set.empty, + var incomingFacts: Map[BasicBlock, Set[IFDSFact]] = Map.empty, + var outgoingFacts: Map[BasicBlock, Map[CFGNode, Set[IFDSFact]]] = Map.empty + ) + + /** + * Determines the basic blocks, at which the analysis starts. + * + * @param sourceFact The source fact of the analysis. + * @param cfg The control flow graph of the analyzed method. + * @return The basic blocks, at which the analysis starts. + */ + protected def startBlocks(sourceFact: IFDSFact, cfg: CFG[Stmt[V], TACStmts[V]]): Set[BasicBlock] + + /** + * Collects the facts valid at all exit nodes based on the current results. + * + * @return A map, mapping from each predecessor of all exit nodes to the facts, which hold at + * the exit node under the assumption that the predecessor was executed before. + */ + protected def collectResult(implicit state: State): Map[DeclaredMethodJavaStatement, Set[IFDSFact]] + + /** + * Creates an IFDSProperty containing the result of this analysis. + * + * @param result Maps each exit statement to the facts, which hold after the exit statement. + * @return An IFDSProperty containing the `result`. + */ + protected def createPropertyValue( + result: Map[DeclaredMethodJavaStatement, Set[IFDSFact]] + ): IFDSProperty[DeclaredMethodJavaStatement, IFDSFact] = propertyKey.create(result) + + /** + * Determines the nodes, that will be analyzed after some `basicBlock`. + * + * @param basicBlock The basic block, that was analyzed before. + * @return The nodes, that will be analyzed after `basicBlock`. + */ + protected def nextNodes(basicBlock: BasicBlock): Set[CFGNode] + + /** + * Checks, if some `node` is the last node. + * + * @return True, if `node` is the last node, i.e. there is no next node. + */ + protected def isLastNode(node: CFGNode): Boolean + + /** + * If the passed `node` is a catch node, all successors of this catch node are determined. + * + * @param node The node. + * @return If the node is a catch node, all its successors will be returned. + * Otherwise, the node itself will be returned. + */ + protected def skipCatchNode(node: CFGNode): Set[BasicBlock] + + /** + * Determines the first index of some `basic block`, that will be analyzed. + * + * @param basicBlock The basic block. + * @return The first index of some `basic block`, that will be analyzed. + */ + protected def firstIndex(basicBlock: BasicBlock): Int + + /** + * Determines the last index of some `basic block`, that will be analzyed. + * + * @param basicBlock The basic block. + * @return The last index of some `basic block`, that will be analzyed. + */ + protected def lastIndex(basicBlock: BasicBlock): Int + + /** + * Determines the index, that will be analyzed after some other `index`. + * + * @param index The source index. + * @return The index, that will be analyzed after `index`. + */ + protected def nextIndex(index: Int): Int + + /** + * Gets the first statement of a node, that will be analyzed. + * + * @param node The node. + * @return The first statement of a node, that will be analyzed. + */ + protected def firstStatement(node: CFGNode)(implicit state: State): DeclaredMethodJavaStatement + + /** + * Determines the statement, that will be analyzed after some other `statement`. + * + * @param statement The source statement. + * @return The successor statements + */ + protected def nextStatements(statement: DeclaredMethodJavaStatement)(implicit state: State): Set[DeclaredMethodJavaStatement] + + /** + * Determines the facts, for which a `callee` is analyzed. + * + * @param call The call, which calls `callee`. + * @param callee The method, which is called by `call`. + * @param in The facts, which hold before the `call`. + * @return The facts, for which `callee` will be analyzed. + */ + protected def callToStartFacts(call: DeclaredMethodJavaStatement, callee: DeclaredMethod, in: Set[IFDSFact])( + implicit + state: State + ): Set[IFDSFact] + + /** + * Collects the exit facts of a `callee` and adds them to the `summaryEdges`. + * + * @param summaryEdges The current summary edges. They map successor statements of the `call` + * to facts, which hold before they are executed. + * @param successors The successor of `call`, which is considered. + * @param call The statement, which calls `callee`. + * @param callee The method, called by `call`. + * @param exitFacts Maps exit statements of the `callee` to the facts, which hold after them. + * @return The summary edges plus the exit to return facts for `callee` and `successor`. + */ + protected def addExitToReturnFacts( + summaryEdges: Map[DeclaredMethodJavaStatement, Set[IFDSFact]], + successors: Set[DeclaredMethodJavaStatement], + call: DeclaredMethodJavaStatement, + callee: DeclaredMethod, + exitFacts: Map[DeclaredMethodJavaStatement, Set[IFDSFact]] + )(implicit state: State): Map[DeclaredMethodJavaStatement, Set[IFDSFact]] + + /** + * Performs an IFDS analysis for a method-fact-pair. + * + * @param entity The method-fact-pair, that will be analyzed. + * @return An IFDS property mapping from exit statements to the facts valid after these exit + * statements. Returns an interim result, if the TAC or call graph of this method or the + * IFDS analysis for a callee is still pending. + */ + def performAnalysis(entity: (DeclaredMethod, IFDSFact)): ProperPropertyComputationResult = { + val (declaredMethod, sourceFact) = entity + + /* + * The analysis can only handle single defined methods. + * If a method is not single defined, this analysis assumes that it does not create any + * facts. + */ + if (!declaredMethod.hasSingleDefinedMethod) + return Result(entity, createPropertyValue(Map.empty)); + + ifdsProblem.specialCase(entity, propertyKey) match { + case Some(result) => return result; + case _ => + } + + val method = declaredMethod.definedMethod + val declaringClass: ObjectType = method.classFile.thisType + + /* + * Fetch the method's three address code. If it is not present, return an empty interim + * result. + */ + val (code, cfg) = propertyStore(method, TACAI.key) match { + case FinalP(TheTACAI(tac)) => (tac.stmts, tac.cfg) + + case epk: EPK[Method, TACAI] => + return InterimResult.forUB( + entity, + createPropertyValue(Map.empty), + Set(epk), + _ => performAnalysis(entity) + ); + + case tac => + throw new UnknownError(s"can't handle intermediate TACs ($tac)"); + } + + // Start processing at the start of the cfg with the given source fact + implicit val state: State = + new State(declaringClass, method, entity, code, cfg, Map(entity -> Set.empty), Map()) + val queue = mutable.Queue + .empty[(BasicBlock, Set[IFDSFact], Option[Int], Option[Method], Option[IFDSFact])] + startBlocks(sourceFact, cfg).foreach { start => + state.incomingFacts += start -> Set(sourceFact) + queue.enqueue((start, Set(sourceFact), None, None, None)) + } + process(queue) + createResult() + } + + /** + * Creates the current (intermediate) result for the analysis. + * + * @return A result containing a map, which maps each exit statement to the facts valid after + * the statement, based on the current results. If the analysis is still waiting for its + * method's TAC or call graph or the result of another method-fact-pair, an interim + * result will be returned. + * + */ + protected def createResult()(implicit state: State): ProperPropertyComputationResult = { + val propertyValue = createPropertyValue(collectResult) + val dependees = state.pendingIfdsDependees.values ++ state.pendingTacDependees.values + if (dependees.isEmpty) Result(state.source, propertyValue) + else InterimResult.forUB(state.source, propertyValue, dependees.toSet, propertyUpdate) + } + + /** + * Called, when there is an updated result for a tac, call graph or another method-fact-pair, + * for which the current analysis is waiting. Re-analyzes the relevant parts of this method and + * returns the new analysis result. + * + * @param eps The new property value. + * @return The new (interim) result of this analysis. + */ + protected def propertyUpdate( + eps: SomeEPS + )(implicit state: State): ProperPropertyComputationResult = { + (eps: @unchecked) match { + case FinalE(e: (DeclaredMethod, IFDSFact) @unchecked) => + reAnalyzeCalls(state.pendingIfdsCallSites(e), e._1.definedMethod, Some(e._2)) + + case interimEUBP @ InterimEUBP( + e: (DeclaredMethod, IFDSFact) @unchecked, + ub: IFDSProperty[DeclaredMethodJavaStatement, IFDSFact] @unchecked + ) => + if (ub.flows.values + .forall(facts => facts.size == 1 && facts.forall(_ == ifdsProblem.nullFact))) { + // Do not re-analyze the caller if we only get the null fact. + // Update the pendingIfdsDependee entry to the new interim result. + state.pendingIfdsDependees += + e -> interimEUBP + .asInstanceOf[EOptionP[(DeclaredMethod, IFDSFact), IFDSProperty[DeclaredMethodJavaStatement, IFDSFact]]] + } else + reAnalyzeCalls(state.pendingIfdsCallSites(e), e._1.definedMethod, Some(e._2)) + + case FinalEP(_: DefinedMethod, _: Callees) => + reAnalyzebasicBlocks(state.pendingCgCallSites) + + case InterimEUBP(_: DefinedMethod, _: Callees) => + reAnalyzebasicBlocks(state.pendingCgCallSites) + + case FinalEP(method: Method, _: TACAI) => + state.pendingTacDependees -= method + reAnalyzebasicBlocks(state.pendingTacCallSites(declaredMethods(method))) + } + + createResult() + } + + /** + * Called, when some new information is found at the last node of the method. + * This method can be overwritten by a subclass to perform additional actions. + * + * @param nextIn The input facts, which were found. + * @param oldIn The input facts, which were already known, if present. + */ + protected def foundNewInformationForLastNode( + nextIn: Set[IFDSFact], + oldIn: Option[Set[IFDSFact]], + state: State + ): Unit = {} + + /** + * Processes a statement with a call. + * + * @param basicBlock The basic block, which contains the `call`. + * @param call The call statement. + * @param callees All possible callees. + * @param in The facts, which hold before the call statement. + * @param calleeWithUpdateFact If present, the `callees` will only be analyzed with this fact + * instead of the facts returned by callToStartFacts. + * @return A map, mapping from each successor statement of the `call` to the facts, which hold + * at their start. + */ + protected def handleCall( + basicBlock: BasicBlock, + call: DeclaredMethodJavaStatement, + callees: SomeSet[Method], + in: Set[IFDSFact], + calleeWithUpdateFact: Option[IFDSFact] + )( + implicit + state: State + ): Map[DeclaredMethodJavaStatement, Set[IFDSFact]] = { + val successors = nextStatements(call) + val inputFacts = beforeHandleCall(call, in) + // Facts valid at the start of each successor + var summaryEdges: Map[DeclaredMethodJavaStatement, Set[IFDSFact]] = Map.empty + + /* + * If calleeWithUpdateFact is present, this means that the basic block already has been + * analyzed with the `inputFacts`. + */ + if (calleeWithUpdateFact.isEmpty) + for (successor <- successors) { + numberOfCalls.callToReturnFlow += 1 + sumOfInputFactsForCallbacks += in.size + summaryEdges += successor -> + propagateNullFact( + inputFacts, + ifdsProblem.callToReturnFlow(call, successor, inputFacts, state.source) + ) + } + + for (calledMethod <- callees) { + val callee = declaredMethods(calledMethod) + ifdsProblem.outsideAnalysisContext(callee) match { + case Some(handler) => + // Let the concrete analysis decide what to do. + for { + successor <- successors + } summaryEdges += + successor -> (summaryEdges(successor) ++ + handler(call, successor, in)) + case None => + val callToStart = + if (calleeWithUpdateFact.isDefined) Set(calleeWithUpdateFact.get) + else { + propagateNullFact(inputFacts, callToStartFacts(call, callee, inputFacts)) + } + var allNewExitFacts: Map[DeclaredMethodJavaStatement, Set[IFDSFact]] = Map.empty + // Collect exit facts for each input fact separately + for (fact <- callToStart) { + /* + * If this is a recursive call with the same input facts, we assume that the + * call only produces the facts that are already known. The call site is added to + * `pendingIfdsCallSites`, so that it will be re-evaluated if new output facts + * become known for the input fact. + */ + if ((calledMethod eq state.method) && fact == state.source._2) { + val newDependee = + if (state.pendingIfdsCallSites.contains(state.source)) + state.pendingIfdsCallSites(state.source) + + ((basicBlock, call.index)) + else Set((basicBlock, call.index)) + state.pendingIfdsCallSites = + state.pendingIfdsCallSites.updated(state.source, newDependee) + allNewExitFacts = mergeMaps(allNewExitFacts, collectResult) + } else { + val e = (callee, fact) + val callFlows = propertyStore(e, propertyKey.key) + .asInstanceOf[EOptionP[(DeclaredMethod, IFDSFact), IFDSProperty[DeclaredMethodJavaStatement, IFDSFact]]] + val oldValue = state.pendingIfdsDependees.get(e) + val oldExitFacts: Map[DeclaredMethodJavaStatement, Set[IFDSFact]] = oldValue match { + case Some(ep: InterimEUBP[_, IFDSProperty[DeclaredMethodJavaStatement, IFDSFact]]) => ep.ub.flows + case _ => Map.empty + } + val exitFacts: Map[DeclaredMethodJavaStatement, Set[IFDSFact]] = callFlows match { + case ep: FinalEP[_, IFDSProperty[DeclaredMethodJavaStatement, IFDSFact]] => + if (state.pendingIfdsCallSites.contains(e) + && state.pendingIfdsCallSites(e).nonEmpty) { + val newDependee = + state.pendingIfdsCallSites(e) - ((basicBlock, call.index)) + state.pendingIfdsCallSites = state.pendingIfdsCallSites.updated(e, newDependee) + } + state.pendingIfdsDependees -= e + ep.p.flows + case ep: InterimEUBP[_, IFDSProperty[DeclaredMethodJavaStatement, IFDSFact]] => + /* + * Add the call site to `pendingIfdsCallSites` and + * `pendingIfdsDependees` and continue with the facts in the interim + * result for now. When the analysis for the callee finishes, the + * analysis for this call site will be triggered again. + */ + addIfdsDependee(e, callFlows, basicBlock, call.index) + ep.ub.flows + case _ => + addIfdsDependee(e, callFlows, basicBlock, call.index) + Map.empty + } + // Only process new facts that are not in `oldExitFacts` + allNewExitFacts = mergeMaps( + allNewExitFacts, + filterNewInformation(exitFacts, oldExitFacts, project) + ) + /* + * If new exit facts were discovered for the callee-fact-pair, all call + * sites depending on this pair have to be re-evaluated. oldValue is + * undefined if the callee-fact pair has not been queried before or returned + * a FinalEP. + */ + if (oldValue.isDefined && oldExitFacts != exitFacts) { + reAnalyzeCalls( + state.pendingIfdsCallSites(e), + e._1.definedMethod, + Some(e._2) + ) + } + } + } + summaryEdges = + addExitToReturnFacts(summaryEdges, successors, call, callee, allNewExitFacts) + } + } + summaryEdges + } + + /** + * This method is called at the beginning of handleCall. + * A subclass can overwrite this method, to change the input facts of the call. + * + * @param call The call statement. + * @param in The input facts, which hold before the `call`. + * @return The changed set of input facts. + */ + protected def beforeHandleCall(call: DeclaredMethodJavaStatement, in: Set[IFDSFact]): Set[IFDSFact] = in + + /** + * Gets the set of all methods directly callable at some call statement. + * + * @param basicBlock The basic block containing the call. + * @param pc The call's program counter. + * @param caller The caller, performing the call. + * @return All methods directly callable at the statement index. + */ + protected def getCallees( + statement: DeclaredMethodJavaStatement, + caller: DeclaredMethod + ): Iterator[DeclaredMethod] = { + val pc = statement.code(statement.index).pc + val ep = propertyStore(caller, Callees.key) + ep match { + case FinalEP(_, p) => p.directCallees(typeProvider.newContext(caller), pc).map(_.method) + case _ => + throw new IllegalStateException( + "call graph mut be computed before the analysis starts" + ) + } + } + + /** + * Merges two maps that have sets as values. + * + * @param map1 The first map. + * @param map2 The second map. + * @return A map containing the keys of both maps. Each key is mapped to the union of both maps' + * values. + */ + protected def mergeMaps[S, T](map1: Map[S, Set[T]], map2: Map[S, Set[T]]): Map[S, Set[T]] = { + var result = map1 + for ((key, values) <- map2) { + result.get(key) match { + case Some(resultValues) => + if (resultValues.size > values.size) + result = result.updated(key, resultValues ++ values) + else + result = result.updated(key, values ++ resultValues) + case None => + result = result.updated(key, values) + } + } + result + } + + /** + * Analyzes a queue of BasicBlocks. + * + * @param worklist A queue of the following elements: + * bb The basic block that will be analyzed. + * in New data flow facts found to hold at the beginning of the basic block. + * calleeWithUpdateIndex If the basic block is analyzed because there is new information + * for a callee, this is the call site's index. + * calleeWithUpdate If the basic block is analyzed because there is new information for a + * callee, this is the callee. + * calleeWithUpdateFact If the basic block is analyzed because there is new information + * for a callee with a specific input fact, this is the input fact. + */ + private def process( + worklist: mutable.Queue[(BasicBlock, Set[IFDSFact], Option[Int], Option[Method], Option[IFDSFact])] + )(implicit state: State): Unit = { + while (worklist.nonEmpty) { + val (basicBlock, in, calleeWithUpdateIndex, calleeWithUpdate, calleeWithUpdateFact) = + worklist.dequeue() + val oldOut = state.outgoingFacts.getOrElse(basicBlock, Map.empty) + val nextOut = analyzeBasicBlock( + basicBlock, + in, + calleeWithUpdateIndex, + calleeWithUpdate, + calleeWithUpdateFact + ) + val allOut = mergeMaps(oldOut, nextOut).view.mapValues(facts => subsume(facts, project)).toMap + state.outgoingFacts = state.outgoingFacts.updated(basicBlock, allOut) + + for (successor <- nextNodes(basicBlock)) { + if (isLastNode(successor)) { + // Re-analyze recursive call sites with the same input fact. + val nextOutSuccessors = nextOut.get(successor) + if (nextOutSuccessors.isDefined && nextOutSuccessors.get.nonEmpty) { + val oldOutSuccessors = oldOut.get(successor) + if (oldOutSuccessors.isEmpty || containsNewInformation( + nextOutSuccessors.get, + oldOutSuccessors.get, + project + )) { + val source = state.source + foundNewInformationForLastNode( + nextOutSuccessors.get, + oldOutSuccessors, + state + ) + reAnalyzeCalls( + state.pendingIfdsCallSites(source), + source._1.definedMethod, + Some(source._2) + ) + } + } + } else { + val successorBlock = successor.asBasicBlock + val nextIn = nextOut.getOrElse(successorBlock, Set.empty) + val oldIn = state.incomingFacts.getOrElse(successorBlock, Set.empty) + val newIn = notSubsumedBy(nextIn, oldIn, project) + val mergedIn = + if (nextIn.size > oldIn.size) nextIn ++ oldIn + else oldIn ++ nextIn + state.incomingFacts = + state.incomingFacts.updated(successorBlock, subsume(mergedIn, project)) + /* + * Only process the successor with new facts. + * It is analyzed at least one time because of the null fact. + */ + if (newIn.nonEmpty) worklist.enqueue((successorBlock, newIn, None, None, None)) + } + } + } + } + + /** + * Computes for one basic block the facts valid on each CFG edge leaving the block if `sources` + * held before the block. + * + * @param basicBlock The basic block, that will be analyzed. + * @param in The facts, that hold before the block. + * @param calleeWithUpdateIndex If the basic block is analyzed because there is new information + * for a callee, this is the call site's index. + * @param calleeWithUpdate If the basic block is analyzed because there is new information for + * a callee, this is the callee. + * @param calleeWithUpdateFact If the basic block is analyzed because there is new information + * for a callee with a specific input fact, this is the input fact. + * @return A map, mapping each successor node to its input facts. Instead of catch nodes, this + * map contains their handler nodes. + */ + private def analyzeBasicBlock( + basicBlock: BasicBlock, + in: Set[IFDSFact], + calleeWithUpdateIndex: Option[Int], + calleeWithUpdate: Option[Method], + calleeWithUpdateFact: Option[IFDSFact] + )( + implicit + state: State + ): Map[CFGNode, Set[IFDSFact]] = { + + /* + * Collects information about a statement. + * + * @param index The statement's index. + * @return A tuple of the following elements: + * statement: The statement at `index`. + * calees: The methods possibly called at this statement, if it contains a call. + * If `index` equals `calleeWithUpdateIndex`, only `calleeWithUpdate` will + * be returned. + * calleeFact: If `index` equals `calleeWithUpdateIndex`, only + * `calleeWithUpdateFact` will be returned, None otherwise. + */ + def collectInformation( + index: Int + ): (DeclaredMethodJavaStatement, Option[SomeSet[Method]], Option[IFDSFact]) = { + val stmt = state.code(index) + val statement = old.DeclaredMethodJavaStatement(state.method, basicBlock, stmt, index, state.code, state.cfg, state.source._1) + val calleesO = + if (calleeWithUpdateIndex.contains(index)) calleeWithUpdate.map(Set(_)) + else getCalleesIfCallStatement(statement) + val calleeFact = + if (calleeWithUpdateIndex.contains(index)) calleeWithUpdateFact + else None + (statement, calleesO, calleeFact) + } + + val last = lastIndex(basicBlock) + var flows: Set[IFDSFact] = in + var index = firstIndex(basicBlock) + + // Iterate over all statements but the last one, only keeping the resulting DataFlowFacts. + while (index != last) { + val (statement, calleesO, calleeFact) = collectInformation(index) + val next = nextIndex(index) + flows = if (calleesO.isEmpty) { + val successor = + old.DeclaredMethodJavaStatement(state.method, basicBlock, state.code(next), next, state.code, state.cfg, state.source._1) + numberOfCalls.normalFlow += 1 + sumOfInputFactsForCallbacks += in.size + ifdsProblem.normalFlow(statement, Some(successor), flows) + } else + // Inside a basic block, we only have one successor --> Take the head + handleCall(basicBlock, statement, calleesO.get, flows, calleeFact).values.head + index = next + } + + // Analyze the last statement for each possible successor statement. + val (statement, calleesO, callFact) = collectInformation(last) + var result: Map[CFGNode, Set[IFDSFact]] = + if (calleesO.isEmpty) { + var result: Map[CFGNode, Set[IFDSFact]] = Map.empty + for (node <- nextNodes(basicBlock)) { + numberOfCalls.normalFlow += 1 + sumOfInputFactsForCallbacks += in.size + result += node -> ifdsProblem.normalFlow(statement, Some(firstStatement(node)), flows) + } + result + } else + handleCall(basicBlock, statement, calleesO.get, flows, callFact) + .map(entry => entry._1.node -> entry._2) + + // Propagate the null fact. + result = result.map(result => result._1 -> propagateNullFact(in, result._2)) + result + } + + /** + * Re-analyzes some basic blocks. + * + * @param basicBlocks The basic blocks, that will be re-analyzed. + */ + private def reAnalyzebasicBlocks(basicBlocks: Set[BasicBlock])(implicit state: State): Unit = { + val queue: mutable.Queue[(BasicBlock, Set[IFDSFact], Option[Int], Option[Method], Option[IFDSFact])] = mutable.Queue.empty + for (bb <- basicBlocks) queue.enqueue((bb, state.incomingFacts(bb), None, None, None)) + process(queue) + } + + /** + * Re-analyzes some call sites with respect to one specific callee. + * + * @param callSites The call sites, which are analyzed. + * @param callee The callee, which will be considered at the `callSites`. + * @param fact If defined, the `callee` will only be analyzed for this fact. + */ + private def reAnalyzeCalls( + callSites: Set[(BasicBlock, Int)], + callee: Method, + fact: Option[IFDSFact] + )(implicit state: State): Unit = { + val queue: mutable.Queue[(BasicBlock, Set[IFDSFact], Option[Int], Option[Method], Option[IFDSFact])] = mutable.Queue.empty + for ((block, index) <- callSites) + queue.enqueue((block, state.incomingFacts(block), Some(index), Some(callee), fact)) + process(queue) + } + + /** + * Gets the set of all methods possibly called at some statement. + * + * @param statement The statement. + * @return All methods possibly called at the statement or None, if the statement does not + * contain a call. + */ + private def getCalleesIfCallStatement(statement: DeclaredMethodJavaStatement)( + implicit + state: State + ): Option[SomeSet[Method]] = { + statement.stmt.astID match { + case StaticMethodCall.ASTID | NonVirtualMethodCall.ASTID | VirtualMethodCall.ASTID => + Some(definedMethods(getCallees(statement, state.source._1))) + case Assignment.ASTID | ExprStmt.ASTID => + getExpression(statement.stmt).astID match { + case StaticFunctionCall.ASTID | NonVirtualFunctionCall.ASTID | VirtualFunctionCall.ASTID => + Some(definedMethods(getCallees(statement, state.source._1))) + case _ => None + } + case _ => None + } + } + + /** + * Maps some declared methods to their defined methods. + * + * @param declaredMethods Some declared methods. + * @return All defined methods of `declaredMethods`. + */ + private def definedMethods(declaredMethods: Iterator[DeclaredMethod]): SomeSet[Method] = { + val result = scala.collection.mutable.Set.empty[Method] + declaredMethods + .filter( + declaredMethod => + declaredMethod.hasSingleDefinedMethod || + declaredMethod.hasMultipleDefinedMethods + ) + .foreach( + declaredMethod => + declaredMethod + .foreachDefinedMethod(defineMethod => result.add(defineMethod)) + ) + result + } + + /** + * Retrieves the expression of an assignment or expression statement. + * + * @param statement The statement. Must be an Assignment or ExprStmt. + * @return The statement's expression. + */ + private def getExpression(statement: Stmt[V]): Expr[V] = statement.astID match { + case Assignment.ASTID => statement.asAssignment.expr + case ExprStmt.ASTID => statement.asExprStmt.expr + case _ => throw new UnknownError("Unexpected statement") + } + + /** + * If `from` contains a null fact, it will be added to `to`. + * + * @param from The set, which may contain the null fact initially. + * @param to The set, to which the null fact may be added. + * @return `to` with the null fact added, if it is contained in `from`. + */ + private def propagateNullFact(from: Set[IFDSFact], to: Set[IFDSFact]): Set[IFDSFact] = { + if (from.contains(ifdsProblem.nullFact)) to + ifdsProblem.nullFact + else to + } + + /** + * Adds a method-fact-pair as to the IFDS call sites and dependees. + * + * @param entity The method-fact-pair. + * @param calleeProperty The property, that was returned for `entity`. + * @param callBB The basic block of the call site. + * @param callIndex The index of the call site. + */ + private def addIfdsDependee( + entity: (DeclaredMethod, IFDSFact), + calleeProperty: EOptionP[(DeclaredMethod, IFDSFact), IFDSProperty[DeclaredMethodJavaStatement, IFDSFact]], + callBB: BasicBlock, + callIndex: Int + )(implicit state: State): Unit = { + val callSites = state.pendingIfdsCallSites + state.pendingIfdsCallSites = callSites.updated( + entity, + callSites.getOrElse(entity, Set.empty) + ((callBB, callIndex)) + ) + state.pendingIfdsDependees += entity -> calleeProperty + } +} + +object AbstractIFDSAnalysis { + /** + * When true, the cross product of exit and successor in returnFLow will be optimized. + */ + var OPTIMIZE_CROSS_PRODUCT_IN_RETURN_FLOW: Boolean = true +} + +/** + * A statement that is passed to the concrete analysis. + * + * @param method The method containing the statement. + * @param node The basic block containing the statement. + * @param stmt The TAC statement. + * @param index The index of the Statement in the code. + * @param code The method's TAC code. + * @param cfg The method's CFG. + */ +case class DeclaredMethodJavaStatement( + method: Method, + node: CFGNode, + stmt: Stmt[V], + index: Int, + code: Array[Stmt[V]], + cfg: CFG[Stmt[V], TACStmts[V]], + declaredMethod: DeclaredMethod +) extends Statement[DeclaredMethod, CFGNode] { + + override def hashCode(): Int = method.hashCode() * 31 + index + + override def equals(o: Any): Boolean = o match { + case s: DeclaredMethodJavaStatement => s.index == index && s.method == method + case _ => false + } + + override def toString: String = s"${method.toJava}" + override def callable: DeclaredMethod = declaredMethod + + def asJavaStatement: JavaStatement = JavaStatement(method, index, code, cfg) +} + +abstract class IFDSAnalysisScheduler[IFDSFact <: AbstractIFDSFact] + extends FPCFLazyAnalysisScheduler { + + final override type InitializationData = AbstractIFDSAnalysis[IFDSFact] + + def property: IFDSPropertyMetaInformation[DeclaredMethodJavaStatement, IFDSFact] + + final override def derivesLazily: Some[PropertyBounds] = Some(PropertyBounds.ub(property)) + + override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey) + + override val uses: Set[PropertyBounds] = Set(PropertyBounds.finalP(TACAI)) + + override def beforeSchedule(p: SomeProject, ps: PropertyStore): Unit = {} + + /** + * Registers the analysis as a lazy computation, that is, the method + * will call `ProperytStore.scheduleLazyComputation`. + */ + override def register( + p: SomeProject, + ps: PropertyStore, + analysis: AbstractIFDSAnalysis[IFDSFact] + ): FPCFAnalysis = { + ps.registerLazyPropertyComputation(property.key, analysis.performAnalysis) + analysis + } + + override def afterPhaseScheduling(ps: PropertyStore, analysis: FPCFAnalysis): Unit = { + val ifdsAnalysis = analysis.asInstanceOf[AbstractIFDSAnalysis[IFDSFact]] + for (e <- ifdsAnalysis.ifdsProblem.entryPoints) { ps.force(e, ifdsAnalysis.propertyKey.key) } + } + + override def afterPhaseCompletion( + p: SomeProject, + ps: PropertyStore, + analysis: FPCFAnalysis + ): Unit = {} + +} + +abstract class AbsractIFDSAnalysisRunner { + + protected def analysisClass: IFDSAnalysisScheduler[_] + + protected def printAnalysisResults(analysis: AbstractIFDSAnalysis[_], ps: PropertyStore): Unit + + protected def run( + debug: Boolean, + useL2: Boolean, + delay: Boolean, + evalSchedulingStrategies: Boolean, + evaluationFile: Option[File] + ): Unit = { + + if (debug) { + PropertyStore.updateDebug(true) + } + + def evalProject(p: SomeProject): (Milliseconds, NumberOfCalls, Option[Object], Long) = { + if (useL2) { + p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { + case None => Set(classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[_]]) + case Some(requirements) => + requirements + classOf[l2.DefaultPerformInvocationsDomainWithCFGAndDefUse[_]] + } + } else { + p.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { + case None => Set(classOf[PrimitiveTACAIDomain]) + case Some(requirements) => requirements + classOf[PrimitiveTACAIDomain] + } + } + + val ps = p.get(PropertyStoreKey) + var analysisTime: Milliseconds = Milliseconds.None + p.get(RTACallGraphKey) + println("Start: "+new java.util.Date) + org.opalj.util.gc() + if (AbstractIFDSAnalysisRunner.MEASURE_MEMORY) + JOptionPane.showMessageDialog(null, "Call Graph finished") + val analysis = + time { + p.get(FPCFAnalysesManagerKey).runAll(analysisClass)._2 + }(t => analysisTime = t.toMilliseconds).collect { + case (_, a: AbstractIFDSAnalysis[_]) => a + }.head + if (AbstractIFDSAnalysisRunner.MEASURE_MEMORY) + JOptionPane.showMessageDialog(null, "Analysis finished") + + printAnalysisResults(analysis, ps) + println(s"The analysis took $analysisTime.") + println( + ps.statistics.iterator + .map(_.toString()) + .toList + .sorted + .mkString("PropertyStore Statistics:\n\t", "\n\t", "\n") + ) + ( + analysisTime, + analysis.numberOfCalls, + additionalEvaluationResult(analysis), + analysis.sumOfInputFactsForCallbacks + ) + } + + val p = Project(bytecode.RTJar) + + if (delay) { + println("Sleeping for three seconds.") + Thread.sleep(3000) + } + + if (evalSchedulingStrategies) { + val results = for { + i <- 1 to AbstractIFDSAnalysisRunner.NUM_EXECUTIONS_EVAL_SCHEDULING_STRATEGIES + strategy <- PKESequentialPropertyStore.Strategies + } yield { + println(s"Round: $i - $strategy") + val strategyValue = ConfigValueFactory.fromAnyRef(strategy) + val newConfig = + p.config.withValue(PKESequentialPropertyStore.TasksManagerKey, strategyValue) + val evaluationResult = evalProject(Project.recreate(p, newConfig)) + org.opalj.util.gc() + (i, strategy, evaluationResult._1, evaluationResult._2) + } + println(results.mkString("AllResults:\n\t", "\n\t", "\n")) + if (evaluationFile.nonEmpty) { + val pw = new PrintWriter(evaluationFile.get) + PKESequentialPropertyStore.Strategies.foreach { strategy => + val strategyResults = results.filter(_._2 == strategy) + val averageTime = strategyResults.map(_._3.timeSpan).sum / strategyResults.size + val (normalFlow, callToStart, exitToReturn, callToReturn) = + computeAverageNumberCalls(strategyResults.map(_._4)) + pw.println(s"Strategy $strategy:") + pw.println(s"Average time: ${averageTime}ms") + pw.println(s"Average calls of normalFlow: $normalFlow") + pw.println(s"Average calls of callToStart: $callToStart") + pw.println(s"Average calls of exitToReturn: $exitToReturn") + pw.println(s"Average calls of callToReturn: $callToReturn") + pw.println() + } + pw.close() + } + } else { + var times = Seq.empty[Milliseconds] + var numberOfCalls = Seq.empty[NumberOfCalls] + var sumsOfInputFactsForCallbacks = Seq.empty[Long] + var additionalEvaluationResults = Seq.empty[Object] + for { + _ <- 1 to AbstractIFDSAnalysisRunner.NUM_EXECUTIONS + } { + val evaluationResult = evalProject(Project.recreate(p)) + val additionalEvaluationResult = evaluationResult._3 + times :+= evaluationResult._1 + numberOfCalls :+= evaluationResult._2 + sumsOfInputFactsForCallbacks :+= evaluationResult._4 + if (additionalEvaluationResult.isDefined) + additionalEvaluationResults :+= additionalEvaluationResult.get + } + if (evaluationFile.nonEmpty) { + val (normalFlow, callFlow, returnFlow, callToReturnFlow) = computeAverageNumberCalls( + numberOfCalls + ) + val time = times.map(_.timeSpan).sum / times.size + val sumOfInputFactsForCallbacks = sumsOfInputFactsForCallbacks.sum / sumsOfInputFactsForCallbacks.size + val pw = new PrintWriter(evaluationFile.get) + pw.println(s"Average time: ${time}ms") + pw.println(s"Average calls of normalFlow: $normalFlow") + pw.println(s"Average calls of callFlow: $callFlow") + pw.println(s"Average calls of returnFlow: $returnFlow") + pw.println(s"Average calls of callToReturnFlow: $callToReturnFlow") + pw.println(s"Sum of input facts for callbacks: $sumOfInputFactsForCallbacks") + if (additionalEvaluationResults.nonEmpty) + writeAdditionalEvaluationResultsToFile(pw, additionalEvaluationResults) + pw.close() + } + } + } + + protected def additionalEvaluationResult(analysis: AbstractIFDSAnalysis[_]): Option[Object] = None + + protected def writeAdditionalEvaluationResultsToFile( + writer: PrintWriter, + additionalEvaluationResults: Seq[Object] + ): Unit = {} + + protected def canBeCalledFromOutside( + method: DeclaredMethod, + propertyStore: PropertyStore + ): Boolean = + propertyStore(method, Callers.key) match { + // This is the case, if the method may be called from outside the library. + case FinalEP(_, p: Callers) => p.hasCallersWithUnknownContext + case _ => + throw new IllegalStateException( + "call graph mut be computed before the analysis starts" + ) + } + + private def computeAverageNumberCalls(numberOfCalls: Seq[NumberOfCalls]): (Int, Int, Int, Int) = { + val length = numberOfCalls.length + val normalFlow = numberOfCalls.map(_.normalFlow).sum / length + val callFlow = numberOfCalls.map(_.callFlow).sum / length + val returnFlow = numberOfCalls.map(_.returnFlow).sum / length + val callToReturnFlow = numberOfCalls.map(_.callToReturnFlow).sum / length + (normalFlow, callFlow, returnFlow, callToReturnFlow) + } +} + +object AbstractIFDSAnalysisRunner { + var NUM_EXECUTIONS = 10 + var NUM_EXECUTIONS_EVAL_SCHEDULING_STRATEGIES = 2 + var MEASURE_MEMORY = false +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/BackwardIFDSAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/BackwardIFDSAnalysis.scala new file mode 100644 index 0000000000..1c518dbc6b --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/BackwardIFDSAnalysis.scala @@ -0,0 +1,451 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.old + +import org.opalj.br.DeclaredMethod +import org.opalj.br.cfg.{BasicBlock, CFG, CFGNode, CatchNode} +import org.opalj.fpcf._ +import org.opalj.ifds.old.IFDSProblem +import org.opalj.ifds.{AbstractIFDSFact, IFDSProperty, IFDSPropertyMetaInformation} +import org.opalj.tac._ +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V +import org.opalj.tac.fpcf.analyses.ifds.old +import org.opalj.tac.fpcf.properties.cg.Callers +import org.opalj.tac.fpcf.properties.{TACAI, TheTACAI} +import org.opalj.value.ValueInformation + +import scala.annotation.tailrec + +/** + * An IFDS analysis, which analyzes the code against the control flow direction. + * + * @tparam UnbalancedIFDSFact The type of unbalanced return facts facts, which are tracked by the + * concrete analysis. + * @author Mario Trageser + */ +abstract class BackwardIFDSAnalysis[IFDSFact <: AbstractIFDSFact, UnbalancedIFDSFact <: IFDSFact with UnbalancedReturnFact[IFDSFact]](ifdsProblem: IFDSProblem[IFDSFact, DeclaredMethod, DeclaredMethodJavaStatement, CFGNode] with BackwardIFDSProblem[IFDSFact, UnbalancedIFDSFact, DeclaredMethod, DeclaredMethodJavaStatement], propertyKey: IFDSPropertyMetaInformation[DeclaredMethodJavaStatement, IFDSFact]) extends AbstractIFDSAnalysis[IFDSFact](ifdsProblem, propertyKey) { + /** + * If this method is analyzed for an unbalanced return fact, the single star block is the block, + * which contains the call. + * Otherwise, the start blocks are the return nodes of the method. + */ + override protected def startBlocks( + sourceFact: IFDSFact, + cfg: CFG[Stmt[V], TACStmts[V]] + ): Set[BasicBlock] = + sourceFact match { + case fact: UnbalancedReturnFact[IFDSFact @unchecked] => Set(cfg.bb(fact.index)) + case _ => + Set(cfg.normalReturnNode, cfg.abnormalReturnNode) + .flatMap(_.predecessors) + .foldLeft(Set.empty[BasicBlock])((c, n) => c + n.asBasicBlock) + } + + /** + * Collects the output facts of the entry point of the analyzed method. + */ + override protected def collectResult(implicit state: State): Map[DeclaredMethodJavaStatement, Set[IFDSFact]] = { + val startBlock = state.cfg.startBlock + val startPC = startBlock.startPC + val statement = + old.DeclaredMethodJavaStatement(state.method, startBlock, state.code(startPC), startPC, state.code, state.cfg, state.source._1) + val exitFacts = state.outgoingFacts.get(startBlock).flatMap(_.get(SyntheticStartNode)) + if (exitFacts.isDefined) Map(statement -> exitFacts.get) + else Map.empty + } + + /** + * If the update is for an IFDS entity with an unbalanced return fact, the IFDS dependency is + * updated if it is an interim result or removed if it is a final result. + * If the update is not for an unbalanced return fact, the update will be handled by + * AbstractIFDSAnalysis. + */ + override protected def propertyUpdate( + eps: SomeEPS + )(implicit state: State): ProperPropertyComputationResult = { + (eps: @unchecked) match { + /* + * If the analysis for the unbalanced return finished, remove the entity from the + * dependencies. + */ + case FinalE(e: (DeclaredMethod, IFDSFact) @unchecked) => + if (e._2.isInstanceOf[UnbalancedReturnFact[IFDSFact @unchecked]]) { + state.pendingIfdsDependees -= e + createResult() + } else super.propertyUpdate(eps) + /* + * If there is an interim result for an unbalanced return fact, ignore it and just create the result. + */ + case interimEUBP @ InterimEUBP( + e: (DeclaredMethod, IFDSFact) @unchecked, + _: IFDSProperty[DeclaredMethodJavaStatement, IFDSFact] @unchecked + ) => + if (e._2.isInstanceOf[UnbalancedReturnFact[IFDSFact @unchecked]]) { + state.pendingIfdsDependees += + e -> interimEUBP + .asInstanceOf[EOptionP[(DeclaredMethod, IFDSFact), IFDSProperty[DeclaredMethodJavaStatement, IFDSFact]]] + createResult() + } else super.propertyUpdate(eps) + + // If this is not an update for an unbalanced return fact, handle it as usual. + case _ => super.propertyUpdate(eps) + } + } + + /** + * If `basicBlock` is the method's entry point, the synthetic start node will be returned. + * Otherwise, its predecessors will be returned. + */ + override protected def nextNodes(basicBlock: BasicBlock): Set[CFGNode] = { + if (basicBlock.startPC == 0) Set(SyntheticStartNode) + else { + val predecessors = scala.collection.mutable.Set.empty[CFGNode] + basicBlock.predecessors.foreach { + case basicBlock: BasicBlock => predecessors += basicBlock + case catchNode: CatchNode => + predecessors ++= catchNode.predecessors.iterator.map(_.asBasicBlock) + } + predecessors.toSet + } + } + + /** + * The synthetic start node is the last node. + */ + override protected def isLastNode(node: CFGNode): Boolean = node == SyntheticStartNode + + /** + * When new output facts for the method's entry point are found, a concrete analysis may add + * additional facts. + * Then, all direct callers of the method are determined. For each caller, new unbalanced return + * facts are created and the caller entity will be added to the pending IFDS dependencies, so + * that the caller entity will be analyzed. + */ + override protected def foundNewInformationForLastNode( + nextIn: Set[IFDSFact], + oldIn: Option[Set[IFDSFact]], + state: State + ): Unit = { + var newIn = if (oldIn.isDefined) notSubsumedBy(nextIn, oldIn.get, project) else nextIn + val created = ifdsProblem.createFactsAtStartNode(newIn, state.source) + if (created.nonEmpty) { + // Add the created facts to newIn and update the outgoing facts in the state. + newIn = newIn ++ created + val startBlock = state.cfg.startBlock + val oldOut = state.outgoingFacts(startBlock) + val newOut = oldOut.updated(SyntheticStartNode, oldOut(SyntheticStartNode) ++ created) + state.outgoingFacts = state.outgoingFacts.updated(startBlock, newOut) + } + // Only create unbalanced returns, if we are at an entry point or in a (indirect) caller of it. + if (ifdsProblem.shouldPerformUnbalancedReturn(state.source)) { + // Get all callers of this method + propertyStore(state.source._1, Callers.key) match { + case FinalEP(_, p: Callers) => + p.callers(state.source._1).iterator.foreach { callersProperty => + val (caller, callPc, directCall) = callersProperty + // We do not handle indirect calls. + if (directCall) { + val definedCaller = caller.definedMethod + // Get the caller's tac to create the unbalanced return facts + val callerTac = propertyStore(definedCaller, TACAI.key) + callerTac match { + case FinalP(TheTACAI(tac)) => + addDependencyForUnbalancedReturn(caller, tac.pcToIndex(callPc), newIn, tac)(state) + case _ => + val pendingTacCallSites = state.pendingTacCallSites + state.pendingTacDependees += definedCaller -> callerTac + state.pendingTacCallSites = pendingTacCallSites.updated( + caller, + pendingTacCallSites.getOrElse(caller, Set.empty) + + state.cfg.startBlock + ) + } + } + + } + case _ => + throw new IllegalStateException( + "call graph mut be computed before the analysis starts" + ) + } + } + } + + /** + * If the node is a basic block, it will be returned. + * Otherwise, all predecessors of the node will be returned. + */ + override protected def skipCatchNode(node: CFGNode): Set[BasicBlock] = + if (node.isBasicBlock) Set(node.asBasicBlock) + else node.predecessors.map(_.asBasicBlock) + + /** + * The first index of a basic block is its end index. + */ + override protected def firstIndex(basicBlock: BasicBlock): Int = basicBlock.endPC + + /** + * The last index of a basic block ist its start index. + */ + override protected def lastIndex(basicBlock: BasicBlock): Int = basicBlock.startPC + + /** + * The next index against the control flow direction. + */ + override protected def nextIndex(index: Int): Int = index - 1 + + /** + * If the `node` is a basic block, its end statement will be returned. + * If it is a catch node, the first statement of its throwing block will be returned. + * If it is a synthetic start node, an artificial statement without code will be returned. + */ + override protected def firstStatement(node: CFGNode)(implicit state: State): DeclaredMethodJavaStatement = { + if (node.isBasicBlock) { + val index = node.asBasicBlock.endPC + old.DeclaredMethodJavaStatement(state.method, node, state.code(index), index, state.code, state.cfg, state.source._1) + } else if (node.isCatchNode) firstStatement(node.successors.head) + else if (node == SyntheticStartNode) + old.DeclaredMethodJavaStatement(state.method, node, null, 0, state.code, state.cfg, state.source._1) + else throw new IllegalArgumentException(s"Unknown node type: $node") + } + + /** + * The successor statements against the control flow direction. + */ + override protected def nextStatements( + statement: DeclaredMethodJavaStatement + )(implicit state: State): Set[DeclaredMethodJavaStatement] = { + val index = statement.index + val basicBlock = statement.node.asBasicBlock + if (index == 0) { + Set(firstStatement(SyntheticStartNode)) + } else if (index == basicBlock.startPC) + basicBlock.predecessors.map(firstStatement(_)) + else { + val nextIndex = index - 1 + Set( + old.DeclaredMethodJavaStatement( + statement.method, + basicBlock, + statement.code(nextIndex), + nextIndex, + statement.code, + statement.cfg, + statement.declaredMethod + ) + ) + } + } + + /** + * Determines the callees' exit statements and all successor statements in the control flow + * direction, which my be executed after them. Calls returnFlow on those pairs. + */ + override protected def callToStartFacts( + call: DeclaredMethodJavaStatement, + callee: DeclaredMethod, + in: Set[IFDSFact] + )(implicit state: State): Set[IFDSFact] = { + val definedCallee = callee.definedMethod + val ep = propertyStore(definedCallee, TACAI.key) + ep match { + case FinalP(TheTACAI(tac)) => + val cfg = tac.cfg + val successors = predecessorStatementsWithNode(call) + val flow = scala.collection.mutable.Set.empty[IFDSFact] + (cfg.normalReturnNode.predecessors ++ cfg.abnormalReturnNode.predecessors) + .foreach { bb => + val exitPc = bb.asBasicBlock.endPC + val calleeStmts = tac.stmts + val exitStmt = calleeStmts(exitPc) + val exitStatement = + old.DeclaredMethodJavaStatement(definedCallee, cfg.bb(exitPc), exitStmt, exitPc, calleeStmts, cfg, state.source._1) + for { + successor <- successors + if !AbstractIFDSAnalysis.OPTIMIZE_CROSS_PRODUCT_IN_RETURN_FLOW || + (successor._2.isBasicBlock || successor._2.isNormalReturnExitNode) && + (exitStatement.stmt.astID == Return.ASTID || exitStatement.stmt.astID == ReturnValue.ASTID) || + (successor._2.isCatchNode || successor._2.isAbnormalReturnExitNode) && + (exitStatement.stmt.astID != Return.ASTID && exitStatement.stmt.astID != ReturnValue.ASTID) + } { + numberOfCalls.returnFlow += 1 + sumOfInputFactsForCallbacks += in.size + flow ++= ifdsProblem.returnFlow(call, callee, exitStatement, successor._1, in) + } + } + flow.toSet + case _ => + val pendingTacCallSites = state.pendingTacCallSites + val index = call.index + state.pendingTacDependees += definedCallee -> ep + state.pendingTacCallSites = pendingTacCallSites.updated( + callee, + pendingTacCallSites.getOrElse(callee, Set.empty) + call.cfg.bb(index) + ) + Set.empty + } + } + + /** + * Calls callFlow for the facts in exitFacts and adds them for each successor to the summary + * edges. exitFacts should at most contain the callee's entry point. + */ + override protected def addExitToReturnFacts( + summaryEdges: Map[DeclaredMethodJavaStatement, Set[IFDSFact]], + successors: Set[DeclaredMethodJavaStatement], + call: DeclaredMethodJavaStatement, + callee: DeclaredMethod, + exitFacts: Map[DeclaredMethodJavaStatement, Set[IFDSFact]] + )(implicit state: State): Map[DeclaredMethodJavaStatement, Set[IFDSFact]] = { + var result = summaryEdges + if (exitFacts.nonEmpty) { + val in = exitFacts.head._2 + numberOfCalls.callFlow += 1 + sumOfInputFactsForCallbacks += in.size + val exitToReturnFacts = ifdsProblem.callFlow(call, callee, in, state.source) + successors.foreach { successor => + val updatedValue = result.get(successor) match { + case Some(facts) => + if (facts.size >= exitToReturnFacts.size) facts ++ exitToReturnFacts + else exitToReturnFacts ++ facts + case None => exitToReturnFacts + } + result = result.updated(successor, updatedValue) + } + } + result + } + + /** + * If there is an unbalanced return fact for this call, it will be replaced by its inner fact. + */ + override protected def beforeHandleCall(call: DeclaredMethodJavaStatement, in: Set[IFDSFact]): Set[IFDSFact] = + in.map { + case unbalancedFact: UnbalancedReturnFact[IFDSFact @unchecked] if unbalancedFact.index == call.index => + unbalancedFact.innerFact + case fact => fact + } + + /** + * Determines the predecessor statements, i.e. the successor statements in the control flow + * direction. They are mapped to the corresponding predecessor node. When determining the node, + * catch nodes are not skipped. + * + * @param statement The statement, for which the predecessor statements will be determined. + * + * @return A map, mapping from a predecessor statement to the corresponding node. + */ + private def predecessorStatementsWithNode( + statement: DeclaredMethodJavaStatement + )(implicit state: State): Map[DeclaredMethodJavaStatement, CFGNode] = { + val index = statement.index + val basicBlock = statement.node.asBasicBlock + if (index == basicBlock.endPC) + basicBlock.successors.iterator + .map(successorNode => lastStatement(successorNode) -> successorNode) + .toMap + else { + val nextIndex = index + 1 + Map( + old.DeclaredMethodJavaStatement( + statement.method, + basicBlock, + statement.code(nextIndex), + nextIndex, + statement.code, + statement.cfg, + statement.declaredMethod + ) -> basicBlock + ) + } + } + + /** + * Determines the last statement of a `node`. + * If it is a basic block, its entry point will be returned. + * If it is a catch node, the last statement of its successor will be returned. + * If it is an exit node, an artificial statement without code will be returned. + * + * @param node The node, for which the last statement will be determined. + * + * @return The last statement of `node`. + */ + @tailrec private def lastStatement(node: CFGNode)(implicit state: State): DeclaredMethodJavaStatement = { + if (node.isBasicBlock) { + val index = node.asBasicBlock.startPC + old.DeclaredMethodJavaStatement(state.method, node, state.code(index), index, state.code, state.cfg, state.source._1) + } else if (node.isCatchNode) lastStatement(node.successors.head) + else if (node.isExitNode) old.DeclaredMethodJavaStatement(state.method, node, null, 0, state.code, state.cfg, state.source._1) + else throw new IllegalArgumentException(s"Unknown node type: $node") + } + + /** + * Adds a dependency to an unbalanced return fact to the pending IFDS dependencies. + * + * @param caller The caller of the analyzed method. + * @param callIndex The index in the `caller`'s context, at which the analyzed method is called. + * @param in The facts, which hold at the entry point of the analyzed method. + * @param tac The `caller`'s tac. + */ + private def addDependencyForUnbalancedReturn( + caller: DeclaredMethod, + callIndex: Int, + in: Set[IFDSFact], + tac: TACode[TACMethodParameter, DUVar[ValueInformation]] + )(implicit state: State): Unit = { + val callerStmts = tac.stmts + val callerCfg = tac.cfg + val call = old.DeclaredMethodJavaStatement( + caller.definedMethod, + callerCfg.bb(callIndex), + callerStmts(callIndex), + callIndex, + callerStmts, + callerCfg, + caller + ) + ifdsProblem.unbalancedReturnFlow(in, call, caller, state.source).foreach { in => + val callerEntity = (caller, in) + /* + * Add the caller with the unbalanced return fact as a dependency to + * start its analysis. + */ + val callerAnalysisResult = propertyStore(callerEntity, propertyKey.key) + callerAnalysisResult match { + case FinalEP(_, _) => // Caller was already analyzed with the fact + case _ => + val pendingIfdsCallSites = state.pendingIfdsCallSites + state.pendingIfdsDependees += callerEntity -> + callerAnalysisResult + .asInstanceOf[EOptionP[(DeclaredMethod, IFDSFact), IFDSProperty[DeclaredMethodJavaStatement, IFDSFact]]] + state.pendingIfdsCallSites += callerEntity -> + (pendingIfdsCallSites.getOrElse(callerEntity, Set.empty) + + ((state.cfg.startBlock, 0))) + } + } + } +} + +/** + * A synthetic node, that is the predecessor of a method's entry point. + * This node is necessary, because we need to store the output facts of the method's entry point in + * a map, which maps from successor statements of a node to its output facts. + * The synthetic start node represents the successor of the entry point in this map. + */ +object SyntheticStartNode extends CFGNode { + + override def isBasicBlock: Boolean = false + + override def isStartOfSubroutine: Boolean = false + + override def isAbnormalReturnExitNode: Boolean = false + + override def isExitNode: Boolean = false + + override def isNormalReturnExitNode: Boolean = false + + override def isCatchNode: Boolean = false + + override def toHRR: Option[String] = Some("Synthetic Start Node") + + override def nodeId: Int = -1 +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/ForwardICFG.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/ForwardICFG.scala new file mode 100644 index 0000000000..edec39f814 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/ForwardICFG.scala @@ -0,0 +1,95 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.old + +import org.opalj.br.DeclaredMethod +import org.opalj.br.analyses.DeclaredMethods +import org.opalj.br.cfg.CFGNode +import org.opalj.fpcf.{FinalEP, PropertyStore} +import org.opalj.ifds.AbstractIFDSFact +import org.opalj.ifds.old.ICFG +import org.opalj.tac.fpcf.analyses.cg.TypeProvider +import org.opalj.tac.fpcf.properties.cg.Callees + +class ForwardICFG[IFDSFact <: AbstractIFDSFact](implicit + propertyStore: PropertyStore, + typeProvider: TypeProvider, + declaredMethods: DeclaredMethods +) extends ICFG[IFDSFact, DeclaredMethod, DeclaredMethodJavaStatement, CFGNode] { + /** + * Determines the basic blocks, at which the analysis starts. + * + * @param sourceFact The source fact of the analysis. + * @param callable The analyzed callable. + * @return The basic blocks, at which the analysis starts. + */ + override def startNodes(sourceFact: IFDSFact, callable: DeclaredMethod): Set[CFGNode] = ??? + + /** + * Determines the nodes, that will be analyzed after some `basicBlock`. + * + * @param node The basic block, that was analyzed before. + * @return The nodes, that will be analyzed after `basicBlock`. + */ + override def nextNodes(node: CFGNode): Set[CFGNode] = ??? + + /** + * Checks, if some `node` is the last node. + * + * @return True, if `node` is the last node, i.e. there is no next node. + */ + override def isLastNode(node: CFGNode): Boolean = ??? + + /** + * Determines the first index of some `basic block`, that will be analyzed. + * + * @param basicBlock The basic block. + * @return The first index of some `basic block`, that will be analyzed. + */ + override def firstStatement(basicBlock: CFGNode): DeclaredMethodJavaStatement = ??? + + /** + * Determines the last index of some `basic block`, that will be analzyed. + * + * @param basicBlock The basic block. + * @return The last index of some `basic block`, that will be analzyed. + */ + override def lastStatement(basicBlock: CFGNode): DeclaredMethodJavaStatement = ??? + + /** + * Determines the statement that will be analyzed after some other statement. + * + * @param statement The current statement. + * @return The statement that will be analyzed after `statement`. + */ + override def nextStatement(statement: DeclaredMethodJavaStatement): DeclaredMethodJavaStatement = ??? + + /** + * Determines the statement, that will be analyzed after some other `statement`. + * + * @param statement The source statement. + * @return The successor statements + */ + override def nextStatements(statement: DeclaredMethodJavaStatement): Set[DeclaredMethodJavaStatement] = ??? + + /** + * Gets the set of all methods possibly called at some statement. + * + * @param statement The statement. + * @return All callables possibly called at the statement or None, if the statement does not + * contain a call. + */ + override def getCalleesIfCallStatement(statement: DeclaredMethodJavaStatement): Option[collection.Set[DeclaredMethod]] = { + val pc = statement.code(statement.index).pc + val caller = declaredMethods(statement.method) + val ep = propertyStore(caller, Callees.key) + ep match { + case FinalEP(_, p) => Some(p.directCallees(typeProvider.newContext(caller), pc).map(_.method).toSet) + case _ => + throw new IllegalStateException( + "call graph mut be computed before the analysis starts" + ) + } + } + + override def isExitStatement(statement: DeclaredMethodJavaStatement): Boolean = ??? +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/ForwardIFDSAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/ForwardIFDSAnalysis.scala new file mode 100644 index 0000000000..d82c4fb10d --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/ForwardIFDSAnalysis.scala @@ -0,0 +1,220 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.old + +import org.opalj.br.DeclaredMethod +import org.opalj.br.cfg.{BasicBlock, CFG, CFGNode} +import org.opalj.ifds.old.IFDSProblem +import org.opalj.ifds.{AbstractIFDSFact, IFDSPropertyMetaInformation} +import org.opalj.tac.fpcf.analyses.ifds.old +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V +import org.opalj.tac.{Return, ReturnValue, Stmt, TACStmts} + +/** + * An IFDS analysis, which analyzes the code in the control flow direction. + * + * @author Mario Trageser + */ +abstract class ForwardIFDSAnalysis[IFDSFact <: AbstractIFDSFact](ifdsProblem: IFDSProblem[IFDSFact, DeclaredMethod, DeclaredMethodJavaStatement, CFGNode], propertyKey: IFDSPropertyMetaInformation[DeclaredMethodJavaStatement, IFDSFact]) extends AbstractIFDSAnalysis[IFDSFact](ifdsProblem, propertyKey) { + + /** + * The analysis starts at the entry block. + */ + override protected def startBlocks( + sourceFact: IFDSFact, + cfg: CFG[Stmt[V], TACStmts[V]] + ): Set[BasicBlock] = + Set(cfg.startBlock) + + /** + * Collects the output facts at the predecessors of the normal and abnormal return node. + */ + override protected def collectResult(implicit state: State): Map[DeclaredMethodJavaStatement, Set[IFDSFact]] = { + mergeMaps( + resultOfExitNode(state.cfg.normalReturnNode), + resultOfExitNode(state.cfg.abnormalReturnNode) + ) + } + + /** + * Returns the next successor nodes of `basicBlock`. + * Catch nodes are skipped to their successor. + */ + override protected def nextNodes(basicBlock: BasicBlock): Set[CFGNode] = + basicBlock.successors.map { successor => + if (successor.isCatchNode) successor.successors.head + else successor + } + + /** + * The exit nodes are the last nodes. + */ + override protected def isLastNode(node: CFGNode): Boolean = node.isExitNode + + /** + * If the node is a basic block, it will be returned. + * Otherwise, its first successor will be returned. + */ + override protected def skipCatchNode(node: CFGNode): Set[BasicBlock] = + Set((if (node.isBasicBlock) node else node.successors.head).asBasicBlock) + + /** + * The first index of a basic block is its start index. + */ + override protected def firstIndex(basicBlock: BasicBlock): Int = basicBlock.startPC + + /** + * The last index of a basic block is its end index. + */ + override protected def lastIndex(basicBlock: BasicBlock): Int = basicBlock.endPC + + /** + * The next index in the direction of the control flow. + */ + override protected def nextIndex(index: Int): Int = index + 1 + + /** + * If the `node` is a basic block, its first statement will be returned. + * If it is a catch node, the first statement of its handler will be returned. + * If it is an exit node, an artificial statement without code will be returned. + */ + override protected def firstStatement(node: CFGNode)(implicit state: State): DeclaredMethodJavaStatement = { + if (node.isBasicBlock) { + val index = node.asBasicBlock.startPC + old.DeclaredMethodJavaStatement(state.method, node, state.code(index), index, state.code, state.cfg, state.source._1) + } else if (node.isCatchNode) firstStatement(node.successors.head) + else if (node.isExitNode) old.DeclaredMethodJavaStatement(state.method, node, null, 0, state.code, state.cfg, state.source._1) + else throw new IllegalArgumentException(s"Unknown node type: $node") + } + + /** + * The successor statements in the direction of the control flow. + */ + override protected def nextStatements(statement: DeclaredMethodJavaStatement)(implicit state: State): Set[DeclaredMethodJavaStatement] = { + val index = statement.index + val basicBlock = statement.node.asBasicBlock + if (index == basicBlock.endPC) + basicBlock.successors.map(firstStatement(_)) + else { + val nextIndex = index + 1 + Set(old.DeclaredMethodJavaStatement(statement.method, basicBlock, statement.code(nextIndex), nextIndex, + statement.code, statement.cfg, statement.declaredMethod)) + } + } + + /** + * Calls callFlow. + */ + override protected def callToStartFacts(call: DeclaredMethodJavaStatement, callee: DeclaredMethod, + in: Set[IFDSFact])(implicit state: State): Set[IFDSFact] = { + numberOfCalls.callFlow += 1 + sumOfInputFactsForCallbacks += in.size + ifdsProblem.callFlow(call, callee, in, state.source) + } + + /** + * Combines each normal exit node with each normal successor and each abnormal exit statement + * with each catch node. Calls returnFlow for those pairs and adds them to the summary edges. + */ + override protected def addExitToReturnFacts( + summaryEdges: Map[DeclaredMethodJavaStatement, Set[IFDSFact]], + successors: Set[DeclaredMethodJavaStatement], call: DeclaredMethodJavaStatement, + callee: DeclaredMethod, + exitFacts: Map[DeclaredMethodJavaStatement, Set[IFDSFact]] + )(implicit state: State): Map[DeclaredMethodJavaStatement, Set[IFDSFact]] = { + // First process for normal returns, then abnormal returns. + var result = summaryEdges + if (AbstractIFDSAnalysis.OPTIMIZE_CROSS_PRODUCT_IN_RETURN_FLOW) { + val successors = nextStatementsWithNode(call) + for { + successor <- successors + exitStatement <- exitFacts.keys + if (successor._2.isBasicBlock || successor._2.isNormalReturnExitNode) && + (exitStatement.stmt.astID == Return.ASTID || exitStatement.stmt.astID == ReturnValue.ASTID) || + (successor._2.isCatchNode || successor._2.isAbnormalReturnExitNode) && + (exitStatement.stmt.astID != Return.ASTID && exitStatement.stmt.astID != ReturnValue.ASTID) + } result = addSummaryEdge(result, call, exitStatement, successor._1, callee, exitFacts) + } else { + val successors = nextStatements(call) + for { + successor <- successors + exitStatement <- exitFacts.keys + } result = addSummaryEdge(result, call, exitStatement, successor, callee, exitFacts) + } + result + } + + /** + * Like nextStatements, but maps each successor statement to the corresponding successor node. + * When determining the successor node, catch nodes are not skipped. + */ + private def nextStatementsWithNode(statement: DeclaredMethodJavaStatement)(implicit state: State): Map[DeclaredMethodJavaStatement, CFGNode] = { + val index = statement.index + val basicBlock = statement.node.asBasicBlock + if (index == basicBlock.endPC) + basicBlock.successors.iterator + .map(successorNode => firstStatement(successorNode) -> successorNode).toMap + else { + val nextIndex = index + 1 + Map(old.DeclaredMethodJavaStatement(statement.method, basicBlock, statement.code(nextIndex), nextIndex, + statement.code, statement.cfg, statement.declaredMethod) -> basicBlock) + } + } + + /** + * Collects the facts valid at an exit node based on the current results. + * + * @param exit The exit node. + * @return A map, mapping from each predecessor of the `exit` node to the facts valid at the + * `exit` node under the assumption that the predecessor was executed before. + */ + private def resultOfExitNode(exit: CFGNode)( + implicit + state: State + ): Map[DeclaredMethodJavaStatement, Set[IFDSFact]] = { + var result = Map.empty[DeclaredMethodJavaStatement, Set[IFDSFact]] + exit.predecessors foreach { predecessor => + if (predecessor.isBasicBlock) { + val basicBlock = predecessor.asBasicBlock + val exitFacts = state.outgoingFacts.get(basicBlock).flatMap(_.get(exit)) + if (exitFacts.isDefined) { + val lastIndex = basicBlock.endPC + val stmt = old.DeclaredMethodJavaStatement(state.method, basicBlock, state.code(lastIndex), + lastIndex, state.code, state.cfg, state.source._1) + result += stmt -> exitFacts.get + } + } + } + result + } + + /** + * Adds a summary edge for a call to a map representing summary edges. + * + * @param summaryEdges The current map representing the summary edges. + * Maps from successor statements to facts, which hold at their beginning. + * @param call The call, calling the `callee`. + * @param exitStatement The exit statement for the new summary edge. + * @param successor The successor statement of the call for the new summary edge. + * @param callee The callee, called by `call`. + * @param allNewExitFacts A map, mapping from the exit statements of `callee` to their newly + * found exit facts. + * @return `summaryEdges` with an additional or updated summary edge from `call` to `successor`. + */ + private def addSummaryEdge(summaryEdges: Map[DeclaredMethodJavaStatement, Set[IFDSFact]], call: DeclaredMethodJavaStatement, + exitStatement: DeclaredMethodJavaStatement, successor: DeclaredMethodJavaStatement, + callee: DeclaredMethod, + allNewExitFacts: Map[DeclaredMethodJavaStatement, Set[IFDSFact]]): Map[DeclaredMethodJavaStatement, Set[IFDSFact]] = { + val in = allNewExitFacts.getOrElse(exitStatement, Set.empty) + numberOfCalls.returnFlow += 1 + sumOfInputFactsForCallbacks += in.size + val returned = ifdsProblem.returnFlow(call, callee, exitStatement, successor, in) + val newFacts = + if (summaryEdges.contains(successor) && summaryEdges(successor).nonEmpty) { + val summaryForSuccessor = summaryEdges(successor) + if (summaryForSuccessor.size >= returned.size) summaryForSuccessor ++ returned + else returned ++ summaryForSuccessor + } else returned + summaryEdges.updated(successor, newFacts) + } + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/IFDSBasedVariableTypeAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/IFDSBasedVariableTypeAnalysis.scala new file mode 100644 index 0000000000..07befc548d --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/IFDSBasedVariableTypeAnalysis.scala @@ -0,0 +1,110 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.old + +import org.opalj.br.analyses.SomeProject +import org.opalj.fpcf.{PropertyBounds, PropertyKey, PropertyStore} +import org.opalj.ifds.old.{NumberOfSubsumptions, Subsuming} +import org.opalj.ifds.{IFDSProperty, IFDSPropertyMetaInformation} +import org.opalj.tac.fpcf.properties.TACAI +import org.opalj.tac.fpcf.properties.cg.Callers + +import java.io.{File, PrintWriter} + +/** + * A variable type analysis implemented as an IFDS analysis. + * In contrast to an ordinary variable type analysis, which also determines types of fields, + * this analysis only determines the types of local variables. + * The subsuming traint can be mixed in to enable subsuming. + * + * @param project The analyzed project. + * @author Mario Trageser + */ +class IFDSBasedVariableTypeAnalysis(ifdsProblem: VariableTypeProblem)(implicit val project: SomeProject) + extends ForwardIFDSAnalysis[VTAFact](ifdsProblem, VTAResult) + +object IFDSBasedVariableTypeAnalysisScheduler extends IFDSAnalysisScheduler[VTAFact] { + + var SUBSUMING: Boolean = true + + override val uses: Set[PropertyBounds] = + Set(PropertyBounds.finalP(TACAI), PropertyBounds.finalP(Callers)) + + override def init(p: SomeProject, ps: PropertyStore): IFDSBasedVariableTypeAnalysis = { + implicit val project = p + if (SUBSUMING) new IFDSBasedVariableTypeAnalysis(new VariableTypeProblem(p)) with Subsuming[DeclaredMethodJavaStatement, VTAFact] + else new IFDSBasedVariableTypeAnalysis(new VariableTypeProblem(p)) + } + + override def property: IFDSPropertyMetaInformation[DeclaredMethodJavaStatement, VTAFact] = VTAResult +} + +/** + * The IFDSProperty for this analysis. + */ +case class VTAResult(flows: Map[DeclaredMethodJavaStatement, Set[VTAFact]], debugData: Map[DeclaredMethodJavaStatement, Set[VTAFact]] = Map.empty) extends IFDSProperty[DeclaredMethodJavaStatement, VTAFact] { + + override type Self = VTAResult + override def create(result: Map[DeclaredMethodJavaStatement, Set[VTAFact]]): IFDSProperty[DeclaredMethodJavaStatement, VTAFact] = new VTAResult(result) + override def create(result: Map[DeclaredMethodJavaStatement, Set[VTAFact]], debugData: Map[DeclaredMethodJavaStatement, Set[VTAFact]]): IFDSProperty[DeclaredMethodJavaStatement, VTAFact] = new VTAResult(result, debugData) + + override def key: PropertyKey[VTAResult] = VTAResult.key +} + +object VTAResult extends IFDSPropertyMetaInformation[DeclaredMethodJavaStatement, VTAFact] { + + override type Self = VTAResult + override def create(result: Map[DeclaredMethodJavaStatement, Set[VTAFact]]): IFDSProperty[DeclaredMethodJavaStatement, VTAFact] = new VTAResult(result) + override def create(result: Map[DeclaredMethodJavaStatement, Set[VTAFact]], debugData: Map[DeclaredMethodJavaStatement, Set[VTAFact]]): IFDSProperty[DeclaredMethodJavaStatement, VTAFact] = new VTAResult(result, debugData) + + val key: PropertyKey[VTAResult] = PropertyKey.create("VTA", new VTAResult(Map.empty)) +} + +class IFDSBasedVariableTypeAnalysisRunner extends AbsractIFDSAnalysisRunner { + + override def analysisClass: IFDSBasedVariableTypeAnalysisScheduler.type = IFDSBasedVariableTypeAnalysisScheduler + + override def printAnalysisResults(analysis: AbstractIFDSAnalysis[_], ps: PropertyStore): Unit = {} + + override protected def additionalEvaluationResult( + analysis: AbstractIFDSAnalysis[_] + ): Option[Object] = + analysis match { + case subsuming: Subsuming[_, _] => Some(subsuming.numberOfSubsumptions) + case _ => None + } + + override protected def writeAdditionalEvaluationResultsToFile( + writer: PrintWriter, + additionalEvaluationResults: Seq[Object] + ): Unit = { + val numberOfSubsumptions = additionalEvaluationResults.map(_.asInstanceOf[NumberOfSubsumptions]) + val length = additionalEvaluationResults.length + val tries = numberOfSubsumptions.map(_.triesToSubsume).sum / length + val successes = numberOfSubsumptions.map(_.successfulSubsumes).sum / length + writer.println(s"Average tries to subsume: $tries") + writer.println(s"Average successful subsumes: $successes") + } +} + +object IFDSBasedVariableTypeAnalysisRunner { + def main(args: Array[String]): Unit = { + if (args.contains("--help")) { + println("Potential parameters:") + println(" -seq (to use the SequentialPropertyStore)") + println(" -l2 (to use the l2 domain instead of the default l1 domain)") + println(" -delay (for a three seconds delay before the taint flow analysis is started)") + println(" -debug (for debugging mode in the property store)") + println(" -evalSchedulingStrategies (evaluates all available scheduling strategies)") + println(" -f (Stores the average runtime to this file)") + } else { + val fileIndex = args.indexOf("-f") + new IFDSBasedVariableTypeAnalysisRunner().run( + args.contains("-debug"), + args.contains("-l2"), + args.contains("-delay"), + args.contains("-evalSchedulingStrategies"), + if (fileIndex >= 0) Some(new File(args(fileIndex + 1))) else None + ) + } + } +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/IFDSProblem.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/IFDSProblem.scala new file mode 100644 index 0000000000..4cf5cf8305 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/IFDSProblem.scala @@ -0,0 +1,65 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.old + +import org.opalj.ifds.AbstractIFDSFact + +/** + * A data flow fact, that was created by an unbalanced return. + * + * @tparam FactType The type of flow facts, which are tracked by the concrete analysis. + */ +trait UnbalancedReturnFact[FactType] extends AbstractIFDSFact { + + /** + * The index of the call, which creates the inner fact. + */ + val index: Int + + /** + * The fact, which will hold after `index`. + */ + val innerFact: FactType +} + +trait BackwardIFDSProblem[IFDSFact <: AbstractIFDSFact, UnbalancedIFDSFact <: IFDSFact with UnbalancedReturnFact[IFDSFact], Method, Statement] { + /** + * Checks for the analyzed entity, if an unbalanced return should be performed. + * + * @param source The analyzed entity. + * @return False, if no unbalanced return should be performed. + */ + def shouldPerformUnbalancedReturn(source: (Method, IFDSFact)): Boolean + + /** + * Called, when the entry point of the analyzed method is reached. + * Creates unbalanced return facts for the callers. + * + * @param facts The facts, which hold at the entry point of the analyzed method. + * @param call A call, which calls the analyzed method. + * @param caller The method, invoking the `call`. + * @param source The entity, which is currently analyzed. + * @return Unbalanced return facts, that hold after `call` under the assumption, that `facts` + * held at the entry point of the analyzed method. + */ + def unbalancedReturnFlow( + facts: Set[IFDSFact], + call: Statement, + caller: Method, + source: (Method, IFDSFact) + ): Set[UnbalancedIFDSFact] + + /** + * Called, when the analysis found new output facts at a method's start block. + * A concrete analysis may overwrite this method to create additional facts, which will be added + * to the analysis' result. + * + * @param in The new output facts at the start node. + * @param source The entity, which is analyzed. + * @return Nothing by default. + */ + def createFactsAtStartNode( + in: Set[IFDSFact], + source: (Method, IFDSFact) + ): Set[IFDSFact] = + Set.empty +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/JavaIFDSProblem.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/JavaIFDSProblem.scala new file mode 100644 index 0000000000..6eb8f0c074 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/JavaIFDSProblem.scala @@ -0,0 +1,109 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.old + +import org.opalj.br.analyses.{DeclaredMethods, DeclaredMethodsKey, SomeProject} +import org.opalj.br.cfg.CFGNode +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.br.{DeclaredMethod, ObjectType} +import org.opalj.fpcf._ +import org.opalj.ifds.old.IFDSProblem +import org.opalj.ifds.{AbstractIFDSFact, IFDSPropertyMetaInformation} +import org.opalj.tac.cg.TypeProviderKey +import org.opalj.tac.fpcf.analyses.cg.TypeProvider +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V +import org.opalj.tac.fpcf.properties.cg.Callers +import org.opalj.tac.{Assignment, Call, ExprStmt, Stmt} + +abstract class JavaIFDSProblem[Fact <: AbstractIFDSFact](project: SomeProject) extends IFDSProblem[Fact, DeclaredMethod, DeclaredMethodJavaStatement, CFGNode]( + new ForwardICFG[Fact]()(project.get(PropertyStoreKey), project.get(TypeProviderKey), project.get(DeclaredMethodsKey)) +) { + /** + * All declared methods in the project. + */ + implicit final protected val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) + + final implicit val propertyStore: PropertyStore = project.get(PropertyStoreKey) + implicit final protected val typeProvider: TypeProvider = project.get(TypeProviderKey) + + override def outsideAnalysisContext(callee: DeclaredMethod): Option[(DeclaredMethodJavaStatement, DeclaredMethodJavaStatement, Set[Fact]) => Set[Fact]] = callee.definedMethod.body.isDefined match { + case true => None + case false => Some((_call: DeclaredMethodJavaStatement, _successor: DeclaredMethodJavaStatement, in: Set[Fact]) => in) + } + + /** + * Returns all methods, that can be called from outside the library. + * The call graph must be computed, before this method may be invoked. + * + * @return All methods, that can be called from outside the library. + */ + protected def methodsCallableFromOutside: Set[DeclaredMethod] = + declaredMethods.declaredMethods.filter(canBeCalledFromOutside).toSet + + /** + * Checks, if some `method` can be called from outside the library. + * The call graph must be computed, before this method may be invoked. + * + * @param method The method, which may be callable from outside. + * @return True, if `method` can be called from outside the library. + */ + protected def canBeCalledFromOutside(method: DeclaredMethod): Boolean = { + val FinalEP(_, callers) = propertyStore(method, Callers.key) + callers.hasCallersWithUnknownContext + } + + /** + * Gets the call object for a statement that contains a call. + * + * @param call The call statement. + * @return The call object for `call`. + */ + protected def asCall(call: Stmt[V]): Call[V] = call.astID match { + case Assignment.ASTID => call.asAssignment.expr.asFunctionCall + case ExprStmt.ASTID => call.asExprStmt.expr.asFunctionCall + case _ => call.asMethodCall + } + + override def specialCase(source: (DeclaredMethod, Fact), propertyKey: IFDSPropertyMetaInformation[DeclaredMethodJavaStatement, Fact]): Option[ProperPropertyComputationResult] = { + val declaredMethod = source._1 + val method = declaredMethod.definedMethod + val declaringClass: ObjectType = method.classFile.thisType + /* + * If this is not the method's declaration, but a non-overwritten method in a subtype, do + * not re-analyze the code. + */ + if (declaringClass ne declaredMethod.declaringClassType) Some(baseMethodResult(source, propertyKey)) + super.specialCase(source, propertyKey) + } + + /** + * This method will be called if a non-overwritten declared method in a sub type shall be + * analyzed. Analyzes the defined method of the supertype instead. + * + * @param source A pair consisting of the declared method of the subtype and an input fact. + * @return The result of the analysis of the defined method of the supertype. + */ + private def baseMethodResult(source: (DeclaredMethod, Fact), propertyKey: IFDSPropertyMetaInformation[DeclaredMethodJavaStatement, Fact]): ProperPropertyComputationResult = { + + def c(eps: SomeEOptionP): ProperPropertyComputationResult = eps match { + case FinalP(p) => Result(source, p) + + case ep @ InterimUBP(ub: Property) => + InterimResult.forUB(source, ub, Set(ep), c) + + case epk => + InterimResult.forUB(source, propertyKey.create(Map.empty), Set(epk), c) + } + c(propertyStore((declaredMethods(source._1.definedMethod), source._2), propertyKey.key)) + } +} + +abstract class JavaBackwardIFDSProblem[IFDSFact <: AbstractIFDSFact, UnbalancedIFDSFact <: IFDSFact with UnbalancedReturnFact[IFDSFact]](project: SomeProject) extends JavaIFDSProblem[IFDSFact](project) with BackwardIFDSProblem[IFDSFact, UnbalancedIFDSFact, DeclaredMethod, DeclaredMethodJavaStatement] { + /** + * Checks for the analyzed entity, if an unbalanced return should be performed. + * + * @param source The analyzed entity. + * @return False, if no unbalanced return should be performed. + */ + def shouldPerformUnbalancedReturn(source: (DeclaredMethod, IFDSFact)): Boolean = + source._2.isInstanceOf[UnbalancedReturnFact[IFDSFact @unchecked]] || entryPoints.contains(source) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/VariableTypeProblem.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/VariableTypeProblem.scala new file mode 100644 index 0000000000..a0332ee117 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/VariableTypeProblem.scala @@ -0,0 +1,333 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.old + +import org.opalj.br._ +import org.opalj.br.analyses.SomeProject +import org.opalj.collection.immutable.EmptyIntTrieSet +import org.opalj.ifds.AbstractIFDSFact + +import org.opalj.tac.fpcf.analyses.ifds.{JavaIFDSProblem => NewJavaIFDSProblem} +import org.opalj.tac._ +import org.opalj.value.ValueInformation +import scala.annotation.tailrec + +import org.opalj.ifds.AbstractIFDSNullFact + +trait VTAFact extends AbstractIFDSFact +case object VTANullFact extends VTAFact with AbstractIFDSNullFact + +/** + * A possible run time type of a variable. + * + * @param definedBy The variable's definition site. + * @param t The variable's type. + * @param upperBound True, if the variable's type could also be every subtype of `t`. + */ +case class VariableType(definedBy: Int, t: ReferenceType, upperBound: Boolean) extends VTAFact { + + /** + * If this VariableType is an upper bound, it subsumes every subtype. + */ + override def subsumes(other: AbstractIFDSFact, project: SomeProject): Boolean = { + if (upperBound) other match { + case VariableType(definedByOther, tOther, _) if definedBy == definedByOther && t.isObjectType && tOther.isObjectType => + project.classHierarchy.isSubtypeOf(tOther.asObjectType, t.asObjectType) + case _ => false + } + else false + } +} + +/** + * A possible run time type of the receiver of a call. + * + * @param line The line of the call. + * @param t The callee's type. + * @param upperBound True, if the callee's type could also be every subtype of `t`. + */ +case class CalleeType(line: Int, t: ReferenceType, upperBound: Boolean) extends VTAFact { + + /** + * If this CalleeType is an upper bound, it subsumes every subtype. + */ + override def subsumes(other: AbstractIFDSFact, project: SomeProject): Boolean = { + if (upperBound) other match { + case CalleeType(lineOther, tOther, _) if line == lineOther && t.isObjectType && tOther.isObjectType => + tOther.asObjectType.isSubtypeOf(t.asObjectType)(project.classHierarchy) + case _ => false + } + else false + } +} + +class VariableTypeProblem(project: SomeProject) extends JavaIFDSProblem[VTAFact](project) { + override def nullFact: VTAFact = VTANullFact + + /** + * The analysis starts with all public methods in java.lang or org.opalj. + */ + override def entryPoints: Seq[(DeclaredMethod, VTAFact)] = { + project.allProjectClassFiles + .filter(classInsideAnalysisContext) + .flatMap(classFile => classFile.methods) + .filter(isEntryPoint) + .map(method => declaredMethods(method)) + .flatMap(entryPointsForMethod) + } + + /** + * If a new object is instantiated and assigned to a variable or array, a new ValueType will be + * created for the assignment's target. + * If there is an assignment of a variable or array element, a new VariableType will be + * created for the assignment's target with the source's type. + * If there is a field read, a new VariableType will be created with the field's declared type. + */ + override def normalFlow( + statement: DeclaredMethodJavaStatement, + successor: Option[DeclaredMethodJavaStatement], + in: Set[VTAFact] + ): Set[VTAFact] = { + val stmt = statement.stmt + stmt.astID match { + case Assignment.ASTID => + // Add facts for the assigned variable. + in ++ newFacts(statement.method, statement.stmt.asAssignment.expr, statement.index, in) + case ArrayStore.ASTID => + /* + * Add facts for the array store, like it was a variable assignment. + * By doing so, we only want to get the variable's type. + * Then, we change the definedBy-index to the one of the array and wrap the variable's + * type with an array type. + * Note, that an array type may have at most 255 dimensions. + */ + val flow = scala.collection.mutable.Set.empty[VTAFact] + flow ++= in + newFacts(statement.method, stmt.asArrayStore.value, statement.index, in).foreach { + case VariableType(_, t, upperBound) if !(t.isArrayType && t.asArrayType.dimensions <= 254) => + stmt.asArrayStore.arrayRef.asVar.definedBy + .foreach(flow += VariableType(_, ArrayType(t), upperBound)) + case _ => // Nothing to do + } + flow.toSet + // If the statement is neither an assignment, nor an array store, we just propagate our facts. + case _ => in + } + } + + /** + * For each variable, which can be passed as an argument to the call, a new VariableType is + * created for the callee context. + */ + override def callFlow( + call: DeclaredMethodJavaStatement, + callee: DeclaredMethod, + in: Set[VTAFact], + source: (DeclaredMethod, VTAFact) + ): Set[VTAFact] = { + val callObject = asCall(call.stmt) + val allParams = callObject.allParams + // Iterate over all input facts and over all parameters of the call. + val flow = scala.collection.mutable.Set.empty[VTAFact] + in.foreach { + case VariableType(definedBy, t, upperBound) => + allParams.iterator.zipWithIndex.foreach { + /* + * We are only interested in a pair of a variable type and a parameter, if the + * variable and the parameter can refer to the same object. + */ + case (parameter, parameterIndex) if parameter.asVar.definedBy.contains(definedBy) => + // If this is the case, create a new fact for the method's formal parameter. + flow += VariableType( + NewJavaIFDSProblem + .switchParamAndVariableIndex(parameterIndex, callee.definedMethod.isStatic), + t, + upperBound + ) + case _ => // Nothing to do + } + case _ => // Nothing to do + } + flow.toSet + } + + /** + * If the call is an instance call, new CalleeTypes will be created for the call, one for each + * VariableType, which could be the call's target. + */ + override def callToReturnFlow( + call: DeclaredMethodJavaStatement, + successor: DeclaredMethodJavaStatement, + in: Set[VTAFact], + source: (DeclaredMethod, VTAFact) + ): Set[VTAFact] = { + // Check, to which variables the callee may refer + val calleeDefinitionSites = asCall(call.stmt).receiverOption + .map(callee => callee.asVar.definedBy) + .getOrElse(EmptyIntTrieSet) + val calleeTypeFacts = in.collect { + // If we know the variable's type, we also know on which type the call is performed. + case VariableType(index, t, upperBound) if calleeDefinitionSites.contains(index) => + CalleeType(call.index, t, upperBound) + } + if (in.size >= calleeTypeFacts.size) in ++ calleeTypeFacts + else calleeTypeFacts ++ in + } + + /** + * If the call returns a value which is assigned to a variable, a new VariableType will be + * created in the caller context with the returned variable's type. + */ + override def returnFlow( + call: DeclaredMethodJavaStatement, + callee: DeclaredMethod, + exit: DeclaredMethodJavaStatement, + successor: DeclaredMethodJavaStatement, + in: Set[VTAFact] + ): Set[VTAFact] = + // We only create a new fact, if the call returns a value, which is assigned to a variable. + if (exit.stmt.astID == ReturnValue.ASTID && call.stmt.astID == Assignment.ASTID) { + val returnValue = exit.stmt.asReturnValue.expr.asVar + in.collect { + // If we know the type of the return value, we create a fact for the assigned variable. + case VariableType(definedBy, t, upperBound) if returnValue.definedBy.contains(definedBy) => + VariableType(call.index, t, upperBound) + } + } else Set.empty + + /** + * Only methods in java.lang and org.opalj are inside the analysis context. + * + * @param callee The callee. + * @return True, if the callee is inside the analysis context. + */ + override def outsideAnalysisContext(callee: DeclaredMethod): Option[OutsideAnalysisContextHandler] = + if (classInsideAnalysisContext(callee.definedMethod.classFile) && + super.outsideAnalysisContext(callee).isEmpty) + None + else { + Some(((call: DeclaredMethodJavaStatement, successor: DeclaredMethodJavaStatement, in: Set[VTAFact]) => { + val returnType = callee.descriptor.returnType + if (call.stmt.astID == Assignment.ASTID && returnType.isReferenceType) { + Set(VariableType(call.index, returnType.asReferenceType, upperBound = true)) + } else Set.empty[VTAFact] + }): OutsideAnalysisContextHandler) + } + + /** + * When `normalFlow` reaches an assignment or array store, this method computes the new facts + * created by the statement. + * + * @param expression The source expression of the assignment or array store. + * @param statementIndex The statement's index. + * @param in The facts, which hold before the statement. + * @return The new facts created by the statement. + */ + private def newFacts( + method: Method, + expression: Expr[DUVar[ValueInformation]], + statementIndex: Int, + in: Set[VTAFact] + ): Iterator[VariableType] = expression.astID match { + case New.ASTID => + in.iterator.collect { + // When a constructor is called, we always know the exact type. + case VTANullFact => + VariableType(statementIndex, expression.asNew.tpe, upperBound = false) + } + case Var.ASTID => + in.iterator.collect { + // When we know the source type, we also know the type of the assigned variable. + case VariableType(index, t, upperBound) if expression.asVar.definedBy.contains(index) => + VariableType(statementIndex, t, upperBound) + } + case ArrayLoad.ASTID => + in.iterator.collect { + // When we know the array's type, we also know the type of the loaded element. + case VariableType(index, t, upperBound) if isArrayOfObjectType(t) && + expression.asArrayLoad.arrayRef.asVar.definedBy.contains(index) => + VariableType(statementIndex, t.asArrayType.elementType.asReferenceType, upperBound) + } + case GetField.ASTID | GetStatic.ASTID => + val t = expression.asFieldRead.declaredFieldType + /* + * We do not track field types. So we must assume, that it contains any subtype of its + * compile time type. + */ + if (t.isReferenceType) + Iterator(VariableType(statementIndex, t.asReferenceType, upperBound = true)) + else Iterator.empty + case _ => Iterator.empty + } + + /** + * Checks, if some type is an array type containing an object type. + * + * @param t The type to be checked. + * @param includeObjectType If true, this method also returns true if `t` is an object type + * itself. + * + * @return True, if `t` is an array type of an object type. + */ + @tailrec private def isArrayOfObjectType( + t: FieldType, + includeObjectType: Boolean = false + ): Boolean = { + if (t.isArrayType) isArrayOfObjectType(t.asArrayType.elementType, includeObjectType = true) + else if (t.isObjectType && includeObjectType) true + else false + } + + /** + * Checks, if a class is inside the analysis context. + * By default, that are the packages java.lang and org.opalj. + * + * @param classFile The class, which is checked. + * @return True, if the class is inside the analysis context. + */ + private def classInsideAnalysisContext(classFile: ClassFile): Boolean = { + val fqn = classFile.fqn + fqn.startsWith("java/lang") || fqn.startsWith("org/opalj/fpcf/fixtures/vta") + } + + /** + * Checks, if a method is an entry point of this analysis. + * + * @param method The method to be checked. + * @return True, if this method is an entry point of the analysis. + */ + private def isEntryPoint(method: Method): Boolean = { + val declaredMethod = declaredMethods(method) + method.body.isDefined && canBeCalledFromOutside(declaredMethod) + } + + /** + * For an entry point method, this method computes all pairs (`method`, inputFact) where + * inputFact is a VariableType for one of the method's parameter with its compile time type as + * an upper bound. + * + * @param method The entry point method. + * + * @return All pairs (`method`, inputFact) where inputFact is a VariableType for one of the + * method's parameter with its compile time type as an upper bound. + */ + private def entryPointsForMethod(method: DeclaredMethod): Seq[(DeclaredMethod, VTAFact)] = { + // Iterate over all parameters, which have a reference type. + (method.descriptor.parameterTypes.zipWithIndex.collect { + case (t, index) if t.isReferenceType => + /* + * Create a fact for the parameter, which says, that the parameter may have any + * subtype of its compile time type. + */ + VariableType( + NewJavaIFDSProblem.switchParamAndVariableIndex(index, method.definedMethod.isStatic), + t.asReferenceType, + upperBound = true + ) + /* +* In IFDS problems, we must always also analyze the null fact, because it creates the facts, +* which hold independently of other source facts. +* Map the input facts, in which we are interested, to a pair of the method and the fact. +*/ + } :+ VTANullFact).map(fact => (method, fact)) + } +} \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/taint/BackwardTaintProblem.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/taint/BackwardTaintProblem.scala new file mode 100644 index 0000000000..6a86552b1a --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/taint/BackwardTaintProblem.scala @@ -0,0 +1,376 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.old.taint + +import org.opalj.br.analyses.SomeProject +import org.opalj.br.{DeclaredMethod, Method, ObjectType} +import org.opalj.tac._ +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V +import org.opalj.tac.fpcf.analyses.ifds.{JavaIFDSProblem => NewJavaIFDSProblem} +import org.opalj.tac.fpcf.analyses.ifds.old.{JavaBackwardIFDSProblem, DeclaredMethodJavaStatement} +import org.opalj.tac.fpcf.analyses.ifds.taint._ + +import org.opalj.tac.fpcf.analyses.ifds.old.UnbalancedReturnFact +import org.opalj.tac.fpcf.properties._ + +/** + * The unbalanced return fact of this analysis. + * + * @param index The index, at which the analyzed method is called by some caller. + * @param innerFact The fact, which will hold in the caller context after the call. + * @param callChain The current call chain from the sink. + */ +case class UnbalancedTaintFact(index: Int, innerFact: TaintFact, callChain: Seq[Method]) + extends UnbalancedReturnFact[TaintFact] with TaintFact + +abstract class BackwardTaintProblem(project: SomeProject) extends JavaBackwardIFDSProblem[TaintFact, UnbalancedTaintFact](project) with TaintProblem[DeclaredMethod, DeclaredMethodJavaStatement, TaintFact] { + override def nullFact: TaintFact = TaintNullFact + + /** + * If a tainted variable gets assigned a value, this value will be tainted. + */ + override def normalFlow(statement: DeclaredMethodJavaStatement, successor: Option[DeclaredMethodJavaStatement], + in: Set[TaintFact]): Set[TaintFact] = { + val stmt = statement.stmt + stmt.astID match { + case Assignment.ASTID if isTainted(statement.index, in) => + in ++ createNewTaints(stmt.asAssignment.expr, statement) + case ArrayStore.ASTID => + val arrayStore = stmt.asArrayStore + val arrayIndex = TaintProblem.getIntConstant(arrayStore.index, statement.code) + val arrayDefinedBy = arrayStore.arrayRef.asVar.definedBy + val result = in ++ in.collect { + // In this case, we taint the whole array. + case Variable(index) if arrayDefinedBy.contains(index) => + createNewTaints(arrayStore.value, statement) + // In this case, we taint exactly the stored element. + case ArrayElement(index, taintedElement) if arrayDefinedBy.contains(index) && + (arrayIndex.isEmpty || arrayIndex.get == taintedElement) => + createNewTaints(arrayStore.value, statement) + }.flatten + if (arrayDefinedBy.size == 1 && arrayIndex.isDefined) + result - ArrayElement(arrayDefinedBy.head, arrayIndex.get) + else result + case PutField.ASTID => + val putField = stmt.asPutField + val objectDefinedBy = putField.objRef.asVar.definedBy + if (in.exists { + case InstanceField(index, declaringClass, name) if objectDefinedBy.contains(index) && + putField.declaringClass == declaringClass && putField.name == name => + true + case _ => false + }) in ++ createNewTaints(putField.value, statement) + else in + case PutStatic.ASTID => + val putStatic = stmt.asPutStatic + if (in.exists { + case StaticField(declaringClass, name) if putStatic.declaringClass == declaringClass && putStatic.name == name => + true + case _ => false + }) in ++ createNewTaints(putStatic.value, statement) + else in + case _ => in + } + } + + /** + * Taints the actual parameters in the caller context if the formal parameters in the callee + * context were tainted. + * Does not taint anything, if the sanitize method was called. + */ + override def callFlow(call: DeclaredMethodJavaStatement, callee: DeclaredMethod, + in: Set[TaintFact], source: (DeclaredMethod, TaintFact)): Set[TaintFact] = + if (sanitizesReturnValue(callee)) Set.empty + else taintActualsIfFormalsTainted(callee, call, in, source) + + /** + * If the returned value on in the caller context is tainted, the returned values in the callee + * context will be tainted. If an actual pass-by-reference-parameter in the caller context is + * tainted, the formal parameter in the callee context will be tainted. + */ + override def returnFlow(call: DeclaredMethodJavaStatement, callee: DeclaredMethod, exit: DeclaredMethodJavaStatement, + successor: DeclaredMethodJavaStatement, in: Set[TaintFact]): Set[TaintFact] = { + val callObject = asCall(call.stmt) + val staticCall = callee.definedMethod.isStatic + val flow = collection.mutable.Set.empty[TaintFact] + if (call.stmt.astID == Assignment.ASTID && exit.stmt.astID == ReturnValue.ASTID) + in.foreach { + case Variable(index) if index == call.index => + flow ++= createNewTaints(exit.stmt.asReturnValue.expr, exit) + case ArrayElement(index, taintedElement) if index == call.index => + flow ++= createNewArrayElementTaints(exit.stmt.asReturnValue.expr, taintedElement, + call) + case InstanceField(index, declaringClass, name) if index == call.index => + flow ++= createNewInstanceFieldTaints(exit.stmt.asReturnValue.expr, declaringClass, + name, call) + case _ => // Nothing to do + } + val thisOffset = if (callee.definedMethod.isStatic) 0 else 1 + callObject.allParams.iterator.zipWithIndex + .filter(pair => (pair._2 == 0 && !staticCall) || + callObject.descriptor.parameterTypes(pair._2 - thisOffset).isReferenceType) + .foreach { pair => + val param = pair._1.asVar + val paramIndex = pair._2 + in.foreach { + case Variable(index) if param.definedBy.contains(index) => + flow += Variable(NewJavaIFDSProblem.switchParamAndVariableIndex(paramIndex, staticCall)) + case ArrayElement(index, taintedElement) if param.definedBy.contains(index) => + flow += ArrayElement( + NewJavaIFDSProblem.switchParamAndVariableIndex(paramIndex, staticCall), taintedElement + ) + case InstanceField(index, declaringClass, name) if param.definedBy.contains(index) => + flow += InstanceField( + NewJavaIFDSProblem.switchParamAndVariableIndex(paramIndex, staticCall), + declaringClass, name + ) + case staticField: StaticField => flow += staticField + case _ => // Nothing to do + } + } + flow.toSet + } + + /** + * Adds a FlowFact, if `createFlowFactAtCall` creates one. + * Removes taints according to `sanitizeParamters`. + */ + override def callToReturnFlow(call: DeclaredMethodJavaStatement, successor: DeclaredMethodJavaStatement, + in: Set[TaintFact], + source: (DeclaredMethod, TaintFact)): Set[TaintFact] = { + val flowFact = createFlowFactAtCall(call, in, source) + val result = in.filter(!sanitizesParameter(call, _)) + if (flowFact.isDefined) result + flowFact.get + else result + } + + /** + * If the returned value is tainted, all actual parameters will be tainted. + */ + override def outsideAnalysisContext(callee: DeclaredMethod): Option[OutsideAnalysisContextHandler] = + super.outsideAnalysisContext(callee) match { + case Some(_) => Some((call: DeclaredMethodJavaStatement, successor: DeclaredMethodJavaStatement, in: Set[TaintFact]) => { + val callStatement = asCall(call.stmt) + in ++ in.collect { + case Variable(index) if index == call.index => + callStatement.allParams.flatMap(createNewTaints(_, call)) + case ArrayElement(index, _) if index == call.index => + callStatement.allParams.flatMap(createNewTaints(_, call)) + case InstanceField(index, _, _) if index == call.index => + callStatement.allParams.flatMap(createNewTaints(_, call)) + }.flatten + }) + case None => None + } + + /** + * Creates an UnbalancedTaintFact for each actual parameter on the caller side, of the formal + * parameter of this method is tainted. + */ + override def unbalancedReturnFlow(facts: Set[TaintFact], call: DeclaredMethodJavaStatement, + caller: DeclaredMethod, + source: (DeclaredMethod, TaintFact)): Set[UnbalancedTaintFact] = + taintActualsIfFormalsTainted(source._1, call, facts, source, isCallFlow = false) + .map(UnbalancedTaintFact(call.index, _, currentCallChain(source))) + // Avoid infinite loops. + .filter(unbalancedTaintFact => !containsHeadTwice(unbalancedTaintFact.callChain)) + + /** + * Called in callToReturnFlow. Creates a FlowFact if necessary. + * + * @param call The call. + * @param in The facts, which hold before the call. + * @param source The entity, which is analyzed. + * @return Some FlowFact, if necessary. Otherwise None. + */ + protected def createFlowFactAtCall(call: DeclaredMethodJavaStatement, in: Set[TaintFact], + source: (DeclaredMethod, TaintFact)): Option[FlowFact] + + /** + * Called, when a FlowFact holds at the start node of a callee. Creates a FlowFact in the caller + * context if necessary. + * + * @param calleeFact The FlowFact, which holds at the start node of the callee. + * @param source The analyzed entity. + * @return Some FlowFact, if necessary. Otherwise None. + */ + protected def applyFlowFactFromCallee( + calleeFact: FlowFact, + source: (DeclaredMethod, TaintFact) + ): Option[FlowFact] + + /** + * Called, when new FlowFacts are found at the beginning of a method. + * Creates a FlowFact if necessary. + * + * @param in The newly found facts. + * @param source The entity, which is analyzed. + * @return Some FlowFact, if necessary. Otherwise None. + */ + protected def createFlowFactAtBeginningOfMethod( + in: Set[TaintFact], + source: (DeclaredMethod, TaintFact) + ): Option[FlowFact] + + /** + * Propagates the call to createFlowFactAtBeginningOfMethod. + */ + override def createFactsAtStartNode( + in: Set[TaintFact], + source: (DeclaredMethod, TaintFact) + ): Set[TaintFact] = { + val flowFact = createFlowFactAtBeginningOfMethod(in, source) + if (flowFact.isDefined) Set(flowFact.get) + else Set.empty + } + + /** + * Gets the current call chain. + * If the input fact for the analysis is an UnbalancedTaintFact, this is the current method + * concatenated with the fact's call chain. + * Otherwise, it is just this method. + * + * @param source The entity, which is analyzed. + * @return The current call chain. + */ + protected def currentCallChain(source: (DeclaredMethod, TaintFact)): Seq[Method] = { + val method = source._1.definedMethod + val sourceFact = source._2 + sourceFact match { + case fact: UnbalancedTaintFact => method +: fact.callChain + case _ => Seq(method) + } + } + + /** + * Checks, if the head of a sequence is contained in its tail. + * + * @param callChain The sequence. + * @return True, if the head of `callChain` is contained in its tail. + */ + protected def containsHeadTwice(callChain: Seq[Method]): Boolean = + callChain.tail.contains(callChain.head) + + /** + * Checks, if some variable or array element is tainted. + * + * @param index The index of the variable or array element. + * @param in The current data flow facts. + * @param taintedElement If present, the taint of a specific array element will be checked. + * @return True, if the variable or array element is tainted. + */ + private def isTainted(index: Int, in: Set[TaintFact], taintedElement: Option[Int] = None): Boolean = in.exists { + case Variable(variableIndex) => variableIndex == index + case ArrayElement(variableIndex, element) => + variableIndex == index && (taintedElement.isEmpty || taintedElement.get == element) + case _ => false + } + + /** + * Taints all variables in an `expression`. + * + * @param expression The expression. + * @param statement The statement, which contains the expression. + * @return The new taints. + */ + private def createNewTaints(expression: Expr[V], statement: DeclaredMethodJavaStatement): Set[TaintFact] = + expression.astID match { + case Var.ASTID => expression.asVar.definedBy.map(Variable) + case ArrayLoad.ASTID => + val arrayLoad = expression.asArrayLoad + val arrayIndex = TaintProblem.getIntConstant(expression.asArrayLoad.index, statement.code) + val arrayDefinedBy = arrayLoad.arrayRef.asVar.definedBy + if (arrayIndex.isDefined) arrayDefinedBy.map(ArrayElement(_, arrayIndex.get)) + else arrayDefinedBy.map(Variable) + case BinaryExpr.ASTID | PrefixExpr.ASTID | Compare.ASTID | + PrimitiveTypecastExpr.ASTID | NewArray.ASTID | ArrayLength.ASTID => + (0 until expression.subExprCount).foldLeft(Set.empty[TaintFact])((acc, subExpr) => + acc ++ createNewTaints(expression.subExpr(subExpr), statement)) + case GetField.ASTID => + val getField = expression.asGetField + getField.objRef.asVar.definedBy + .map(InstanceField(_, getField.declaringClass, getField.name)) + /*case GetStatic.ASTID => + val getStatic = expression.asGetStatic + Set(StaticField(getStatic.declaringClass, getStatic.name))*/ + case _ => Set.empty + } + + /** + * Creates some taints for an array, but only taints some element. + * + * @param expression The expression, referring to the array. + * @param taintedElement The array element, which will be tainted. + * @param statement The statement, containing the expression. + * @return An ArrayElement fact for the expression and the tainted element. + */ + private def createNewArrayElementTaints(expression: Expr[V], taintedElement: Int, + statement: DeclaredMethodJavaStatement): Set[TaintFact] = + createNewTaints(expression, statement).map { + case Variable(variableIndex) => ArrayElement(variableIndex, taintedElement) + // We do not nest taints. Instead, we taint the whole inner array. + case ArrayElement(variableIndex, _) => ArrayElement(variableIndex, taintedElement) + case InstanceField(variableIndex, _, _) => ArrayElement(variableIndex, taintedElement) + } + + private def createNewInstanceFieldTaints(expression: Expr[V], declaringClass: ObjectType, + name: String, statement: DeclaredMethodJavaStatement): Set[TaintFact] = + createNewTaints(expression, statement).map { + case Variable(variableIndex) => InstanceField(variableIndex, declaringClass, name) + // We do not nest taints. Instead, taint the whole field. + case ArrayElement(variableIndex, _) => InstanceField(variableIndex, declaringClass, name) + case InstanceField(variableIndex, _, _) => + InstanceField(variableIndex, declaringClass, name) + } + + /** + * Taints actual parameters on in the caller context, if the corresponding formal parameter in + * the `callee` context is tainted. + * Additionally propagates taints of static fields. + * + * @param callee The callee. + * @param call The call, calling the `callee`. + * @param calleeFacts The facts in the `callee` context. + * @param source The entity, which is analyzed. + * @param isCallFlow True, if this method is called in callFlow. In this case, FlowFacts will + * be propagated to the caller context. + * @return The data flow facts in the caller context. + */ + private def taintActualsIfFormalsTainted( + callee: DeclaredMethod, + call: DeclaredMethodJavaStatement, + calleeFacts: Set[TaintFact], + source: (DeclaredMethod, TaintFact), + isCallFlow: Boolean = true + ): Set[TaintFact] = { + val stmt = call.stmt + val callStatement = asCall(stmt) + val staticCall = callee.definedMethod.isStatic + val thisOffset = if (staticCall) 0 else 1 + val formalParameterIndices = (0 until callStatement.descriptor.parametersCount) + .map(index => NewJavaIFDSProblem.switchParamAndVariableIndex(index + thisOffset, staticCall)) + val facts = scala.collection.mutable.Set.empty[TaintFact] + calleeFacts.foreach { + case Variable(index) if formalParameterIndices.contains(index) => + facts ++= createNewTaints( + callStatement.allParams(NewJavaIFDSProblem.switchParamAndVariableIndex(index, staticCall)), call + ) + case ArrayElement(index, taintedElement) if formalParameterIndices.contains(index) => + facts ++= createNewArrayElementTaints( + callStatement.allParams(NewJavaIFDSProblem.switchParamAndVariableIndex(index, staticCall)), + taintedElement, call + ) + case InstanceField(index, declaringClass, name) if formalParameterIndices.contains(index) => + facts ++= createNewInstanceFieldTaints( + callStatement.allParams(NewJavaIFDSProblem.switchParamAndVariableIndex(index, staticCall)), + declaringClass, name, call + ) + case staticField: StaticField => facts += staticField + // If the source was reached in a callee, create a flow fact from this method to the sink. + case calleeFact: FlowFact if isCallFlow => + val callerFact = applyFlowFactFromCallee(calleeFact, source) + if (callerFact.isDefined) facts += callerFact.get + case _ => // Nothing to do + } + facts.toSet + } +} \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/taint/ForwardTaintProblem.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/taint/ForwardTaintProblem.scala new file mode 100644 index 0000000000..9039effbd7 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/old/taint/ForwardTaintProblem.scala @@ -0,0 +1,344 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.old.taint + +import org.opalj.br.DeclaredMethod +import org.opalj.br.analyses.SomeProject +import org.opalj.tac._ +import org.opalj.tac.fpcf.analyses.ifds.JavaMethod +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V +import org.opalj.tac.fpcf.analyses.ifds.old.{JavaIFDSProblem, DeclaredMethodJavaStatement} +import org.opalj.tac.fpcf.analyses.ifds.taint._ +import org.opalj.tac.fpcf.properties._ +import org.opalj.tac.fpcf.analyses.ifds.{JavaIFDSProblem => NewJavaIFDSProblem} + +abstract class ForwardTaintProblem(project: SomeProject) extends JavaIFDSProblem[TaintFact](project) with TaintProblem[DeclaredMethod, DeclaredMethodJavaStatement, TaintFact] { + override def nullFact: TaintFact = TaintNullFact + + /** + * If a variable gets assigned a tainted value, the variable will be tainted. + */ + override def normalFlow(statement: DeclaredMethodJavaStatement, successor: Option[DeclaredMethodJavaStatement], + in: Set[TaintFact]): Set[TaintFact] = + statement.stmt.astID match { + case Assignment.ASTID => + in ++ createNewTaints(statement.stmt.asAssignment.expr, statement, in) + case ArrayStore.ASTID => + val store = statement.stmt.asArrayStore + val definedBy = store.arrayRef.asVar.definedBy + val arrayIndex = TaintProblem.getIntConstant(store.index, statement.code) + if (isTainted(store.value, in)) { + if (arrayIndex.isDefined) + // Taint a known array index + definedBy.foldLeft(in) { (c, n) => + c + ArrayElement(n, arrayIndex.get) + } + else + // Taint the whole array if the index is unknown + definedBy.foldLeft(in) { (c, n) => + c + Variable(n) + } + } else if (arrayIndex.isDefined && definedBy.size == 1) + // Untaint if possible + in - ArrayElement(definedBy.head, arrayIndex.get) + else in + case PutField.ASTID => + val put = statement.stmt.asPutField + val definedBy = put.objRef.asVar.definedBy + if (isTainted(put.value, in)) + definedBy.foldLeft(in) { (in, defSite) => + in + InstanceField(defSite, put.declaringClass, put.name) + } + else + in + case PutStatic.ASTID => + val put = statement.stmt.asPutStatic + if (isTainted(put.value, in)) + in + StaticField(put.declaringClass, put.name) + else + in + case _ => in + } + + /** + * Propagates tainted parameters to the callee. If a call to the sink method with a tainted + * parameter is detected, no call-to-start + * edges will be created. + */ + override def callFlow(call: DeclaredMethodJavaStatement, callee: DeclaredMethod, + in: Set[TaintFact], a: (DeclaredMethod, TaintFact)): Set[TaintFact] = { + val callObject = asCall(call.stmt) + val allParams = callObject.allParams + var facts = Set.empty[TaintFact] + + if (relevantCallee(callee)) { + val allParamsWithIndices = allParams.zipWithIndex + in.foreach { + // Taint formal parameter if actual parameter is tainted + case Variable(index) => + allParamsWithIndices.foreach { + case (param, paramIndex) if param.asVar.definedBy.contains(index) => + facts += Variable(NewJavaIFDSProblem.switchParamAndVariableIndex( + paramIndex, + callee.definedMethod.isStatic + )) + case _ => // Nothing to do + } + + // Taint element of formal parameter if element of actual parameter is tainted + case ArrayElement(index, taintedIndex) => + allParamsWithIndices.foreach { + case (param, paramIndex) if param.asVar.definedBy.contains(index) => + facts += ArrayElement( + NewJavaIFDSProblem.switchParamAndVariableIndex(paramIndex, callee.definedMethod.isStatic), + taintedIndex + ) + case _ => // Nothing to do + } + + case InstanceField(index, declClass, taintedField) => + // Taint field of formal parameter if field of actual parameter is tainted + // Only if the formal parameter is of a type that may have that field! + allParamsWithIndices.foreach { + case (param, pIndex) if param.asVar.definedBy.contains(index) && + (NewJavaIFDSProblem.switchParamAndVariableIndex(pIndex, callee.definedMethod.isStatic) != -1 || + project.classHierarchy.isSubtypeOf(declClass, callee.declaringClassType)) => + facts += InstanceField( + NewJavaIFDSProblem.switchParamAndVariableIndex(pIndex, callee.definedMethod.isStatic), + declClass, taintedField + ) + case _ => // Nothing to do + } + + case sf: StaticField => facts += sf + + case _ => // Nothing to do + } + } + + facts + } + + /** + * Taints an actual parameter, if the corresponding formal parameter was tainted in the callee. + * If the callee's return value was tainted and it is assigned to a variable in the callee, the + * variable will be tainted. + * If a FlowFact held in the callee, this method will be appended to a new FlowFact, which holds + * at this method. + * Creates new taints and FlowFacts, if necessary. + * If the sanitize method was called, nothing will be tainted. + */ + override def returnFlow(call: DeclaredMethodJavaStatement, callee: DeclaredMethod, exit: DeclaredMethodJavaStatement, + successor: DeclaredMethodJavaStatement, in: Set[TaintFact]): Set[TaintFact] = { + + /** + * Checks whether the callee's formal parameter is of a reference type. + */ + def isRefTypeParam(index: Int): Boolean = + if (index == -1) true + else { + val parameterOffset = if (callee.definedMethod.isStatic) 0 else 1 + callee.descriptor.parameterType( + NewJavaIFDSProblem.switchParamAndVariableIndex(index, callee.definedMethod.isStatic) + - parameterOffset + ).isReferenceType + } + + if (sanitizesReturnValue(callee)) return Set.empty + val callStatement = asCall(call.stmt) + val allParams = callStatement.allParams + var flows: Set[TaintFact] = Set.empty + in.foreach { + // Taint actual parameter if formal parameter is tainted + case Variable(index) if index < 0 && index > -100 && isRefTypeParam(index) => + val param = allParams( + NewJavaIFDSProblem.switchParamAndVariableIndex(index, callee.definedMethod.isStatic) + ) + flows ++= param.asVar.definedBy.iterator.map(Variable) + + // Taint element of actual parameter if element of formal parameter is tainted + case ArrayElement(index, taintedIndex) if index < 0 && index > -100 => + val param = allParams( + NewJavaIFDSProblem.switchParamAndVariableIndex(index, callee.definedMethod.isStatic) + ) + flows ++= param.asVar.definedBy.iterator.map(ArrayElement(_, taintedIndex)) + + case InstanceField(index, declClass, taintedField) if index < 0 && index > -10 => + // Taint field of actual parameter if field of formal parameter is tainted + val param = + allParams(NewJavaIFDSProblem.switchParamAndVariableIndex(index, callee.definedMethod.isStatic)) + param.asVar.definedBy.foreach { defSite => + flows += InstanceField(defSite, declClass, taintedField) + } + + case sf: StaticField => flows += sf + + // Track the call chain to the sink back + case FlowFact(flow) if !flow.contains(JavaMethod(call.method)) => + flows += FlowFact(JavaMethod(call.method) +: flow) + case _ => + } + + // Propagate taints of the return value + if (exit.stmt.astID == ReturnValue.ASTID && call.stmt.astID == Assignment.ASTID) { + val returnValueDefinedBy = exit.stmt.asReturnValue.expr.asVar.definedBy + in.foreach { + case Variable(index) if returnValueDefinedBy.contains(index) => + flows += Variable(call.index) + case ArrayElement(index, taintedIndex) if returnValueDefinedBy.contains(index) => + flows += ArrayElement(call.index, taintedIndex) + case InstanceField(index, declClass, taintedField) if returnValueDefinedBy.contains(index) => + flows += InstanceField(call.index, declClass, taintedField) + case TaintNullFact => + val taints = createTaints(callee, call) + if (taints.nonEmpty) flows ++= taints + case _ => // Nothing to do + } + } + val flowFact = createFlowFact(callee, call, in) + if (flowFact.isDefined) flows += flowFact.get + + flows + } + + /** + * Removes taints according to `sanitizeParamters`. + */ + override def callToReturnFlow(call: DeclaredMethodJavaStatement, successor: DeclaredMethodJavaStatement, + in: Set[TaintFact], + source: (DeclaredMethod, TaintFact)): Set[TaintFact] = + in.filter(!sanitizesParameter(call, _)) + + /** + * Called, when the exit to return facts are computed for some `callee` with the null fact and + * the callee's return value is assigned to a vairbale. + * Creates a taint, if necessary. + * + * @param callee The called method. + * @param call The call. + * @return Some variable fact, if necessary. Otherwise none. + */ + protected def createTaints(callee: DeclaredMethod, call: DeclaredMethodJavaStatement): Set[TaintFact] + + /** + * Called, when the call to return facts are computed for some `callee`. + * Creates a FlowFact, if necessary. + * + * @param callee The method, which was called. + * @param call The call. + * @return Some FlowFact, if necessary. Otherwise None. + */ + protected def createFlowFact(callee: DeclaredMethod, call: DeclaredMethodJavaStatement, + in: Set[TaintFact]): Option[FlowFact] + + /** + * If a parameter is tainted, the result will also be tainted. + * We assume that the callee does not call the source method. + */ + override def outsideAnalysisContext(callee: DeclaredMethod): Option[OutsideAnalysisContextHandler] = { + super.outsideAnalysisContext(callee) match { + case Some(_) => Some(((call: DeclaredMethodJavaStatement, successor: DeclaredMethodJavaStatement, in: Set[TaintFact]) => { + val allParams = asCall(call.stmt).receiverOption ++ asCall(call.stmt).params + if (call.stmt.astID == Assignment.ASTID && in.exists { + case Variable(index) => + allParams.zipWithIndex.exists { + case (param, _) if param.asVar.definedBy.contains(index) => true + case _ => false + } + case ArrayElement(index, _) => + allParams.zipWithIndex.exists { + case (param, _) if param.asVar.definedBy.contains(index) => true + case _ => false + } + case _ => false + }) Set(Variable(call.index)) + else Set.empty + }): OutsideAnalysisContextHandler) + case None => None + } + + } + + /** + * Checks, if a `callee` should be analyzed, i.e. callFlow and returnFlow should create facts. + * True by default. This method can be overwritten by a subclass. + * + * @param callee The callee. + * @return True, by default. + */ + protected def relevantCallee(callee: DeclaredMethod): Boolean = true + + /** + * Creates new facts for an assignment. A new fact for the assigned variable will be created, + * if the expression contains a tainted variable + * + * @param expression The source expression of the assignment + * @param statement The assignment statement + * @param in The incoming facts + * @return The new facts, created by the assignment + */ + private def createNewTaints(expression: Expr[V], statement: DeclaredMethodJavaStatement, in: Set[TaintFact]): Set[TaintFact] = + expression.astID match { + case Var.ASTID => + val definedBy = expression.asVar.definedBy + in.collect { + case Variable(index) if definedBy.contains(index) => + Variable(statement.index) + } + case ArrayLoad.ASTID => + val loadExpression = expression.asArrayLoad + val arrayDefinedBy = loadExpression.arrayRef.asVar.definedBy + if (in.exists { + // One specific array element may be tainted + case ArrayElement(index, taintedElement) => + val loadedIndex = TaintProblem.getIntConstant(loadExpression.index, statement.code) + arrayDefinedBy.contains(index) && + (loadedIndex.isEmpty || taintedElement == loadedIndex.get) + // Or the whole array + case Variable(index) => arrayDefinedBy.contains(index) + case _ => false + }) Set(Variable(statement.index)) + else + Set.empty + case GetField.ASTID => + val get = expression.asGetField + val objectDefinedBy = get.objRef.asVar.definedBy + if (in.exists { + // The specific field may be tainted + case InstanceField(index, _, taintedField) => + taintedField == get.name && objectDefinedBy.contains(index) + // Or the whole object + case Variable(index) => objectDefinedBy.contains(index) + case _ => false + }) + Set(Variable(statement.index)) + else + Set.empty + case GetStatic.ASTID => + val get = expression.asGetStatic + if (in.contains(StaticField(get.declaringClass, get.name))) + Set(Variable(statement.index)) + else Set.empty + case BinaryExpr.ASTID | PrefixExpr.ASTID | Compare.ASTID | PrimitiveTypecastExpr.ASTID | + NewArray.ASTID | ArrayLength.ASTID => + (0 until expression.subExprCount).foldLeft(Set.empty[TaintFact])((acc, subExpr) => + acc ++ createNewTaints(expression.subExpr(subExpr), statement, in)) + case _ => Set.empty + } + + /** + * Checks, if the result of some variable expression could be tainted. + * + * @param expression The variable expression. + * @param in The current data flow facts. + * @return True, if the expression could be tainted + */ + private def isTainted(expression: Expr[V], in: Set[TaintFact]): Boolean = { + val definedBy = expression.asVar.definedBy + expression.isVar && in.exists { + case Variable(index) => definedBy.contains(index) + case ArrayElement(index, _) => definedBy.contains(index) + case InstanceField(index, _, _) => definedBy.contains(index) + case _ => false + } + } +} \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/taint/ForwardTaintProblem.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/taint/ForwardTaintProblem.scala new file mode 100644 index 0000000000..b75afe37a2 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/taint/ForwardTaintProblem.scala @@ -0,0 +1,367 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.taint + +import org.opalj.br.Method +import org.opalj.br.analyses.{DeclaredMethodsKey, SomeProject} +import org.opalj.ifds.Dependees.Getter +import org.opalj.tac._ +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V +//import org.opalj.tac.fpcf.analyses.ifds.taint.summaries.TaintSummaries +import org.opalj.tac.fpcf.analyses.ifds.{JavaIFDSProblem, JavaMethod, JavaStatement} +import org.opalj.tac.fpcf.properties._ + +//import java.io.File + +abstract class ForwardTaintProblem(project: SomeProject) + extends JavaIFDSProblem[TaintFact](project) + with TaintProblem[Method, JavaStatement, TaintFact] { + val declaredMethods = project.get(DeclaredMethodsKey) + + def useSummaries: Boolean = false + //lazy val summaries: TaintSummaries = TaintSummaries(new File(getClass.getResource("/summaries/").getPath).listFiles().toList) + + override def nullFact: TaintFact = TaintNullFact + + override def needsPredecessor(statement: JavaStatement): Boolean = false + + /** + * If a variable gets assigned a tainted value, the variable will be tainted. + */ + override def normalFlow(statement: JavaStatement, in: TaintFact, predecessor: Option[JavaStatement]): Set[TaintFact] = { + statement.stmt.astID match { + case Assignment.ASTID => + Set(in) ++ createNewTaints(statement.stmt.asAssignment.expr, statement, in) + case ArrayStore.ASTID => + val store = statement.stmt.asArrayStore + val definedBy = store.arrayRef.asVar.definedBy + val arrayIndex = TaintProblem.getIntConstant(store.index, statement.code) + if (isTainted(store.value, in)) { + if (arrayIndex.isDefined) { + // Taint a known array index + Set(in) ++ definedBy.map { ArrayElement(_, arrayIndex.get) } + } else + // Taint the whole array if the index is unknown + Set(in) ++ definedBy.map { Variable(_) } + } else if (arrayIndex.isDefined && definedBy.size == 1 && in == ArrayElement(definedBy.head, arrayIndex.get)) { + // untaint + Set() + } else Set(in) + case PutField.ASTID => + val put = statement.stmt.asPutField + val definedBy = put.objRef.asVar.definedBy + if (isTainted(put.value, in)) { + // RHS is tainted, thus the lhs as well + Set(in) ++ definedBy.map { InstanceField(_, put.declaringClass, put.name) } + } else if (isTainted(put.objRef, in)) { + // the lhs is tainted and overwritten + Set() + } else { + // if the taint is not affected, just leave it alive + Set(in) + } + case PutStatic.ASTID => + val put = statement.stmt.asPutStatic + if (isTainted(put.value, in)) { + // RHS is tainted, thus the lhs as well + Set(in, StaticField(put.declaringClass, put.name)) + } else if (in == StaticField(put.declaringClass, put.name)) { + // If LHS is equal to the taint, untaint the field here + Set.empty + } else { + // if the taint is not affected, just leave it alive + Set(in) + } + case _ => Set(in) + } + } + + /** + * Propagates tainted parameters to the callee. If a call to the sink method with a tainted + * parameter is detected, no call-to-start + * edges will be created. + */ + override def callFlow(call: JavaStatement, callee: Method, in: TaintFact): Set[TaintFact] = { + val callObject = JavaIFDSProblem.asCall(call.stmt) + val allParams = callObject.allParams + + val allParamsWithIndices = allParams.zipWithIndex + in match { + // Taint formal parameter if actual parameter is tainted + case Variable(index) => + allParamsWithIndices.flatMap { + case (param, paramIndex) if param.asVar.definedBy.contains(index) => + Some(Variable(JavaIFDSProblem.switchParamAndVariableIndex( + paramIndex, + callee.isStatic + ))) + case _ => None // Nothing to do + }.toSet + + // Taint element of formal parameter if element of actual parameter is tainted + case ArrayElement(index, taintedIndex) => + allParamsWithIndices.flatMap { + case (param, paramIndex) if param.asVar.definedBy.contains(index) => + Some(ArrayElement( + JavaIFDSProblem.switchParamAndVariableIndex(paramIndex, callee.isStatic), + taintedIndex + )) + case _ => None // Nothing to do + }.toSet + case InstanceField(index, declClass, taintedField) => + // Taint field of formal parameter if field of actual parameter is tainted + // Only if the formal parameter is of a type that may have that field! + allParamsWithIndices.flatMap { + case (param, pIndex) if param.asVar.definedBy.contains(index) && + (JavaIFDSProblem.switchParamAndVariableIndex(pIndex, callee.isStatic) != -1 || + project.classHierarchy.isSubtypeOf(declClass, declaredMethods(callee).declaringClassType)) => + Some(InstanceField( + JavaIFDSProblem.switchParamAndVariableIndex(pIndex, callee.isStatic), + declClass, taintedField + )) + case _ => None // Nothing to do + }.toSet + + case sf: StaticField => Set(sf) + + case _ => Set() // Nothing to do + + } + } + + /** + * Taints an actual parameter, if the corresponding formal parameter was tainted in the callee. + * If the callee's return value was tainted and it is assigned to a variable in the callee, the + * variable will be tainted. + * If a FlowFact held in the callee, this method will be appended to a new FlowFact, which holds + * at this method. + * Creates new taints and FlowFacts, if necessary. + * If the sanitize method was called, nothing will be tainted. + */ + override def returnFlow(exit: JavaStatement, in: TaintFact, call: JavaStatement, callFact: TaintFact, successor: JavaStatement): Set[TaintFact] = { + if (!isPossibleReturnFlow(exit, successor)) return Set.empty + + val callee = exit.callable + if (sanitizesReturnValue(callee)) return Set.empty + val callStatement = JavaIFDSProblem.asCall(call.stmt) + val allParams = callStatement.allParams + var flows: Set[TaintFact] = Set.empty + in match { + // Taint actual parameter if formal parameter is tainted + case Variable(index) if index < 0 && index > -100 && JavaIFDSProblem.isRefTypeParam(callee, index) => + val param = allParams( + JavaIFDSProblem.switchParamAndVariableIndex(index, callee.isStatic) + ) + flows ++= param.asVar.definedBy.iterator.map(Variable) + + // Taint element of actual parameter if element of formal parameter is tainted + case ArrayElement(index, taintedIndex) if index < 0 && index > -100 => + val param = allParams( + JavaIFDSProblem.switchParamAndVariableIndex(index, callee.isStatic) + ) + flows ++= param.asVar.definedBy.iterator.map(ArrayElement(_, taintedIndex)) + + case InstanceField(index, declClass, taintedField) if index < 0 && index > -10 => + // Taint field of actual parameter if field of formal parameter is tainted + val param = + allParams(JavaIFDSProblem.switchParamAndVariableIndex(index, callee.isStatic)) + param.asVar.definedBy.foreach { defSite => + flows += InstanceField(defSite, declClass, taintedField) + } + + case sf: StaticField => flows += sf + + // Track the call chain to the sink back + case FlowFact(flow) if !flow.contains(JavaMethod(call.method)) => + flows += FlowFact(JavaMethod(call.method) +: flow) + case _ => + } + + // Propagate taints of the return value + if (exit.stmt.astID == ReturnValue.ASTID && call.stmt.astID == Assignment.ASTID) { + val returnValueDefinedBy = exit.stmt.asReturnValue.expr.asVar.definedBy + in match { + case Variable(index) if returnValueDefinedBy.contains(index) => + flows += Variable(call.index) + case ArrayElement(index, taintedIndex) if returnValueDefinedBy.contains(index) => + flows += ArrayElement(call.index, taintedIndex) + case InstanceField(index, declClass, taintedField) if returnValueDefinedBy.contains(index) => + flows += InstanceField(call.index, declClass, taintedField) + case TaintNullFact => + val taints = createTaints(callee, call) + if (taints.nonEmpty) flows ++= taints + case _ => // Nothing to do + } + } + val flowFact = createFlowFact(callee, call, in) + if (flowFact.isDefined) flows += flowFact.get + + flows + } + + /** + * Removes taints according to `sanitizesParameter`. + */ + + override def callToReturnFlow(call: JavaStatement, in: TaintFact, successor: JavaStatement): Set[TaintFact] = { + if (sanitizesParameter(call, in)) return Set.empty + + val callStmt = JavaIFDSProblem.asCall(call.stmt) + val allParams = callStmt.allParams + + def isRefTypeParam(index: Int): Boolean = { + allParams.find(p => p.asVar.definedBy.contains(index)) match { + case Some(param) => param.asVar.value.isReferenceValue + case None => false + } + } + + + + + if (icfg.getCalleesIfCallStatement(call).isEmpty) { + // If the call does not have any callees, the code is unknown + // and we safely handle it as the identity + Set(in) + } else { + // Otherwise use the java call semantics + in match { + // Local variables that are of a reference type flow through the callee + case Variable(index) if isRefTypeParam(index) => Set.empty + // Arrays are references passed-by-value, thus their contents might change in the callee + case ArrayElement(index, _) if allParams.exists(p => p.asVar.definedBy.contains(index)) => Set.empty + // Fields can be written by reference, thus always through through the callee + case InstanceField(index, _, _) if allParams.exists(p => p.asVar.definedBy.contains(index)) => Set.empty + // Static fields are accessible everywhere, thus have to flow through all callee. + case StaticField(_, _) => Set.empty + // All facts that do not match any parameter or base object, as well as primitives flow over a call + case f: TaintFact => Set(f) + } + } + } + + /** + * Called, when the exit to return facts are computed for some `callee` with the null fact and + * the callee's return value is assigned to a variable. + * Creates a taint, if necessary. + * + * @param callee The called method. + * @param call The call. + * @return Some variable fact, if necessary. Otherwise none. + */ + protected def createTaints(callee: Method, call: JavaStatement): Set[TaintFact] + + /** + * Called, when the call to return facts are computed for some `callee`. + * Creates a FlowFact, if necessary. + * + * @param callee The method, which was called. + * @param call The call. + * @return Some FlowFact, if necessary. Otherwise None. + */ + protected def createFlowFact(callee: Method, call: JavaStatement, + in: TaintFact): Option[FlowFact] + + /** + * If a parameter is tainted, the result will also be tainted. + * We assume that the callee does not call the source method. + */ + override def outsideAnalysisContext(callee: Method): Option[OutsideAnalysisContextHandler] = { + super.outsideAnalysisContext(callee) match { + case Some(_) => Some(((call: JavaStatement, successor: JavaStatement, in: TaintFact, _: Getter) => { + val allParams = JavaIFDSProblem.asCall(call.stmt).receiverOption ++ JavaIFDSProblem.asCall(call.stmt).params + if (call.stmt.astID == Assignment.ASTID && (in match { + case Variable(index) => + allParams.zipWithIndex.exists { + case (param, _) if param.asVar.definedBy.contains(index) => true + case _ => false + } + case ArrayElement(index, _) => + allParams.zipWithIndex.exists { + case (param, _) if param.asVar.definedBy.contains(index) => true + case _ => false + } + case _ => false + })) Set(Variable(call.index)) + else Set.empty + }): OutsideAnalysisContextHandler) + case None => None + } + } + + /** + * Creates new facts for an assignment. A new fact for the assigned variable will be created, + * if the expression contains a tainted variable + * + * @param expression The source expression of the assignment + * @param statement The assignment statement + * @param in The incoming facts + * @return The new facts, created by the assignment + */ + private def createNewTaints(expression: Expr[V], statement: JavaStatement, in: TaintFact): Set[TaintFact] = { + expression.astID match { + case Var.ASTID => + val definedBy = expression.asVar.definedBy + in match { + case Variable(index) if definedBy.contains(index) => + Set(Variable(statement.index)) + case _ => Set() + } + case ArrayLoad.ASTID => + val loadExpression = expression.asArrayLoad + val arrayDefinedBy = loadExpression.arrayRef.asVar.definedBy + if (in match { + // One specific array element may be tainted + case ArrayElement(index, taintedElement) => + val loadedIndex = TaintProblem.getIntConstant(loadExpression.index, statement.code) + arrayDefinedBy.contains(index) && + (loadedIndex.isEmpty || taintedElement == loadedIndex.get) + // Or the whole array + case Variable(index) => arrayDefinedBy.contains(index) + case _ => false + }) Set(Variable(statement.index)) + else + Set.empty + case GetField.ASTID => + val get = expression.asGetField + val objectDefinedBy = get.objRef.asVar.definedBy + if (in match { + // The specific field may be tainted + case InstanceField(index, _, taintedField) => + taintedField == get.name && objectDefinedBy.contains(index) + // Or the whole object + case Variable(index) => objectDefinedBy.contains(index) + case _ => false + }) + Set(Variable(statement.index)) + else + Set.empty + case GetStatic.ASTID => + val get = expression.asGetStatic + if (in == StaticField(get.declaringClass, get.name)) + Set(Variable(statement.index)) + else Set.empty + case BinaryExpr.ASTID | PrefixExpr.ASTID | Compare.ASTID | PrimitiveTypecastExpr.ASTID | + NewArray.ASTID | ArrayLength.ASTID => + (0 until expression.subExprCount).foldLeft(Set.empty[TaintFact])((acc, subExpr) => + acc ++ createNewTaints(expression.subExpr(subExpr), statement, in)) + case _ => Set.empty + } + } + + /** + * Checks, if the result of some variable expression could be tainted. + * + * @param expression The variable expression. + * @param in The current data flow facts. + * @return True, if the expression could be tainted + */ + protected def isTainted(expression: Expr[V], in: TaintFact): Boolean = { + val definedBy = expression.asVar.definedBy + expression.isVar && (in match { + case Variable(index) => definedBy.contains(index) + case ArrayElement(index, _) => definedBy.contains(index) + case InstanceField(index, _, _) => definedBy.contains(index) + case _ => false + }) + } +} \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/taint/TaintProblem.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/taint/TaintProblem.scala new file mode 100644 index 0000000000..030400361d --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/taint/TaintProblem.scala @@ -0,0 +1,59 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.taint + +import org.opalj.tac.{Assignment, Expr, Stmt} +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem.V + +trait TaintProblem[C, Statement, IFDSFact] { + + /** + * Checks, if some `callee` is a sanitizer, which sanitizes its return value. + * In this case, no return flow facts will be created. + * + * @param callee The method, which was called. + * @return True, if the method is a sanitizer. + */ + protected def sanitizesReturnValue(callee: C): Boolean + + /** + * Called in callToReturnFlow. This method can return whether the input fact + * will be removed after `callee` was called. I.e. the method could sanitize parameters. + * + * @param call The call statement. + * @param in The fact which holds before the call. + * @return Whether in will be removed after the call. + */ + protected def sanitizesParameter(call: Statement, in: IFDSFact): Boolean +} + +object TaintProblem { + + /** + * Checks, if some expression always evaluates to the same int constant. + * + * @param expression The expression. + * @param code The TAC code, which contains the expression. + * @return Some int, if this analysis is sure that `expression` always evaluates to the same int + * constant, None otherwise. + */ + def getIntConstant(expression: Expr[V], code: Array[Stmt[V]]): Option[Int] = { + if (expression.isIntConst) Some(expression.asIntConst.value) + else if (expression.isVar) { + val definedByIterator = expression.asVar.definedBy.iterator + var allDefinedByWereConstant = true + var result = scala.collection.mutable.Seq.empty[Int] + while (definedByIterator.hasNext && allDefinedByWereConstant) { + val definedBy = definedByIterator.next() + if (definedBy >= 0) { + val stmt = code(definedBy) + if (stmt.astID == Assignment.ASTID && stmt.asAssignment.expr.isIntConst) + result :+= stmt.asAssignment.expr.asIntConst.value + else allDefinedByWereConstant = false + } else allDefinedByWereConstant = false + } + if (allDefinedByWereConstant && result.tail.forall(_ == result.head)) + Some(result.head) + else None + } else None + } +} \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/taint/summaries/README.md b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/ifds/taint/summaries/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/IFDSProperty.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/IFDSProperty.scala deleted file mode 100644 index 07bd837351..0000000000 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/IFDSProperty.scala +++ /dev/null @@ -1,37 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package tac -package fpcf -package properties - -import org.opalj.fpcf.Property -import org.opalj.fpcf.PropertyMetaInformation -import org.opalj.value.KnownTypedValue -import org.opalj.tac.fpcf.analyses.Statement - -trait IFDSPropertyMetaInformation[DataFlowFact] extends PropertyMetaInformation - -abstract class IFDSProperty[DataFlowFact] - extends Property - with IFDSPropertyMetaInformation[DataFlowFact] { - - /** The type of the TAC domain. */ - type V = DUVar[KnownTypedValue] - - /** - * Maps exit statements to the data flow facts, which hold after them. - */ - def flows: Map[Statement, Set[DataFlowFact]] - - override def equals(other: Any): Boolean = other match { - case that: IFDSProperty[DataFlowFact @unchecked] => - // We cached the "hashCode" to make the following comparison more efficient; - // note that all properties are eventually added to some set and therefore - // the hashCode is required anyway! - (this eq that) || (this.hashCode == that.hashCode && this.flows == that.flows) - case _ => - false - } - - override lazy val hashCode: Int = flows.hashCode() -} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/OldTaint.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/OldTaint.scala new file mode 100644 index 0000000000..44501603d3 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/OldTaint.scala @@ -0,0 +1,25 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.properties + +import org.opalj.fpcf.PropertyKey +import org.opalj.ifds.{IFDSProperty, IFDSPropertyMetaInformation} +import org.opalj.tac.fpcf.analyses.ifds.old.DeclaredMethodJavaStatement +import org.opalj.tac.fpcf.properties._ + +case class OldTaint(flows: Map[DeclaredMethodJavaStatement, Set[TaintFact]], debugData: Map[DeclaredMethodJavaStatement, Set[TaintFact]] = Map.empty) extends IFDSProperty[DeclaredMethodJavaStatement, TaintFact] { + + override type Self = OldTaint + override def create(result: Map[DeclaredMethodJavaStatement, Set[TaintFact]]): IFDSProperty[DeclaredMethodJavaStatement, TaintFact] = new OldTaint(result) + override def create(result: Map[DeclaredMethodJavaStatement, Set[TaintFact]], debugData: Map[DeclaredMethodJavaStatement, Set[TaintFact]]): IFDSProperty[DeclaredMethodJavaStatement, TaintFact] = new OldTaint(result, debugData) + + override def key: PropertyKey[OldTaint] = OldTaint.key +} + +object OldTaint extends IFDSPropertyMetaInformation[DeclaredMethodJavaStatement, TaintFact] { + + override type Self = OldTaint + override def create(result: Map[DeclaredMethodJavaStatement, Set[TaintFact]]): IFDSProperty[DeclaredMethodJavaStatement, TaintFact] = new OldTaint(result) + override def create(result: Map[DeclaredMethodJavaStatement, Set[TaintFact]], debugData: Map[DeclaredMethodJavaStatement, Set[TaintFact]]): IFDSProperty[DeclaredMethodJavaStatement, TaintFact] = new OldTaint(result, debugData) + + val key: PropertyKey[OldTaint] = PropertyKey.create("OldTaint", new OldTaint(Map.empty)) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/Taint.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/Taint.scala new file mode 100644 index 0000000000..efe40947c0 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/Taint.scala @@ -0,0 +1,25 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.properties + +import org.opalj.fpcf.PropertyKey +import org.opalj.ifds.{IFDSProperty, IFDSPropertyMetaInformation} +import org.opalj.tac.fpcf.analyses.ifds.JavaStatement +import org.opalj.tac.fpcf.properties._ + +case class Taint(flows: Map[JavaStatement, Set[TaintFact]], debugData: Map[JavaStatement, Set[TaintFact]] = Map.empty) extends IFDSProperty[JavaStatement, TaintFact] { + + override type Self = Taint + override def create(result: Map[JavaStatement, Set[TaintFact]]): IFDSProperty[JavaStatement, TaintFact] = new Taint(result) + override def create(result: Map[JavaStatement, Set[TaintFact]], debugData: Map[JavaStatement, Set[TaintFact]]): IFDSProperty[JavaStatement, TaintFact] = new Taint(result, debugData) + + override def key: PropertyKey[Taint] = Taint.key +} + +object Taint extends IFDSPropertyMetaInformation[JavaStatement, TaintFact] { + + override type Self = Taint + override def create(result: Map[JavaStatement, Set[TaintFact]]): IFDSProperty[JavaStatement, TaintFact] = new Taint(result) + override def create(result: Map[JavaStatement, Set[TaintFact]], debugData: Map[JavaStatement, Set[TaintFact]]): IFDSProperty[JavaStatement, TaintFact] = new Taint(result, debugData) + + val key: PropertyKey[Taint] = PropertyKey.create("Taint", new Taint(Map.empty)) +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/TaintFact.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/TaintFact.scala new file mode 100644 index 0000000000..6ee0e541df --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/properties/TaintFact.scala @@ -0,0 +1,60 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org +package opalj +package tac +package fpcf +package properties + +import org.opalj.br.ObjectType +import org.opalj.ifds.{AbstractIFDSFact, AbstractIFDSNullFact, Callable} + +trait TaintFact extends AbstractIFDSFact + + +case object TaintNullFact extends TaintFact with AbstractIFDSNullFact + +/** + * A tainted variable. + * + * @param index The variable's definition site. + */ +case class Variable(index: Int) extends TaintFact + +/** + * A tainted array element. + * + * @param index The array's definition site. + * @param element The index of the tainted element in the array. + */ +case class ArrayElement(index: Int, element: Int) extends TaintFact + +/** + * A tainted static field. + * + * @param classType The field's class. + * @param fieldName The field's name. + */ +case class StaticField(classType: ObjectType, fieldName: String) extends TaintFact + +/** + * A tainted instance field. + * + * @param index The definition site of the field's value. + * @param classType The field's type. + * @param fieldName The field's value. + */ +case class InstanceField(index: Int, classType: ObjectType, fieldName: String) extends TaintFact + +/** + * A path of method calls, originating from the analyzed method, over which a tainted variable + * reaches the sink. + * + * @param flow A sequence of method calls, originating from but not including this method. + */ +case class FlowFact(flow: Seq[Callable]) extends TaintFact { + override val hashCode: Int = { + var r = 1 + flow.foreach(f => r = (r + f.hashCode()) * 31) + r + } +} \ No newline at end of file diff --git a/OPAL/tac/src/test/scala/org/opalj/tac/fpcf/analyses/ifds/taint/ForwardTaintAnalysisFixture.scala b/OPAL/tac/src/test/scala/org/opalj/tac/fpcf/analyses/ifds/taint/ForwardTaintAnalysisFixture.scala new file mode 100644 index 0000000000..bdad59c511 --- /dev/null +++ b/OPAL/tac/src/test/scala/org/opalj/tac/fpcf/analyses/ifds/taint/ForwardTaintAnalysisFixture.scala @@ -0,0 +1,66 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.taint + +import org.opalj.br.Method +import org.opalj.br.analyses.{DeclaredMethodsKey, ProjectInformationKeys, SomeProject} +import org.opalj.br.fpcf.PropertyStoreKey +import org.opalj.fpcf.{PropertyBounds, PropertyStore} +import org.opalj.ifds.{IFDSAnalysis, IFDSAnalysisScheduler, IFDSPropertyMetaInformation} +import org.opalj.tac.cg.TypeProviderKey +import org.opalj.tac.fpcf.analyses.ifds.{JavaMethod, JavaStatement} +import org.opalj.tac.fpcf.properties._ + +/** + * An analysis that checks, if the return value of the method `source` can flow to the parameter of + * the method `sink`. + * + * @author Mario Trageser + */ +class ForwardTaintAnalysisFixture(project: SomeProject) + extends IFDSAnalysis()(project, new ForwardTaintProblemFixture(project), Taint) + +class ForwardTaintProblemFixture(p: SomeProject) extends ForwardTaintProblem(p) { + override def useSummaries = true + + /** + * The analysis starts with all public methods in TaintAnalysisTestClass. + */ + override val entryPoints: Seq[(Method, TaintFact)] = p.allProjectClassFiles.filter(classFile => + classFile.thisType.fqn == "org/opalj/fpcf/fixtures/taint/TaintAnalysisTestClass") + .flatMap(classFile => classFile.methods) + .filter(method => method.isPublic && outsideAnalysisContext(method).isEmpty) + .map(method => method -> TaintNullFact) + + /** + * The sanitize method is a sanitizer. + */ + override protected def sanitizesReturnValue(callee: Method): Boolean = callee.name == "sanitize" + + /** + * We do not sanitize paramters. + */ + override protected def sanitizesParameter(call: JavaStatement, in: TaintFact): Boolean = false + + /** + * Creates a new variable fact for the callee, if the source was called. + */ + override protected def createTaints(callee: Method, call: JavaStatement): Set[TaintFact] = + if (callee.name == "source") Set(Variable(call.index)) + else Set.empty + + /** + * Create a FlowFact, if sink is called with a tainted variable. + * Note, that sink does not accept array parameters. No need to handle them. + */ + override protected def createFlowFact(callee: Method, call: JavaStatement, + in: TaintFact): Option[FlowFact] = + if (callee.name == "sink" && in == Variable(-2)) Some(FlowFact(Seq(JavaMethod(call.method)))) + else None +} + +object ForwardTaintAnalysisFixtureScheduler extends IFDSAnalysisScheduler[TaintFact, Method, JavaStatement] { + override def init(p: SomeProject, ps: PropertyStore) = new ForwardTaintAnalysisFixture(p) + override def property: IFDSPropertyMetaInformation[JavaStatement, TaintFact] = Taint + override val uses: Set[PropertyBounds] = Set(PropertyBounds.ub(Taint)) + override def requiredProjectInformation: ProjectInformationKeys = Seq(TypeProviderKey, DeclaredMethodsKey, PropertyStoreKey) +} diff --git a/OPAL/tac/src/test/scala/org/opalj/tac/fpcf/analyses/ifds/taint/old/BackwardTaintAnalysisFixture.scala b/OPAL/tac/src/test/scala/org/opalj/tac/fpcf/analyses/ifds/taint/old/BackwardTaintAnalysisFixture.scala new file mode 100644 index 0000000000..8530e625c3 --- /dev/null +++ b/OPAL/tac/src/test/scala/org/opalj/tac/fpcf/analyses/ifds/taint/old/BackwardTaintAnalysisFixture.scala @@ -0,0 +1,90 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.taint.old + +import org.opalj.br.analyses.SomeProject +import org.opalj.br.{DeclaredMethod, Method} +import org.opalj.fpcf.PropertyStore +import org.opalj.ifds.IFDSPropertyMetaInformation +import org.opalj.tac.fpcf.analyses.ifds._ +import org.opalj.tac.fpcf.analyses.ifds.old._ +import org.opalj.tac.fpcf.analyses.ifds.old.taint.BackwardTaintProblem +import org.opalj.tac.fpcf.properties._ +import org.opalj.tac.fpcf.properties.OldTaint +import org.opalj.tac.fpcf.analyses.ifds.JavaIFDSProblem + +case class UnbalancedTaintFact(index: Int, innerFact: TaintFact, callChain: Array[Method]) + extends UnbalancedReturnFact[TaintFact] with TaintFact + +/** + * An analysis that checks, if the return value of a `source` method can flow to the parameter of a + * `sink` method. + * + * @author Mario Trageser + */ +class BackwardTaintAnalysisFixture(implicit val project: SomeProject) + extends BackwardIFDSAnalysis(new BackwardTaintProblemFixture(project), OldTaint) + +class BackwardTaintProblemFixture(p: SomeProject) extends BackwardTaintProblem(p) { + + override val entryPoints: Seq[(DeclaredMethod, TaintFact)] = p.allProjectClassFiles.filter(classFile => + classFile.thisType.fqn == "org/opalj/fpcf/fixtures/taint/TaintAnalysisTestClass") + .flatMap(_.methods).filter(_.name == "sink") + .map(method => declaredMethods(method) -> + Variable(JavaIFDSProblem.switchParamAndVariableIndex(0, isStaticMethod = true))) + + /** + * The sanitize method is the sanitizer. + */ + override protected def sanitizesReturnValue(callee: DeclaredMethod): Boolean = callee.name == "sanitize" + + /** + * We do not sanitize paramters. + */ + override protected def sanitizesParameter(call: DeclaredMethodJavaStatement, in: TaintFact): Boolean = false + + /** + * Create a flow fact, if a source method is called and the returned value is tainted. + * This is done in callToReturnFlow, because it may be the case that the callee never + * terminates. + * In this case, callFlow would never be called and no FlowFact would be created. + */ + override protected def createFlowFactAtCall(call: DeclaredMethodJavaStatement, in: Set[TaintFact], + source: (DeclaredMethod, TaintFact)): Option[FlowFact] = { + if (in.exists { + case Variable(index) => index == call.index + case _ => false + } && icfg.getCalleesIfCallStatement(call).get.exists(_.name == "source")) { + val callChain = currentCallChain(source) + // Avoid infinite loops. + if (!containsHeadTwice(callChain)) + Some(FlowFact(callChain.map(JavaMethod(_)))) + else None + } else None + } + + /** + * When a callee calls the source, we create a FlowFact with the caller's call chain. + */ + override protected def applyFlowFactFromCallee( + calleeFact: FlowFact, + source: (DeclaredMethod, TaintFact) + ): Option[FlowFact] = + Some(FlowFact(currentCallChain(source).map(JavaMethod(_)))) + + /** + * This analysis does not create FlowFacts at the beginning of a method. + * Instead, FlowFacts are created, when the return value of source is tainted. + */ + override protected def createFlowFactAtBeginningOfMethod( + in: Set[TaintFact], + source: (DeclaredMethod, TaintFact) + ): Option[FlowFact] = + None +} + +object BackwardTaintAnalysisFixtureScheduler extends IFDSAnalysisScheduler[TaintFact] { + + override def init(p: SomeProject, ps: PropertyStore) = new BackwardTaintAnalysisFixture()(p) + + override def property: IFDSPropertyMetaInformation[DeclaredMethodJavaStatement, TaintFact] = OldTaint +} diff --git a/OPAL/tac/src/test/scala/org/opalj/tac/fpcf/analyses/ifds/taint/old/ForwardTaintAnalysisFixture.scala b/OPAL/tac/src/test/scala/org/opalj/tac/fpcf/analyses/ifds/taint/old/ForwardTaintAnalysisFixture.scala new file mode 100644 index 0000000000..064be535f3 --- /dev/null +++ b/OPAL/tac/src/test/scala/org/opalj/tac/fpcf/analyses/ifds/taint/old/ForwardTaintAnalysisFixture.scala @@ -0,0 +1,66 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj.tac.fpcf.analyses.ifds.taint.old + +import org.opalj.br.DeclaredMethod +import org.opalj.br.analyses.SomeProject +import org.opalj.fpcf.PropertyStore +import org.opalj.ifds.IFDSPropertyMetaInformation +import org.opalj.tac.fpcf.analyses.ifds.JavaMethod +import org.opalj.tac.fpcf.analyses.ifds.old.{DeclaredMethodJavaStatement, ForwardIFDSAnalysis, IFDSAnalysisScheduler} +import org.opalj.tac.fpcf.analyses.ifds.old.taint.ForwardTaintProblem +import org.opalj.tac.fpcf.properties._ +import org.opalj.tac.fpcf.properties.OldTaint + +/** + * An analysis that checks, if the return value of the method `source` can flow to the parameter of + * the method `sink`. + * + * @author Mario Trageser + */ +class ForwardTaintAnalysisFixture(implicit val project: SomeProject) + extends ForwardIFDSAnalysis(new ForwardTaintProblemFixture(project), OldTaint) + +class ForwardTaintProblemFixture(p: SomeProject) extends ForwardTaintProblem(p) { + + /** + * The analysis starts with all public methods in TaintAnalysisTestClass. + */ + override val entryPoints: Seq[(DeclaredMethod, TaintFact)] = p.allProjectClassFiles.filter(classFile => + classFile.thisType.fqn == "org/opalj/fpcf/fixtures/taint/TaintAnalysisTestClass") + .flatMap(classFile => classFile.methods) + .filter(method => method.isPublic && outsideAnalysisContext(declaredMethods(method)).isEmpty) + .map(method => declaredMethods(method) -> TaintNullFact) + + /** + * The sanitize method is a sanitizer. + */ + override protected def sanitizesReturnValue(callee: DeclaredMethod): Boolean = callee.name == "sanitize" + + /** + * We do not sanitize paramters. + */ + override protected def sanitizesParameter(call: DeclaredMethodJavaStatement, in: TaintFact): Boolean = false + + /** + * Creates a new variable fact for the callee, if the source was called. + */ + override protected def createTaints(callee: DeclaredMethod, call: DeclaredMethodJavaStatement): Set[TaintFact] = + if (callee.name == "source") Set(Variable(call.index)) + else Set.empty + + /** + * Create a FlowFact, if sink is called with a tainted variable. + * Note, that sink does not accept array parameters. No need to handle them. + */ + override protected def createFlowFact(callee: DeclaredMethod, call: DeclaredMethodJavaStatement, + in: Set[TaintFact]): Option[FlowFact] = + if (callee.name == "sink" && in.contains(Variable(-2))) Some(FlowFact(Seq(JavaMethod(call.method)))) + else None +} + +object ForwardTaintAnalysisFixtureScheduler extends IFDSAnalysisScheduler[TaintFact] { + + override def init(p: SomeProject, ps: PropertyStore) = new ForwardTaintAnalysisFixture()(p) + + override def property: IFDSPropertyMetaInformation[DeclaredMethodJavaStatement, TaintFact] = OldTaint +} diff --git a/build.sbt b/build.sbt index fd44299380..aa540ca719 100644 --- a/build.sbt +++ b/build.sbt @@ -45,7 +45,7 @@ ThisBuild / logBuffered := false ThisBuild / javacOptions ++= Seq("-encoding", "utf8", "-source", "1.8") -ThisBuild /testOptions := { +ThisBuild / testOptions := { baseDirectory .map(bd => Seq(Tests.Argument("-u", bd.getAbsolutePath + "/shippable/testresults"))) .value @@ -62,7 +62,7 @@ ScalaUnidoc / unidoc / scalacOptions := { ScalaUnidoc / unidoc / scalacOptions ++= Opts.doc.sourceUrl( - "https://raw.githubusercontent.com/stg-tud/opal/" + + "https://raw.githubusercontent.com/stg-tud/opal/" + (if (isSnapshot.value) "develop" else "master") + "/€{FILE_PATH}.scala" ) @@ -104,7 +104,10 @@ lazy val buildSettings = PublishingOverwrite.onSnapshotOverwriteSettings ++ Seq(libraryDependencies ++= Dependencies.testlibs) ++ Seq(Defaults.itSettings: _*) ++ - Seq(unmanagedSourceDirectories.withRank(KeyRanks.Invisible) := (Compile / scalaSource).value :: Nil) ++ + Seq( + unmanagedSourceDirectories + .withRank(KeyRanks.Invisible) := (Compile / scalaSource).value :: Nil + ) ++ Seq( Test / unmanagedSourceDirectories := (Test / javaSource).value :: (Test / scalaSource).value :: Nil ) ++ @@ -120,6 +123,9 @@ lazy val buildSettings = // see https://github.com/sbt/sbt-assembly/issues/391 Seq(assembly / assemblyMergeStrategy := { case "module-info.class" => MergeStrategy.discard + case PathList("META-INF", "versions", xs @ _, "module-info.class") => MergeStrategy.discard + case PathList("META-INF", "native-image", xs @ _, "jnijavacpp", "jni-config.json") => MergeStrategy.discard + case PathList("META-INF", "native-image", xs @ _, "jnijavacpp", "reflect-config.json") => MergeStrategy.discard case other => (assembly / assemblyMergeStrategy).value(other) }) @@ -138,10 +144,11 @@ def getScalariformPreferences(dir: File) = { ******************************************************************************/ lazy val opal = `OPAL` lazy val `OPAL` = (project in file(".")) -// .configure(_.copy(id = "OPAL")) + // .configure(_.copy(id = "OPAL")) .settings((Defaults.coreDefaultSettings ++ Seq(publishArtifact := false)): _*) .enablePlugins(ScalaUnidocPlugin) .settings( + javaCppVersion := Dependencies.javaCppVersion, ScalaUnidoc / unidoc / unidocProjectFilter := inAnyProject -- inProjects( hermes, validate, @@ -161,6 +168,8 @@ lazy val `OPAL` = (project in file(".")) tac, de, av, + ll, + js, framework, // bp, (just temporarily...) tools, @@ -179,6 +188,7 @@ lazy val `Common` = (project in file("OPAL/common")) .settings(buildSettings: _*) .settings( name := "Common", + javaCppVersion := Dependencies.javaCppVersion, Compile / doc / scalacOptions := Opts.doc.title("OPAL-Common"), libraryDependencies ++= Dependencies.common(scalaVersion.value) ) @@ -237,7 +247,7 @@ lazy val `BytecodeRepresentation` = (project in file("OPAL/br")) Compile / doc / scalacOptions ++= Opts.doc.title("OPAL - Bytecode Representation"), libraryDependencies ++= Dependencies.br, // Test / publishArtifact := true // Needed to get access to class TestResources and TestSupport - ) + ) .dependsOn(si % "it->it;it->test;test->test;compile->compile") .dependsOn(bi % "it->it;it->test;test->test;compile->compile") .configs(IntegrationTest) @@ -278,6 +288,19 @@ lazy val `AbstractInterpretationFramework` = (project in file("OPAL/ai")) .dependsOn(br % "it->it;it->test;test->test;compile->compile") .configs(IntegrationTest) +lazy val ifds = `IFDS` +lazy val `IFDS` = (project in file("OPAL/ifds")) + .settings(buildSettings: _*) + .settings( + name := "IFDS", + Compile / doc / scalacOptions ++= Opts.doc.title("OPAL - IFDS"), + fork := true, + libraryDependencies ++= Dependencies.ifds + ) + .dependsOn(si % "it->it;it->test;test->test;compile->compile") + .dependsOn(br % "it->it;it->test;test->test;compile->compile") + .configs(IntegrationTest) + lazy val tac = `ThreeAddressCode` lazy val `ThreeAddressCode` = (project in file("OPAL/tac")) .settings(buildSettings: _*) @@ -290,6 +313,7 @@ lazy val `ThreeAddressCode` = (project in file("OPAL/tac")) run / fork := true ) .dependsOn(ai % "it->it;it->test;test->test;compile->compile") + .dependsOn(ifds % "it->it;it->test;test->test;compile->compile") .configs(IntegrationTest) lazy val ba = `BytecodeAssembler` @@ -328,6 +352,30 @@ lazy val `ArchitectureValidation` = (project in file("OPAL/av")) .dependsOn(de % "it->it;it->test;test->test;compile->compile") .configs(IntegrationTest) +lazy val ll = `LLVM` +lazy val `LLVM` = (project in file("OPAL/ll")) + .settings(buildSettings: _*) + .settings( + name := "LLVM", + Compile / doc / scalacOptions ++= Opts.doc.title("OPAL - LLVM"), + fork := true, + javaCppPresetLibs ++= Dependencies.javaCppPresetLibs, + javaCppVersion := Dependencies.javaCppVersion + ) + .dependsOn(tac % "it->it;it->test;test->test;compile->compile") + .configs(IntegrationTest) + +lazy val js = `JavaScript` +lazy val `JavaScript` = (project in file("OPAL/js")) + .settings(buildSettings: _*) + .settings( + name := "JavaScript", + Compile / doc / scalacOptions ++= Opts.doc.title("OPAL - JS"), + fork := true + ) + .dependsOn(tac % "it->it;it->test;test->test;compile->compile") + .configs(IntegrationTest) + lazy val framework = `Framework` lazy val `Framework` = (project in file("OPAL/framework")) .settings(buildSettings: _*) @@ -339,7 +387,9 @@ lazy val `Framework` = (project in file("OPAL/framework")) .dependsOn( ba % "it->it;it->test;test->test;compile->compile", av % "it->it;it->test;test->test;compile->compile", - tac % "it->it;it->test;test->test;compile->compile" + tac % "it->it;it->test;test->test;compile->compile", + ll % "it->it;it->test;test->test;compile->compile", + js % "it->it;it->test;test->test;compile->compile" ) .configs(IntegrationTest) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index fdb9704cca..c5ff55be67 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -69,7 +69,11 @@ object Dependencies { val si = Seq() val bi = Seq(commonstext) val br = Seq(scalaparsercombinators, scalaxml) + val ifds = Seq() val tools = Seq(txtmark, jacksonDF) val hermes = Seq(txtmark, jacksonDF, javafxBase) + val javaCppVersion = "1.5.7" + val javaCppPresetLibs = Seq("llvm" -> "13.0.1") + } diff --git a/project/plugins.sbt b/project/plugins.sbt index ef74163f62..867adb1f84 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -20,3 +20,6 @@ addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.5.2") // For the deployment to maven central: addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.7") addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2") + +// llvm +addSbtPlugin("org.bytedeco" % "sbt-javacpp" % "1.17") \ No newline at end of file