Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c72fd9d
Integrate string analysis in reflection related calls
maximilianruesch Jun 2, 2024
922ce9b
Fix some code style
maximilianruesch Jun 4, 2024
d4205d5
Update integration into reflection related calls
maximilianruesch Jun 18, 2024
6b43194
Harden integration into for name analysis for invalid string information
maximilianruesch Jun 29, 2024
5fb4572
Fix code style
maximilianruesch Jul 8, 2024
685b310
Improve soundness mode handling in reflection related calls
maximilianruesch Jul 21, 2024
04fe685
Exclude all but constant string trees in low soundness call graph ana…
maximilianruesch Aug 7, 2024
aadb92d
Improve regex matching performance
maximilianruesch Sep 1, 2024
c279d06
Remove pattern compilation checks
maximilianruesch Sep 8, 2024
374da68
Fix formatting
maximilianruesch Sep 8, 2024
cd60c6b
Add string analysis demo
maximilianruesch Sep 8, 2024
306cf35
Fix reflection related field accesses
maximilianruesch Sep 8, 2024
3385a28
Simplify matching types in reflective call accesses
maximilianruesch Sep 9, 2024
df0de05
Fix compilation after merge
maximilianruesch Sep 9, 2024
3983f66
Remove recursion guards for specific functions
maximilianruesch Sep 11, 2024
ee321b1
Add demo parameter for level to run
maximilianruesch Sep 11, 2024
98477cf
Fix hash code definition of PD vars
maximilianruesch Sep 16, 2024
e6d29fd
Fix string analysis demo after merge
maximilianruesch Sep 23, 2024
ae96b1f
Remove inaccessible string analysis demo parts
maximilianruesch Sep 23, 2024
17c8cdc
Use string analysis for more strings in reflect analysis
Aug 29, 2025
f22d87a
Fix compile time exceptions and adapt to new CLI framework
maximilianruesch Sep 15, 2025
ec22731
Merge branch 'develop' into migration/reflective-calls-to-string-anal…
maximilianruesch Sep 17, 2025
245914b
Add string constancy to requirements for reflection-related analyses
maximilianruesch Sep 17, 2025
5bac893
Fix formatting
maximilianruesch Sep 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -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)
)
}
}
17 changes: 12 additions & 5 deletions OPAL/br/src/main/scala/org/opalj/br/PDUVar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand All @@ -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 (
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion OPAL/tac/src/main/scala/org/opalj/tac/DUVar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 _))

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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("<init>"))
private[reflection] val constructorMatcher = new NameBasedMethodMatcher(StringTreeConst("<init>"))

/**
* Given an expression that evaluates to a Class<?> object, creates a MethodMatcher to match
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ package analyses
package cg
package reflection

import java.util.regex.Pattern
import scala.collection.immutable.ArraySeq

import org.opalj.br.ClassHierarchy
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

/**
Expand All @@ -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()
}
}

Expand Down
Loading
Loading