diff --git a/DEVELOPING_OPAL/demos/src/main/resources/opal-xerces-playground.zip b/DEVELOPING_OPAL/demos/src/main/resources/opal-xerces-playground.zip new file mode 100644 index 0000000000..6587372223 Binary files /dev/null and b/DEVELOPING_OPAL/demos/src/main/resources/opal-xerces-playground.zip differ diff --git a/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/fpcf/analyses/StringAnalysisDemo.scala b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/fpcf/analyses/StringAnalysisDemo.scala new file mode 100644 index 0000000000..9d3b717bb4 --- /dev/null +++ b/DEVELOPING_OPAL/demos/src/main/scala/org/opalj/fpcf/analyses/StringAnalysisDemo.scala @@ -0,0 +1,161 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package fpcf +package analyses + +import scala.language.postfixOps + +import java.io.File +import java.net.URL + +import org.opalj.ai.domain.l1.DefaultDomainWithCFGAndDefUse +import org.opalj.ai.fpcf.properties.AIDomainFactoryKey +import org.opalj.br.analyses.BasicReport +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.Project +import org.opalj.br.analyses.ProjectsAnalysisApplication +import org.opalj.br.fpcf.ContextProviderKey +import org.opalj.br.fpcf.analyses.ContextProvider +import org.opalj.br.fpcf.cli.MultiProjectAnalysisConfig +import org.opalj.br.fpcf.cli.StringAnalysisArg +import org.opalj.br.fpcf.properties.Context +import org.opalj.br.fpcf.properties.SimpleContextsKey +import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.cli.AnalysisLevelArg +import org.opalj.fpcf.PropertyStoreBasedCommandLineConfig +import org.opalj.tac.cg.AllocationSiteBasedPointsToCallGraphKey +import org.opalj.tac.fpcf.analyses.cg.reflection.ReflectionRelatedCallsAnalysisScheduler +import org.opalj.tac.fpcf.analyses.systemproperties.TriggeredSystemPropertiesAnalysisScheduler +import org.opalj.util.PerformanceEvaluation.time +import org.opalj.util.Seconds + +/** + * @author Maximilian RĂ¼sch + */ +object StringAnalysisDemo extends ProjectsAnalysisApplication { + + protected class StringAnalysisDemoConfig(args: Array[String]) extends MultiProjectAnalysisConfig(args) + with PropertyStoreBasedCommandLineConfig { + val description: String = + """ + | Analyses the callees of the Main.entrypoint method of the given project, + | e.g. run with the DEVELOPING_OPAL/demos/src/main/resources/opal-xerces-playground.zip package. + | + | Also contains some live logging of runtime information about the property store. + |""".stripMargin + + private val analysisLevelArg = + new AnalysisLevelArg(StringAnalysisArg.description, StringAnalysisArg.levels: _*) { + override val defaultValue: Option[String] = Some("trivial") + override val withNone = false + } + + args( + analysisLevelArg ! + ) + init() + + val analyses: Seq[FPCFAnalysisScheduler[_]] = { + StringAnalysisArg.getAnalyses(apply(analysisLevelArg)).map(getScheduler(_, eager = false)) + } + } + + protected type ConfigType = StringAnalysisDemoConfig + + protected def createConfig(args: Array[String]): StringAnalysisDemoConfig = new StringAnalysisDemoConfig(args) + + override protected def analyze( + cp: Iterable[File], + analysisConfig: StringAnalysisDemoConfig, + execution: Int + ): (Project[URL], BasicReport) = { + val (project, projectTime) = analysisConfig.setupProject() + + val domain = classOf[DefaultDomainWithCFGAndDefUse[_]] + project.updateProjectInformationKeyInitializationData(AIDomainFactoryKey) { + case None => Set(domain) + case Some(requirements) => requirements + domain + } + + val cgKey = AllocationSiteBasedPointsToCallGraphKey + implicit val propertyStore: PropertyStore = project.get(PropertyStoreKey) + var analysisTime: Seconds = Seconds.None + val analysesManager = project.get(FPCFAnalysesManagerKey) + val typeIterator = cgKey.getTypeIterator(project) + project.updateProjectInformationKeyInitializationData(ContextProviderKey) { _ => typeIterator } + + project.get(SimpleContextsKey) + time { + analysesManager + .runAll( + cgKey.allCallGraphAnalyses(project) + ++ analysisConfig.analyses + ++ Seq( + ReflectionRelatedCallsAnalysisScheduler, + TriggeredSystemPropertiesAnalysisScheduler + ) + ) + propertyStore.waitOnPhaseCompletion() + } { t => analysisTime = t.toSeconds } + + val declaredMethods = project.get(DeclaredMethodsKey) + val entrypointMethod = project.allMethodsWithBody.find { m => + m.name == "entrypoint" && + m.classFile.thisType.fqn.endsWith("Main") + }.get + val dm = declaredMethods(entrypointMethod) + + implicit val contextProvider: ContextProvider = project.get(ContextProviderKey) + val calleesUB = propertyStore(dm, Callees.key).ub + val calleesByPC = calleesUB.callerContexts.flatMap(calleesUB.callSites(_).iterator).toSeq + val incompleteCallSites = calleesUB.callerContexts.flatMap(calleesUB.incompleteCallSites(_)).toSeq + + def getDepths(filter: Entity => Boolean): Seq[String] = { + val depths = propertyStore.entities(StringConstancyProperty.key) + .filter(eps => filter(eps.e)) + .map(_.ub.tree.depth).toSeq + + depths + .groupBy(depth => depth) + .transform((_: Int, counts) => counts.size) + .toSeq.sortBy(_._1) + .map(depthAndCount => s"Depth: ${depthAndCount._1} Count: ${depthAndCount._2}") + } + + def getMethodsList(contexts: Iterable[Context]): String = { + if (contexts.size > 50) "\n| Too many contexts to display!" + else contexts.iterator + .map(c => s"- ${c.method.name} on ${c.method.declaringClassType.fqn}") + .mkString("\n| ", "\n| ", "") + } + + def getPCMethodsList(pcContexts: Iterable[(Int, Iterator[Context])]): String = { + if (pcContexts.size > 50) "\n| Too many pc contexts to display!" + else pcContexts.iterator + .map(c => s" PC: ${c._1} ${getMethodsList(c._2.toSeq)}\n") + .mkString("\n| ", "\n| ", "") + } + + ( + project, + BasicReport(s""" + | + | Callees: ${calleesByPC.size} ${getPCMethodsList(calleesByPC)} + | + | Access Sites with missing information: $incompleteCallSites + | + | MethodParameterContext depths: + | ${getDepths(_.getClass.getName.endsWith("MethodParameterContext")).mkString("\n| ", "\n| ", "")} + | + | Context depths: + | ${getDepths(_.getClass.getName.endsWith("VariableContext")).mkString("\n| ", "\n| ", "")} + | + | Definition depths: + | ${getDepths(_.getClass.getName.endsWith("VariableDefinition")).mkString("\n| ", "\n| ", "")} + | + | took : $analysisTime seconds (and $projectTime seconds for project setup) + |""".stripMargin) + ) + } +} diff --git a/OPAL/br/src/main/scala/org/opalj/br/PDUVar.scala b/OPAL/br/src/main/scala/org/opalj/br/PDUVar.scala index b2463e38c5..a925a08abf 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/PDUVar.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/PDUVar.scala @@ -34,15 +34,18 @@ abstract class PDUVar[+Value <: ValueInformation] { } class PDVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ ] private ( - val value: Value, - val usePCs: PCs + val originPC: Int, + val value: Value, + val usePCs: PCs ) extends PDUVar[Value] { def defPCs: Nothing = throw new UnsupportedOperationException + override def hashCode(): Int = scala.util.hashing.MurmurHash3.productHash((originPC, usePCs)) + override def equals(other: Any): Boolean = { other match { - case that: PDVar[_] => this.usePCs == that.usePCs + case that: PDVar[_] => this.originPC == that.originPC && this.usePCs == that.usePCs case _ => false } } @@ -54,9 +57,11 @@ class PDVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ object PDVar { - def apply[Value <: ValueInformation](value: Value, useSites: IntTrieSet): PDVar[Value] = new PDVar(value, useSites) + def apply[Value <: ValueInformation](originSite: Int, value: Value, useSites: IntTrieSet): PDVar[Value] = + new PDVar(originSite, value, useSites) - def unapply[Value <: ValueInformation](d: PDVar[Value]): Some[(Value, IntTrieSet)] = Some((d.value, d.usePCs)) + def unapply[Value <: ValueInformation](d: PDVar[Value]): Some[(Int, Value, IntTrieSet)] = + Some((d.originPC, d.value, d.usePCs)) } class PUVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ ] private ( @@ -66,6 +71,8 @@ class PUVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ def usePCs: Nothing = throw new UnsupportedOperationException + override def hashCode(): Int = defPCs.hashCode() + override def equals(other: Any): Boolean = { other match { case that: PUVar[_] => this.defPCs == that.defPCs diff --git a/OPAL/br/src/main/scala/org/opalj/br/fpcf/cli/StringAnalysisArg.scala b/OPAL/br/src/main/scala/org/opalj/br/fpcf/cli/StringAnalysisArg.scala index a2bfd8cf02..e05c444790 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/fpcf/cli/StringAnalysisArg.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/fpcf/cli/StringAnalysisArg.scala @@ -1,5 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.br.fpcf.cli +package org.opalj +package br +package fpcf +package cli import org.opalj.cli.AnalysisLevelArg diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala b/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala index d6b326188a..6f4644e36d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala @@ -199,7 +199,7 @@ class DVar[+Value <: ValueInformation /*org.opalj.ai.ValuesDomain#DomainValue*/ override def toPersistentForm( implicit stmts: Array[Stmt[V]] - ): PDVar[Value] = PDVar(value, usedBy.map(pcOfDefSite _)) + ): PDVar[Value] = PDVar(pcOfDefSite(origin), value, usedBy.map(pcOfDefSite _)) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/MatcherUtil.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/MatcherUtil.scala index 96ad388139..b9f69d4fe6 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/MatcherUtil.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/MatcherUtil.scala @@ -10,6 +10,8 @@ import org.opalj.br.ClassType import org.opalj.br.FieldTypes import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.properties.Context +import org.opalj.br.fpcf.properties.string.StringTreeConst +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.Entity import org.opalj.fpcf.PropertyStore @@ -86,29 +88,27 @@ object MatcherUtil { * provides allocation sites of Strings to be used as the method name. */ private[reflection] def retrieveNameBasedMethodMatcher( + pc: Int, + value: V, context: Context, - expr: V, depender: Entity, - pc: Int, - stmts: Array[Stmt[V]], - failure: () => Unit + stmts: Array[Stmt[V]] )( implicit - typeIterator: TypeIterator, state: TypeIteratorState, ps: PropertyStore, incompleteCallSites: IncompleteCallSites, highSoundness: Boolean ): MethodMatcher = { - val names = StringUtil.getPossibleStrings(expr, context, depender, stmts, failure) - retrieveSuitableMatcher[Set[String]]( - Some(names), + val names = StringUtil.getPossibleStrings(pc, value, context, depender, stmts) + retrieveSuitableMatcher[StringTreeNode]( + names, pc, v => new NameBasedMethodMatcher(v) ) } - private[reflection] val constructorMatcher = new NameBasedMethodMatcher(Set("")) + private[reflection] val constructorMatcher = new NameBasedMethodMatcher(StringTreeConst("")) /** * Given an expression that evaluates to a Class object, creates a MethodMatcher to match diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/MethodHandlesUtil.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/MethodHandlesUtil.scala index 5b91e54a81..5952bdb50a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/MethodHandlesUtil.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/MethodHandlesUtil.scala @@ -12,6 +12,7 @@ import org.opalj.br.MethodDescriptor import org.opalj.br.ReferenceType import org.opalj.br.VoidType import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.properties.string.StringTreeConst object MethodHandlesUtil { // TODO what about the case of an constructor? @@ -36,7 +37,7 @@ object MethodHandlesUtil { MethodDescriptor(desc.parameterTypes.tail, desc.returnType) ) ), - new NameBasedMethodMatcher(Set(name)), + new NameBasedMethodMatcher(StringTreeConst(name)), if (receiver.isArrayType) new ClassBasedMethodMatcher( Set(ClassType.Object), diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/MethodMatcher.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/MethodMatcher.scala index ce18d995ce..68f2180fe9 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/MethodMatcher.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/MethodMatcher.scala @@ -6,6 +6,7 @@ package analyses package cg package reflection +import java.util.regex.Pattern import scala.collection.immutable.ArraySeq import org.opalj.br.ClassHierarchy @@ -13,8 +14,8 @@ import org.opalj.br.ClassType import org.opalj.br.FieldTypes import org.opalj.br.Method import org.opalj.br.MethodDescriptor -import org.opalj.br.analyses.ProjectIndexKey import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.value.IsReferenceValue /** @@ -30,17 +31,17 @@ trait MethodMatcher { def contains(m: Method)(implicit p: SomeProject): Boolean } -final class NameBasedMethodMatcher(val possibleNames: Set[String]) extends MethodMatcher { +final class NameBasedMethodMatcher(val possibleNames: StringTreeNode) extends MethodMatcher { + val pattern = Pattern.compile(possibleNames.regex) override def initialMethods(implicit p: SomeProject): Iterator[Method] = { - val projectIndex = p.get(ProjectIndexKey) - possibleNames.iterator.flatMap(projectIndex.findMethods) + p.allMethods.filter(m => pattern.matcher(m.name).matches()).iterator } override def priority: Int = 2 override def contains(m: Method)(implicit p: SomeProject): Boolean = { - possibleNames.contains(m.name) + pattern.matcher(m.name).matches() } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/ReflectionRelatedCallsAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/ReflectionRelatedCallsAnalysis.scala index 8d06050a8e..dbf644eb5a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/ReflectionRelatedCallsAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/ReflectionRelatedCallsAnalysis.scala @@ -6,8 +6,6 @@ package analyses package cg package reflection -import scala.language.existentials - import scala.collection.immutable.ArraySeq import org.opalj.br.ArrayType @@ -32,9 +30,12 @@ import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.br.fpcf.properties.cg.ForNameClasses import org.opalj.br.fpcf.properties.cg.LoadedClasses +import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.collection.immutable.IntTrieSet import org.opalj.collection.immutable.UIDSet import org.opalj.fpcf.Entity +import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPK import org.opalj.fpcf.EPS import org.opalj.fpcf.FinalEP @@ -56,6 +57,7 @@ import org.opalj.log.Warn import org.opalj.tac.cg.TypeIteratorKey import org.opalj.tac.fpcf.analyses.cg.reflection.MatcherUtil.retrieveSuitableMatcher import org.opalj.tac.fpcf.analyses.cg.reflection.MethodHandlesUtil.retrieveDescriptorBasedMethodMatcher +import org.opalj.tac.fpcf.analyses.string.VariableContext import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.TheTACAI import org.opalj.value.ASObjectValue @@ -134,8 +136,9 @@ class ClassForNameAnalysis private[analyses] ( final val classNameIndex: Int = 0 ) extends ReflectionAnalysis with TypeConsumerAnalysis { + private final val allowDynamicStringTrees: Boolean = HighSoundnessMode.contains("class") + private class State( - val stmts: Array[Stmt[V]], loadedClassesUB: UIDSet[ClassType], callContext: ContextType, val callPC: Int @@ -196,32 +199,28 @@ class ClassForNameAnalysis private[analyses] ( isDirect: Boolean ): ProperPropertyComputationResult = { implicit val incompleteCallSites: IncompleteCallSites = new IncompleteCallSites {} - implicit val state: State = new State(tac.stmts, loadedClassesUB(), callerContext, callPC) - - val className = if (params.nonEmpty) params(classNameIndex) else None + implicit val state: State = new State(loadedClassesUB(), callerContext, callPC) - if (className.isDefined) { - handleForName(className.get.asVar, callerContext, callPC, tac.stmts) + if (params.nonEmpty && params(classNameIndex).isDefined) { + handleForName(callPC, params(classNameIndex).get.asVar, callerContext, tac.stmts) } else { failure(callPC) } - returnResult(className.map(_.asVar).orNull, incompleteCallSites) + returnResult(incompleteCallSites) } private def returnResult( - className: V, incompleteCallSites: IncompleteCallSites )(implicit state: State): ProperPropertyComputationResult = { - val iresults: IterableOnce[ProperPropertyComputationResult] = - incompleteCallSites.partialResults(state.callContext) + val incompleteResults = incompleteCallSites.partialResults(state.callContext) val forNameClassesResult = if (!state.hasFailed && state.hasOpenDependencies) InterimResult.forUB( (state.callContext, state.callPC), ForNameClasses(state.forNameClasses), state.dependees, - c(className, state) + continuation(state) ) else { if (propertyStore((state.callContext, state.callPC), ForNameClasses.key).isFinal) @@ -229,11 +228,10 @@ class ClassForNameAnalysis private[analyses] ( Result((state.callContext, state.callPC), ForNameClasses(state.forNameClasses)) } val results = if (state.hasNewLoadedClasses) { - val r = - Iterator(forNameClassesResult, state.loadedClassesPartialResult) ++ iresults + val r = Iterator(forNameClassesResult, state.loadedClassesPartialResult) ++ incompleteResults state.reset() r - } else Iterator(forNameClassesResult) ++ iresults + } else Iterator(forNameClassesResult) ++ incompleteResults Results(results) } @@ -241,53 +239,44 @@ class ClassForNameAnalysis private[analyses] ( * Retrieves the current state of loaded classes and instantiated types from the property store. */ private[this] def loadedClassesUB(): UIDSet[ClassType] = { - // the set of classes that are definitely loaded at this point in time - val loadedClassesEOptP = propertyStore(project, LoadedClasses.key) - - // the upper bound for loaded classes, seen so far - val loadedClassesUB: UIDSet[ClassType] = loadedClassesEOptP match { + // the upper bound set of classes that are definitely loaded at this point in time + propertyStore(project, LoadedClasses.key) match { case eps: EPS[_, _] => eps.ub.classes case _ => UIDSet.empty } - - loadedClassesUB } /** - * Adds classes that can be loaded by an invocation of Class.forName to the set of loaded - * classes. + * Adds classes that can be loaded by an invocation of Class.forName to the set of loaded classes. */ private[this] def handleForName( + pc: Int, className: V, callContext: ContextType, - pc: Int, stmts: Array[Stmt[V]] - )(implicit state: State, incompleteCallSites: IncompleteCallSites): Unit = { - val loadedClasses = TypesUtil - .getPossibleForNameClasses(className, callContext, pc.asInstanceOf[Entity], stmts, project, () => failure(pc)) - state.addNewLoadedClasses(loadedClasses) + )(implicit state: State): Unit = { + val possibleClasses = TypesUtil.getPossibleForNameClasses( + pc, + className, + callContext, + pc.asInstanceOf[Entity], + stmts, + project, + allowDynamicStringTrees + ) + state.addNewLoadedClasses(possibleClasses) } - private[this] def c( - className: V, - state: State - )(eps: SomeEPS): ProperPropertyComputationResult = { - - // ensures, that we only add new vm reachable methods + private[this] def continuation(state: State)(eps: SomeEPS): ProperPropertyComputationResult = { + // ensures that we only add new vm reachable methods implicit val incompleteCallSites: IncompleteCallSites = new IncompleteCallSites {} - implicit val _state: State = state - - AllocationsUtil.continuationForAllocation[Int, ContextType]( - eps, - state.callContext, - _ => (className, state.stmts), - _.isInstanceOf[Int], - callPC => failure(callPC) - ) { (callPC, _, allocationIndex, stmts) => - val classOpt = TypesUtil - .getPossibleForNameClass(allocationIndex, stmts, project, () => failure(callPC), onlyClassTypes = false) - if (classOpt.isDefined) state.addNewLoadedClasses(classOpt) - } + + val scpUpdate = eps.asInstanceOf[EOptionP[VariableContext, StringConstancyProperty]] + state.addNewLoadedClasses(TypesUtil.getPossibleForNameClasses( + scpUpdate.ub.tree, + project, + allowDynamicStringTrees + )) if (eps.isFinal) { state.removeDependee(eps.toEPK) @@ -295,12 +284,10 @@ class ClassForNameAnalysis private[analyses] ( state.updateDependency(eps) } - returnResult(className, incompleteCallSites) + returnResult(incompleteCallSites)(state) } - private[this] def failure( - callPC: Int - )(implicit incompleteCallSites: IncompleteCallSites, state: State): Unit = { + private[this] def failure(callPC: Int)(implicit incompleteCallSites: IncompleteCallSites, state: State): Unit = { if (HighSoundnessMode.contains("class")) { state.addNewLoadedClasses(p.allClassFiles.iterator.map(_.thisType)) state.hasFailed = true @@ -771,7 +758,6 @@ class MethodInvokeAnalysis private[analyses] ( Option[(ValueInformation, IntTrieSet)], Seq[Option[(ValueInformation, IntTrieSet)]], Set[MethodMatcher], - V, Array[Stmt[V]], V, ContextType @@ -791,6 +777,8 @@ class MethodInvokeAnalysis private[analyses] ( val epk = eps.toEPK + implicit val highSoundness: Boolean = HighSoundnessMode("method") + if (epk.pk == ForNameClasses.key) { val (callPC, receiver, params, matchers, _, _) = state.dependersOf(epk).head.asInstanceOf[classDependerType] @@ -802,9 +790,22 @@ class MethodInvokeAnalysis private[analyses] ( new ClassBasedMethodMatcher(classes, !matchers.contains(PublicMethodMatcher)) addCalls(state.callContext, callPC, _ => receiver, params, allMatchers) - } else { - implicit val highSoundness: Boolean = HighSoundnessMode("method") + } else if (epk.pk == StringConstancyProperty.key) { + state.dependersOf(epk).foreach { + case depender: nameDependerType @unchecked if depender.isInstanceOf[nameDependerType] => + val (callPC, actualReceiver, actualParams, matchers, _, _, _) = depender + val nameMatcher = retrieveSuitableMatcher[StringTreeNode]( + Some(eps.ub.asInstanceOf[StringConstancyProperty].tree), + callPC, + v => new NameBasedMethodMatcher(v) + ) + if (nameMatcher ne NoMethodsMatcher) { + val allMatchers = matchers + getClassMatcher(depender, matchers + nameMatcher) + addCalls(state.callContext, callPC, _ => actualReceiver, actualParams, allMatchers) + } + } + } else { AllocationsUtil.continuationForAllocation[methodDependerType, ContextType]( eps, state.callContext, @@ -817,30 +818,6 @@ class MethodInvokeAnalysis private[analyses] ( addCalls(state.callContext, data._1, _ => data._2, data._3, allMatchers) } - AllocationsUtil.continuationForAllocation[nameDependerType, ContextType]( - eps, - state.callContext, - data => (data._5, data._6), - _.isInstanceOf[(_, _, _, _, _, _, _, _)], - data => { - val allMatchers = data._4 + getClassMatcher(data, data._4) - failure("method", data._1, data._2, data._3, allMatchers) - } - ) { (data, _, allocationIndex, stmts) => - val name = StringUtil.getString(allocationIndex, stmts) - - val nameMatcher = retrieveSuitableMatcher[Set[String]]( - name.map(Set(_)), - data._1, - v => new NameBasedMethodMatcher(v) - ) - - if (nameMatcher ne NoMethodsMatcher) { - val allMatchers = data._4 + getClassMatcher(data, data._4 + nameMatcher) - addCalls(state.callContext, data._1, _ => data._2, data._3, allMatchers) - } - } - AllocationsUtil.continuationForAllocation[classDependerType, ContextType]( eps, state.callContext, @@ -884,11 +861,11 @@ class MethodInvokeAnalysis private[analyses] ( )(implicit state: TACAIBasedCGState[ContextType], indirectCalls: IndirectCalls): MethodMatcher = { implicit val highSoundness: Boolean = HighSoundnessMode("class") MatcherUtil.retrieveClassBasedMethodMatcher( - data._8, data._7, - (data._1, data._2, data._3, matchers, data._7, data._6), - data._1, data._6, + (data._1, data._2, data._3, matchers, data._6, data._5), + data._1, + data._5, project, () => failure("class", data._1, data._2, data._3, matchers), onlyMethodsExactlyInClass = !matchers.contains(PublicMethodMatcher) @@ -983,27 +960,20 @@ class MethodInvokeAnalysis private[analyses] ( if (isGetMethod) matchers += PublicMethodMatcher - var failed: String = null - - val depender = - (callPC, actualReceiver, actualParams, matchers, params.head.asVar, stmts, receiver.asVar, context) + val depender = (callPC, actualReceiver, actualParams, matchers, stmts, receiver.asVar, context) if (!matchers.contains(NoMethodsMatcher)) matchers += MatcherUtil.retrieveNameBasedMethodMatcher( - context, + callPC, params.head.asVar, + context, depender, - callPC, - stmts, - () => failed = "method" + stmts ) if (!matchers.contains(NoMethodsMatcher)) matchers += getClassMatcher(depender, matchers) - if (failed ne null) - failure("method", callPC, actualReceiver, actualParams, matchers) - /*case ArrayLoad(_, _, arrayRef) =>*/ // TODO here we can handle getMethods @@ -1108,7 +1078,6 @@ class MethodHandleInvokeAnalysis private[analyses] ( Boolean, Seq[Option[(ValueInformation, IntTrieSet)]], Set[MethodMatcher], - V, Array[Stmt[V]], V, ContextType @@ -1122,6 +1091,8 @@ class MethodHandleInvokeAnalysis private[analyses] ( val epk = eps.toEPK + implicit val highSoundness: Boolean = HighSoundnessMode("method") + if (epk.pk == ForNameClasses.key) { val (callPC, isVirtual, params, matchers, _, _) = state.dependersOf(epk).head.asInstanceOf[classDependerType] @@ -1134,9 +1105,24 @@ class MethodHandleInvokeAnalysis private[analyses] ( val allMatchers = matchers + new ClassBasedMethodMatcher(classes, false) addCalls(state.callContext, callPC, allMatchers, params) - } else { - implicit val highSoundness: Boolean = HighSoundnessMode("method") + } else if (epk.pk == StringConstancyProperty.key) { + state.dependersOf(epk).foreach { + case depender: nameDependerType @unchecked if depender.isInstanceOf[nameDependerType] => + val (callPC, _, actualParams, previousMatchers, _, _, _) = depender + val nameMatcher = retrieveSuitableMatcher[StringTreeNode]( + Some(eps.ub.asInstanceOf[StringConstancyProperty].tree), + callPC, + v => new NameBasedMethodMatcher(v) + ) + + if (nameMatcher ne NoMethodsMatcher) { + val matchers = previousMatchers + nameMatcher + val allMatchers = matchers + getClassMatcher(depender, matchers) + addCalls(state.callContext, callPC, allMatchers, actualParams) + } + } + } else { AllocationsUtil.continuationForAllocation[methodHandleDependerType, ContextType]( eps, state.callContext, @@ -1157,31 +1143,6 @@ class MethodHandleInvokeAnalysis private[analyses] ( addCalls(state.callContext, data._1, allMatchers, data._4) } - AllocationsUtil.continuationForAllocation[nameDependerType, ContextType]( - eps, - state.callContext, - data => (data._5, data._6), - _.isInstanceOf[(_, _, _, _, _, _, _, _)], - data => { - val allMatchers = data._4 + getClassMatcher(data, data._4) - failure("method", data._1, data._3, allMatchers) - } - ) { (data, _, allocationIndex, stmts) => - val name = StringUtil.getString(allocationIndex, stmts) - - val nameMatcher = retrieveSuitableMatcher[Set[String]]( - name.map(Set(_)), - data._1, - v => new NameBasedMethodMatcher(v) - ) - - if (nameMatcher ne NoMethodsMatcher) { - val matchers = data._4 + nameMatcher - val allMatchers = matchers + getClassMatcher(data, matchers) - addCalls(state.callContext, data._1, allMatchers, data._3) - } - } - AllocationsUtil.continuationForAllocation[classDependerType, ContextType]( eps, state.callContext, @@ -1222,11 +1183,11 @@ class MethodHandleInvokeAnalysis private[analyses] ( )(implicit state: TACAIBasedCGState[ContextType], indirectCalls: IndirectCalls): MethodMatcher = { implicit val highSoundness: Boolean = HighSoundnessMode("class") MatcherUtil.retrieveClassBasedMethodMatcher( - data._8, data._7, - (data._1, data._2, data._3, matchers, data._7, data._6), - data._1, data._6, + (data._1, data._2, data._3, matchers, data._6, data._5), + data._1, + data._5, project, () => failure("class", data._1, data._3, matchers), onlyMethodsExactlyInClass = false, @@ -1430,12 +1391,11 @@ class MethodHandleInvokeAnalysis private[analyses] ( matchers += (if (isConstructor) MatcherUtil.constructorMatcher else MatcherUtil.retrieveNameBasedMethodMatcher( - context, - name, - (callPC, isVirtual, persistentActualParams, matchers, name, stmts, refc, context), callPC, - stmts, - () => failure("method", callPC, persistentActualParams, matchers) + name, + context, + (callPC, isVirtual, persistentActualParams, matchers, stmts, refc, context), + stmts )) } if (!matchers.contains(NoMethodsMatcher)) @@ -1466,7 +1426,7 @@ class MethodHandleInvokeAnalysis private[analyses] ( onlyMethodsExactlyInClass = false ) else { - val data = (callPC, isVirtual, persistentActualParams, matchers, name, stmts, refc, context) + val data = (callPC, isVirtual, persistentActualParams, matchers, stmts, refc, context) matchers += getClassMatcher(data, matchers) } } @@ -1663,7 +1623,8 @@ object ReflectionRelatedCallsAnalysisScheduler extends BasicFPCFEagerAnalysisSch Callees, LoadedClasses, ForNameClasses, - TACAI + TACAI, + StringConstancyProperty ) override def uses(p: SomeProject, ps: PropertyStore): Set[PropertyBounds] = { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/StringUtil.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/StringUtil.scala index a670036e5d..938e7e1f82 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/StringUtil.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/StringUtil.scala @@ -6,13 +6,43 @@ package analyses package cg package reflection -import org.opalj.br.ClassType import org.opalj.br.fpcf.properties.Context +import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.Entity import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.UBP +import org.opalj.tac.fpcf.analyses.string.VariableContext object StringUtil { + /** + * Returns a regex that models all strings the given value might evaluate to + * Clients MUST handle dependencies where the depender is the given one and the dependee provides string constancy + * information. + */ + def getPossibleStrings( + pc: Int, + value: V, + context: Context, + depender: Entity, + stmts: Array[Stmt[V]] + )( + implicit + ps: PropertyStore, + state: TypeIteratorState + ): Option[StringTreeNode] = { + val stringEOptP = ps(VariableContext(pc, value.toPersistentForm(stmts), context), StringConstancyProperty.key) + if (stringEOptP.isRefinable) { + state.addDependency(depender, stringEOptP) + } + + stringEOptP match { + case UBP(ub) => Some(ub.tree) + case _ => None + } + } + /** * Returns Strings that a given expression may evaluate to. * Identifies local use of String constants. @@ -34,48 +64,8 @@ object StringUtil { }.flatten) } - /** - * Returns Strings that a given expression may evaluate to. - * Identifies String constants. - * Clients MUST handle dependencies where the depender is the given one and the dependee - * provides allocation sites of Strings. - */ - def getPossibleStrings[ContextType <: Context]( - value: V, - context: ContextType, - depender: Entity, - stmts: Array[Stmt[V]], - failure: () => Unit - )( - implicit - typeIterator: TypeIterator, - state: TypeIteratorState, - ps: PropertyStore - ): Set[String] = { - var strings = Set.empty[String] - - AllocationsUtil.handleAllocations( - value, - context, - depender, - stmts, - _ eq ClassType.String, - failure - ) { (_, defSite, _stmts) => - getString(defSite, _stmts) match { - case Some(v) => - strings += v - case _ => - failure() - } - } - - strings - } - - def getString(stringDefSite: Int, stmts: Array[Stmt[V]]): Option[String] = { - val expr = stmts(stringDefSite).asAssignment.expr - expr match { + private def getString(stringDefSite: Int, stmts: Array[Stmt[V]]): Option[String] = { + stmts(stringDefSite).asAssignment.expr match { case StringConst(_, v) => Some(v) case _ => None } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/TypesUtil.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/TypesUtil.scala index 3239bdde98..87fc5f4310 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/TypesUtil.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/reflection/TypesUtil.scala @@ -6,7 +6,8 @@ package analyses package cg package reflection -import org.opalj.br.ArrayType +import java.util.regex.Pattern + import org.opalj.br.BaseType import org.opalj.br.ClassType import org.opalj.br.MethodDescriptor @@ -16,6 +17,8 @@ import org.opalj.br.VoidType import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.ForNameClasses +import org.opalj.br.fpcf.properties.string.StringConstancyLevel +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.collection.immutable.UIDSet import org.opalj.fpcf.Entity import org.opalj.fpcf.EPS @@ -44,51 +47,45 @@ object TypesUtil { /** * Returns classes that may be loaded by an invocation of Class.forName. * Clients MUST handle dependencies where the depender is the given one and the dependee - * provides allocation sites of Strings that give class names of such classes. + * provides a string regex that matches the FQN class names of the loaded classes. */ def getPossibleForNameClasses( - className: V, - context: Context, - depender: Entity, - stmts: Array[Stmt[V]], - project: SomeProject, - failure: () => Unit + pc: Int, + className: V, + context: Context, + depender: Entity, + stmts: Array[Stmt[V]], + project: SomeProject, + allowDynamic: Boolean )( implicit - typeIterator: TypeIterator, - state: TypeIteratorState, - ps: PropertyStore - ): Set[ReferenceType] = { - StringUtil.getPossibleStrings(className, context, depender, stmts, failure).flatMap { cls => - referenceTypeFromFQN(cls) - }.filter { - case at: ArrayType => - val et = at.elementType - !et.isClassType || project.classFile(et.asClassType).isDefined - case ct: ClassType => - project.classFile(ct).isDefined - } + ps: PropertyStore, + state: TypeIteratorState + ): Set[ClassType] = { + val stringTree = StringUtil.getPossibleStrings(pc, className, context, depender, stmts) + stringTree.map(getPossibleForNameClasses(_, project, allowDynamic)).getOrElse(Set.empty) } /** * Returns class that may be loaded by an invocation of Class.forName with the given String. */ - def getPossibleForNameClass( - classNameDefSite: Int, - stmts: Array[Stmt[V]], - project: SomeProject, - failure: () => Unit, - onlyClassTypes: Boolean - ): Option[ClassType] = { - val className = StringUtil.getString(classNameDefSite, stmts).flatMap { cls => - val tpe = referenceTypeFromFQN(cls) - if (tpe.isDefined && tpe.get.isArrayType) - if (onlyClassTypes) None - else Some(ClassType.Object) - else tpe.asInstanceOf[Option[ClassType]] + def getPossibleForNameClasses( + classNameStringTree: StringTreeNode, + project: SomeProject, + allowDynamic: Boolean + ): Set[ClassType] = { + if (classNameStringTree.isInvalid || ( + classNameStringTree.constancyLevel != StringConstancyLevel.Constant + && !allowDynamic + ) + ) { + Set.empty + } else { + val pattern = Pattern.compile(classNameStringTree.regex) + project.allClassFiles + .filter(cf => pattern.matcher(cf.thisType.toBinaryJavaName).matches()) + .map(_.thisType).toSet } - if (className.isEmpty) failure() - className.filter(project.classFile(_).isDefined) } @inline private[this] def referenceTypeFromFQN(fqn: String): Option[ReferenceType] = { @@ -308,7 +305,7 @@ object TypesUtil { * Retrieves the possible runtime types of a local variable if they are known precisely. * Otherwise, an empty Iterator is returned. */ - def getTypesOfVar( + private[reflection] def getTypesOfVar( uvar: V ): Option[Iterator[ReferenceType]] = { val value = uvar.value.asReferenceValue @@ -319,5 +316,4 @@ object TypesUtil { None } } - } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/FieldMatcher.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/FieldMatcher.scala index c641191f9a..802faa8097 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/FieldMatcher.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/FieldMatcher.scala @@ -6,13 +6,14 @@ package analyses package fieldaccess package reflection +import java.util.regex.Pattern import scala.collection.immutable.ArraySeq import org.opalj.br.ClassType import org.opalj.br.Field import org.opalj.br.FieldType -import org.opalj.br.analyses.ProjectIndexKey import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.value.IsReferenceValue /** @@ -29,13 +30,18 @@ trait FieldMatcher { } -final class NameBasedFieldMatcher(val possibleNames: Set[String]) extends FieldMatcher { +final class NameBasedFieldMatcher(val possibleNames: StringTreeNode) extends FieldMatcher { + val pattern = Pattern.compile(possibleNames.regex) + override def initialFields(implicit p: SomeProject): Iterator[Field] = { - val projectIndex = p.get(ProjectIndexKey) - possibleNames.iterator.flatMap(projectIndex.findFields) + p.allFields.filter(f => pattern.matcher(f.name).matches()).iterator } + override def priority: Int = 2 - override def contains(f: Field)(implicit p: SomeProject): Boolean = possibleNames.contains(f.name) + + override def contains(f: Field)(implicit p: SomeProject): Boolean = { + pattern.matcher(f.name).matches() + } } class ClassBasedFieldMatcher( diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/MatcherUtil.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/MatcherUtil.scala index 3a915836d4..dd95b92dc3 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/MatcherUtil.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/MatcherUtil.scala @@ -10,6 +10,7 @@ import org.opalj.br.ClassType import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.fieldaccess.IncompleteFieldAccesses +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.Entity import org.opalj.fpcf.PropertyStore import org.opalj.tac.fpcf.analyses.cg.TypeIterator @@ -62,23 +63,21 @@ object MatcherUtil { * provides allocation sites of Strings to be used as the method name. */ private[reflection] def retrieveNameBasedFieldMatcher( + pc: Int, + value: V, context: Context, - expr: V, depender: Entity, - pc: Int, - stmts: Array[Stmt[V]], - failure: () => Unit + stmts: Array[Stmt[V]] )( implicit - typeIterator: TypeIterator, state: TypeIteratorState, ps: PropertyStore, highSoundness: Boolean, incompleteFieldAccesses: IncompleteFieldAccesses ): FieldMatcher = { - val names = StringUtil.getPossibleStrings(expr, context, depender, stmts, failure) - retrieveSuitableMatcher[Set[String]]( - Some(names), + val names = StringUtil.getPossibleStrings(pc, value, context, depender, stmts) + retrieveSuitableMatcher[StringTreeNode]( + names, pc, v => new NameBasedFieldMatcher(v) ) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/MethodHandlesUtil.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/MethodHandlesUtil.scala index cf236d0c46..b21dc28a37 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/MethodHandlesUtil.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/MethodHandlesUtil.scala @@ -10,6 +10,7 @@ import org.opalj.br.ClassType import org.opalj.br.FieldType import org.opalj.br.ReferenceType import org.opalj.br.analyses.SomeProject +import org.opalj.br.fpcf.properties.string.StringTreeConst object MethodHandlesUtil { @@ -21,7 +22,7 @@ object MethodHandlesUtil { )(implicit project: SomeProject): Set[FieldMatcher] = { Set( new LBTypeBasedFieldMatcher(fieldType), - new NameBasedFieldMatcher(Set(name)), + new NameBasedFieldMatcher(StringTreeConst(name)), if (isStatic) StaticFieldMatcher else NonStaticFieldMatcher, if (declaringClass.isArrayType) new ClassBasedFieldMatcher(Set(ClassType.Object), onlyFieldsExactlyInClass = false) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/ReflectionRelatedFieldAccessesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/ReflectionRelatedFieldAccessesAnalysis.scala index 0f36bbee61..90ae9f1ef8 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/ReflectionRelatedFieldAccessesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/fieldaccess/reflection/ReflectionRelatedFieldAccessesAnalysis.scala @@ -46,6 +46,8 @@ import org.opalj.br.fpcf.properties.fieldaccess.FieldWriteAccessInformation import org.opalj.br.fpcf.properties.fieldaccess.IndirectFieldAccesses import org.opalj.br.fpcf.properties.fieldaccess.MethodFieldReadAccessInformation import org.opalj.br.fpcf.properties.fieldaccess.MethodFieldWriteAccessInformation +import org.opalj.br.fpcf.properties.string.StringConstancyProperty +import org.opalj.br.fpcf.properties.string.StringTreeNode import org.opalj.fpcf.EOptionP import org.opalj.fpcf.EPS import org.opalj.fpcf.FinalEP @@ -66,7 +68,6 @@ import org.opalj.tac.fpcf.analyses.cg.BaseAnalysisState import org.opalj.tac.fpcf.analyses.cg.TypeConsumerAnalysis import org.opalj.tac.fpcf.analyses.cg.TypeIteratorState import org.opalj.tac.fpcf.analyses.cg.persistentUVar -import org.opalj.tac.fpcf.analyses.cg.reflection.StringUtil import org.opalj.tac.fpcf.analyses.cg.reflection.TypesUtil import org.opalj.tac.fpcf.analyses.cg.reflection.VarargsUtil import org.opalj.tac.fpcf.analyses.fieldaccess.reflection.MatcherUtil.retrieveSuitableMatcher @@ -163,7 +164,6 @@ sealed trait FieldInstanceBasedReflectiveFieldAccessAnalysis extends ReflectionA receiver: AccessReceiver, param: Option[AccessParameter], matchers: Set[FieldMatcher], - nameVar: V, callerStatements: Array[Stmt[V]], classVar: V, callerContext: ContextType @@ -259,6 +259,39 @@ sealed trait FieldInstanceBasedReflectiveFieldAccessAnalysis extends ReflectionA new ClassBasedFieldMatcher(classes, !depender.matchers.contains(PublicFieldMatcher)) addFieldAccess(state.callContext, depender.pc, _ => depender.receiver, _ => depender.param, allMatchers) + } else if (epk.pk == StringConstancyProperty.key) { + state.dependersOf(epk).foreach { + case data: NameDepender => + val nameMatcher = retrieveSuitableMatcher[StringTreeNode]( + Some(eps.ub.asInstanceOf[StringConstancyProperty].tree), + data.pc, + v => new NameBasedFieldMatcher(v) + ) + + if (nameMatcher ne NoFieldsMatcher) { + val matchers = data.matchers + nameMatcher + val allMatchers = matchers + + MatcherUtil.retrieveClassBasedFieldMatcher( + data.callerContext, + data.classVar, + ClassDepender( + data.pc, + data.receiver, + data.param, + matchers, + data.classVar, + data.callerStatements + ), + data.pc, + data.callerStatements, + project, + () => failure(data.pc, data.receiver, data.param, matchers), + onlyFieldsExactlyInClass = !data.matchers.contains(PublicFieldMatcher) + ) + + addFieldAccess(state.callContext, data.pc, _ => data.receiver, _ => data.param, allMatchers) + } + } } else { AllocationsUtil.continuationForAllocation[FieldDepender, ContextType]( eps, @@ -279,46 +312,6 @@ sealed trait FieldInstanceBasedReflectiveFieldAccessAnalysis extends ReflectionA addFieldAccess(state.callContext, data.pc, _ => data.receiver, _ => data.param, allMatchers) } - AllocationsUtil.continuationForAllocation[NameDepender, ContextType]( - eps, - state.callContext, - data => (data.nameVar, data.callerStatements), - _.isInstanceOf[NameDepender], - data => failure(data.pc, data.receiver, data.param, data.matchers) - ) { (data, _, allocationIndex, stmts) => - val name = StringUtil.getString(allocationIndex, stmts) - - val nameMatcher = retrieveSuitableMatcher[Set[String]]( - name.map(Set(_)), - data.pc, - v => new NameBasedFieldMatcher(v) - ) - - if (nameMatcher ne NoFieldsMatcher) { - val matchers = data.matchers + nameMatcher - val allMatchers = matchers + - MatcherUtil.retrieveClassBasedFieldMatcher( - data.callerContext, - data.classVar, - ClassDepender( - data.pc, - data.receiver, - data.param, - matchers, - data.classVar, - data.callerStatements - ), - data.pc, - stmts, - project, - () => failure(data.pc, data.receiver, data.param, matchers), - onlyFieldsExactlyInClass = !data.matchers.contains(PublicFieldMatcher) - ) - - addFieldAccess(state.callContext, data.pc, _ => data.receiver, _ => data.param, allMatchers) - } - } - AllocationsUtil.continuationForAllocation[ClassDepender, ContextType]( eps, state.callContext, @@ -379,21 +372,19 @@ sealed trait FieldInstanceBasedReflectiveFieldAccessAnalysis extends ReflectionA if (!matchers.contains(NoFieldsMatcher)) matchers += MatcherUtil.retrieveNameBasedFieldMatcher( - context, + accessPC, params.head.asVar, + context, NameDepender( accessPC, actualReceiver, actualParameter, matchers, - params.head.asVar, stmts, receiver.asVar, context ), - accessPC, - stmts, - () => failure(accessPC, actualReceiver, actualParameter, matchers) + stmts ) if (!matchers.contains(NoFieldsMatcher)) { @@ -751,7 +742,6 @@ class MethodHandleInvokeAnalysis private[analyses] ( isStatic: Boolean, persistentActualParams: Seq[Option[(ValueInformation, PCs)]], matchers: Set[FieldMatcher], - nameVar: V, stmts: Array[Stmt[V]], classVar: V, accessContext: ContextType @@ -784,6 +774,40 @@ class MethodHandleInvokeAnalysis private[analyses] ( new ClassBasedFieldMatcher(classes, onlyFieldsExactlyInClass = false) addFieldAccesses(state.callContext, depender.pc, allMatchers, depender.persistentActualParams) + } else if (epk.pk == StringConstancyProperty.key) { + state.dependersOf(epk).foreach { + case data: NameDepender => + val nameMatcher = retrieveSuitableMatcher[StringTreeNode]( + Some(eps.ub.asInstanceOf[StringConstancyProperty].tree), + data.pc, + v => new NameBasedFieldMatcher(v) + ) + + if (nameMatcher ne NoFieldsMatcher) { + val matchers = data.matchers + nameMatcher + val allMatchers = matchers + + MatcherUtil.retrieveClassBasedFieldMatcher( + data.accessContext, + data.classVar, + ClassDepender( + data.pc, + data.isStatic, + data.persistentActualParams, + matchers, + data.classVar, + data.stmts + ), + data.pc, + data.stmts, + project, + () => failure(data.pc, data.persistentActualParams, matchers), + onlyFieldsExactlyInClass = false, + considerSubclasses = !data.isStatic + ) + + addFieldAccesses(state.callContext, data.pc, allMatchers, data.persistentActualParams) + } + } } else { AllocationsUtil.continuationForAllocation[MethodHandleDepender, ContextType]( eps, @@ -805,47 +829,6 @@ class MethodHandleInvokeAnalysis private[analyses] ( addFieldAccesses(state.callContext, data.pc, allMatchers, data.persistentActualParams) } - AllocationsUtil.continuationForAllocation[NameDepender, ContextType]( - eps, - state.callContext, - data => (data.nameVar, data.stmts), - _.isInstanceOf[NameDepender], - data => failure(data.pc, data.persistentActualParams, data.matchers) - ) { (data, _, allocationIndex, stmts) => - val name = StringUtil.getString(allocationIndex, stmts) - - val nameMatcher = retrieveSuitableMatcher[Set[String]]( - name.map(Set(_)), - data.pc, - v => new NameBasedFieldMatcher(v) - ) - - if (nameMatcher ne NoFieldsMatcher) { - val matchers = data.matchers + nameMatcher - val allMatchers = matchers + - MatcherUtil.retrieveClassBasedFieldMatcher( - data.accessContext, - data.classVar, - ClassDepender( - data.pc, - data.isStatic, - data.persistentActualParams, - matchers, - data.classVar, - data.stmts - ), - data.pc, - stmts, - project, - () => failure(data.pc, data.persistentActualParams, matchers), - onlyFieldsExactlyInClass = false, - considerSubclasses = !data.isStatic - ) - - addFieldAccesses(state.callContext, data.pc, allMatchers, data.persistentActualParams) - } - } - AllocationsUtil.continuationForAllocation[ClassDepender, ContextType]( eps, state.callContext, @@ -1041,29 +1024,27 @@ class MethodHandleInvokeAnalysis private[analyses] ( if (!matchers.contains(NoFieldsMatcher)) matchers += MatcherUtil.retrieveNameBasedFieldMatcher( - context, + accessPC, handleData.nameVar, + context, NameDepender( accessPC, handleData.isStatic, persistentActualParams, matchers, - handleData.nameVar, stmts, handleData.classVar, context ), - accessPC, - stmts, - () => failure(accessPC, persistentActualParams, matchers) + stmts ) if (!matchers.contains(NoFieldsMatcher)) if (!handleData.isStatic) { - if (actualParams.isDefined && actualParams.get.nonEmpty && actualParams.get.head.isDefined) { - val receiverValue = actualParams.get.head.get.value - if (receiverValue.isReferenceValue) - matchers += new ActualReceiverBasedFieldMatcher(receiverValue.asReferenceValue) + val receiverValueOpt = actualParams.flatMap(_.headOption).flatten.map(_.value) + if (receiverValueOpt.isDefined) { + if (receiverValueOpt.get.isReferenceValue) + matchers += new ActualReceiverBasedFieldMatcher(receiverValueOpt.get.asReferenceValue) } else matchers += MatcherUtil.retrieveClassBasedFieldMatcher( context, @@ -1207,6 +1188,7 @@ object ReflectionRelatedFieldAccessesAnalysisScheduler extends BasicFPCFEagerAna ForNameClasses, Callers, TACAI, + StringConstancyProperty, FieldReadAccessInformation, FieldWriteAccessInformation, MethodFieldReadAccessInformation, diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala index 4ec2b7356e..587c96303f 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/StringAnalysis.scala @@ -163,7 +163,7 @@ private[string] class ContextFreeStringAnalysis(override val project: SomeProjec case PUVar(_, defPCs) => defPCs.map(pc => mapDefPCToStringTree(pc)) - case PDVar(_, _) => + case PDVar(_, _, _) => Set(mapDefPCToStringTree(state.entity.pc)) } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala index 4d25ce7a60..0af4ca2323 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/l1/interpretation/L1StaticFunctionCallInterpreter.scala @@ -43,8 +43,8 @@ case class L1StaticFunctionCallInterpreter()( state: InterpretationState ): ProperPropertyComputationResult = { call.name match { - case "getProperty" if call.declaringClass eq ClassType.System => interpretGetSystemPropertiesCall(target) - case "valueOf" if call.declaringClass eq ClassType.String => processStringValueOf(target, call) + case "getProperty" if call.declaringClass == ClassType.System => interpretGetSystemPropertiesCall(target) + case "valueOf" if call.declaringClass == ClassType.String => processStringValueOf(target, call) case _ if (call.descriptor.returnType eq ClassType.String) || (call.descriptor.returnType eq ClassType.Object) => interpretArbitraryCall(target, call) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala index e51eced3ba..6282d20015 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/string/trivial/TrivialStringAnalysis.scala @@ -96,7 +96,7 @@ class TrivialStringAnalysis(override val project: SomeProject) extends FPCFAnaly case PUVar(_, defPCs) => StringTreeOr(defPCs.map(pc => mapDefPCToStringTree(pc))) - case PDVar(_, _) => + case PDVar(_, _, _) => mapDefPCToStringTree(state.entity.pc) }