Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Call graph cleanup #90

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import org.opalj.br.Method
import org.opalj.br.MethodDescriptor
import org.opalj.br.ObjectType
import org.opalj.br.ReferenceType
import org.opalj.br.analyses.cg.IsOverridableMethodKey
import org.opalj.tac.fpcf.properties.TACAI

trait CGState extends TACAIBasedAnalysisState {
Expand All @@ -45,6 +46,8 @@ trait CGState extends TACAIBasedAnalysisState {
trait AbstractCallGraphAnalysis extends ReachableMethodAnalysis {
type State <: CGState

private[this] val isMethodOverridable: Method ⇒ Answer = project.get(IsOverridableMethodKey)

def createInitialState(definedMethod: DefinedMethod, tacEP: EPS[Method, TACAI]): State

/**
Expand Down Expand Up @@ -79,7 +82,61 @@ trait AbstractCallGraphAnalysis extends ReachableMethodAnalysis {
specializedDeclaringClassType: ReferenceType,
potentialTargets: ForeachRefIterator[ObjectType],
calleesAndCallers: DirectCalls
)(implicit state: State): Unit
)(implicit state: State): Unit = {

for (possibleTgtType ← potentialTargets) {
if (canResolveCall(state)(possibleTgtType)) {
val tgtR = project.instanceCall(
caller.declaringClassType, possibleTgtType, call.name, call.descriptor
)

handleCall(
caller,
call.name,
call.descriptor,
call.declaringClass,
pc,
tgtR,
calleesAndCallers
)
} else {
handleUnresolvedCall(possibleTgtType, call, pc)
}
}

// Deal with the fact that there may be unknown subtypes of the receiver type that might
// override the method
if (specializedDeclaringClassType.isObjectType) {
val declType = specializedDeclaringClassType.asObjectType

val mResult = if (classHierarchy.isInterface(declType).isYes)
org.opalj.Result(project.resolveInterfaceMethodReference(
declType, call.name, call.descriptor
))
else
org.opalj.Result(project.resolveMethodReference(
declType,
call.name,
call.descriptor,
forceLookupInSuperinterfacesOnFailure = true
))

if (mResult.isEmpty) {
unknownLibraryCall(
caller,
call.name,
call.descriptor,
call.declaringClass,
declType,
caller.definedMethod.classFile.thisType.packageName,
pc,
calleesAndCallers
)
} else if (isMethodOverridable(mResult.value).isYesOrUnknown) {
calleesAndCallers.addIncompleteCallSite(pc)
}
}
}

protected final def processMethod(
state: State, calls: DirectCalls
Expand Down Expand Up @@ -351,4 +408,18 @@ trait AbstractCallGraphAnalysis extends ReachableMethodAnalysis {
calleesAndCallers
)
}

/**
* Decides whether this call graph implementation can resolve this call immediately.
*/
@inline protected[this] def canResolveCall(implicit state: State): ObjectType ⇒ Boolean

/**
* Handles a call that is not immediately resolved by this call graph implementation.
*/
@inline protected[this] def handleUnresolvedCall(
possibleTgtType: ObjectType,
call: Call[V] with VirtualCall[V],
pc: Int
)(implicit state: State): Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@ package fpcf
package analyses
package cg

import org.opalj.collection.ForeachRefIterator
import org.opalj.fpcf.EOptionP
import org.opalj.fpcf.EPS
import org.opalj.br.Method
import org.opalj.br.analyses.SomeProject
import org.opalj.br.DefinedMethod
import org.opalj.br.ObjectType
import org.opalj.br.ReferenceType
import org.opalj.br.analyses.cg.IsOverridableMethodKey
import org.opalj.tac.fpcf.properties.TACAI

class CHAState(
Expand All @@ -36,63 +33,26 @@ class CHACallGraphAnalysis private[analyses] (
) extends AbstractCallGraphAnalysis {
override type State = CHAState

private[this] val isMethodOverridable: Method ⇒ Answer = project.get(IsOverridableMethodKey)

override def doHandleImpreciseCall(
caller: DefinedMethod,
call: Call[V] with VirtualCall[V],
pc: Int,
specializedDeclaringClassType: ReferenceType,
potentialTargets: ForeachRefIterator[ObjectType],
calleesAndCallers: DirectCalls
)(implicit state: CHAState): Unit = {
for (tgt ← potentialTargets) {
val tgtR = project.instanceCall(
caller.declaringClassType, tgt, call.name, call.descriptor
)
handleCall(
caller, call.name, call.descriptor, call.declaringClass, pc, tgtR, calleesAndCallers
)
}

// TODO: Document what happens here
if (specializedDeclaringClassType.isObjectType) {
val declType = specializedDeclaringClassType.asObjectType

val mResult = if (classHierarchy.isInterface(declType).isYes)
org.opalj.Result(project.resolveInterfaceMethodReference(
declType, call.name, call.descriptor
))
else
org.opalj.Result(project.resolveMethodReference(
declType,
call.name,
call.descriptor,
forceLookupInSuperinterfacesOnFailure = true
))

if (mResult.isEmpty) {
unknownLibraryCall(
caller,
call.name,
call.descriptor,
call.declaringClass,
declType,
caller.definedMethod.classFile.thisType.packageName,
pc,
calleesAndCallers
)
} else if (isMethodOverridable(mResult.value).isYesOrUnknown) {
calleesAndCallers.addIncompleteCallSite(pc)
}
}
}

override def createInitialState(
definedMethod: DefinedMethod, tacEP: EPS[Method, TACAI]
): CHAState = {
new CHAState(definedMethod, tacEP)
}

@inline override protected[this] def canResolveCall(
implicit
state: CHAState
): ObjectType ⇒ Boolean = {
_ ⇒ true
}

@inline protected[this] def handleUnresolvedCall(
possibleTgtType: ObjectType,
call: Call[V] with VirtualCall[V],
pc: Int
)(implicit state: CHAState): Unit = {
throw new UnsupportedOperationException()
}
}

object CHACallGraphAnalysisScheduler extends CallGraphAnalysisScheduler {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ abstract class AbstractDoPrivilegedPointsToCGAnalysis private[cg] (
) extends TACAIBasedAPIBasedAnalysis with AbstractPointsToBasedAnalysis {

override protected[this] type State = PointsToBasedCGState[PointsToSet]
override protected[this] type DependerType = CallSiteT
override protected[this] type DependerType = CallSite

override def processNewCaller(
caller: DefinedMethod,
Expand All @@ -86,7 +86,7 @@ abstract class AbstractDoPrivilegedPointsToCGAnalysis private[cg] (

val actualParamDefSites = call.params.head.asVar.definedBy

val callSite = (pc, call.name, call.descriptor, call.declaringClass)
val callSite = CallSite(pc, call.name, call.descriptor, call.declaringClass)

val pointsToSets = currentPointsToOfDefSites(callSite, actualParamDefSites)

Expand Down Expand Up @@ -176,7 +176,7 @@ class DoPrivilegedPointsToCGAnalysis private[cg] (
}

override protected[this] type State = PointsToBasedCGState[PointsToSet]
override protected[this] type DependerType = CallSiteT
override protected[this] type DependerType = CallSite

trait PointsToBase extends AbstractPointsToBasedAnalysis {
override protected[this] type ElementType = self.ElementType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ import org.opalj.ai.isImmediateVMException
import org.opalj.br.MethodDescriptor
import org.opalj.br.ReferenceType

case class CallSite(
pc: Int, methodName: String, methodDescriptor: MethodDescriptor, receiver: ReferenceType
)

package object cg {

type V = DUVar[ValueInformation]

type CallSiteT = (Int /*PC*/ , String, MethodDescriptor, ReferenceType)

/**
* A persistent representation (using pcs instead of TAC value origins) for a UVar.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ trait AbstractPointsToBasedCallGraphAnalysis[PointsToSet <: PointsToSetLike[_, _
with AbstractPointsToBasedAnalysis {

override type State = PointsToBasedCGState[PointsToSet]
override type DependerType = CallSiteT
override type DependerType = CallSite

override protected[this] def handlePreciseCall(
calleeType: ObjectType,
Expand Down Expand Up @@ -68,7 +68,7 @@ trait AbstractPointsToBasedCallGraphAnalysis[PointsToSet <: PointsToSetLike[_, _
calleesAndCallers: DirectCalls
)(implicit state: State): Unit = {
val callerType = caller.definedMethod.classFile.thisType
val callSite = (pc, call.name, call.descriptor, call.declaringClass)
val callSite = CallSite(pc, call.name, call.descriptor, call.declaringClass)

// get the upper bound of the pointsToSet and creates a dependency if needed
val currentPointsToSets = currentPointsToOfDefSites(callSite, call.receiver.asVar.definedBy)
Expand Down Expand Up @@ -122,7 +122,7 @@ trait AbstractPointsToBasedCallGraphAnalysis[PointsToSet <: PointsToSetLike[_, _
if (newType.isObjectType) newType.asObjectType else ObjectType.Object
if (typesLeft.contains(theType.id)) {
state.removeTypeForCallSite(callSite, theType)
val (pc, name, descriptor, declaredType) = callSite
val CallSite(pc, name, descriptor, declaredType) = callSite
val tgtR = project.instanceCall(
state.method.declaringClassType,
theType,
Expand Down Expand Up @@ -181,6 +181,21 @@ trait AbstractPointsToBasedCallGraphAnalysis[PointsToSet <: PointsToSetLike[_, _
pointsToUB(p2s)
}
}

@inline override protected[this] def canResolveCall(
implicit
state: State
): ObjectType ⇒ Boolean = {
throw new UnsupportedOperationException()
}

@inline protected[this] def handleUnresolvedCall(
possibleTgtType: ObjectType,
call: Call[V] with VirtualCall[V],
pc: Int
)(implicit state: State): Unit = {
throw new UnsupportedOperationException()
}
}

class TypeBasedPointsToBasedCallGraphAnalysis private[pointsto] (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ class PointsToBasedCGState[PointsToSet <: PointsToSetLike[_, _, PointsToSet]](
// efficiently perform updates here.
// If we get an update for a dependee, we have to update all points-to sets for the
// its dependers (_dependeeToDependers(dependee).
private[this] val _dependeeToDependers: mutable.Map[Entity, mutable.Set[CallSiteT]] = {
private[this] val _dependeeToDependers: mutable.Map[Entity, mutable.Set[CallSite]] = {
mutable.Map.empty
}
private[this] val _dependerToDependees: mutable.Map[CallSiteT, mutable.Set[Entity]] = {
private[this] val _dependerToDependees: mutable.Map[CallSite, mutable.Set[Entity]] = {
mutable.Map.empty
}

final def addPointsToDependency(
depender: CallSiteT,
depender: CallSite,
dependee: EOptionP[Entity, PointsToSet]
): Unit = {
assert(
Expand Down Expand Up @@ -90,7 +90,7 @@ class PointsToBasedCGState[PointsToSet <: PointsToSetLike[_, _, PointsToSet]](
throw new RuntimeException(s"failed to remove dependee: $dependee")
}

final def removePointsToDepender(depender: CallSiteT): Unit = {
final def removePointsToDepender(depender: CallSite): Unit = {
// for every dependee of the given depender:
// we have to remove the depender from the set of their dependers
for (dependee ← _dependerToDependees(depender)) {
Expand All @@ -111,7 +111,7 @@ class PointsToBasedCGState[PointsToSet <: PointsToSetLike[_, _, PointsToSet]](
}
}

final def removeTypeForCallSite(callSite: CallSiteT, instantiatedType: ObjectType): Unit = {
final def removeTypeForCallSite(callSite: CallSite, instantiatedType: ObjectType): Unit = {
if (!_virtualCallSites.contains(callSite))
assert(_virtualCallSites(callSite).contains(instantiatedType.id))
val typesLeft = _virtualCallSites(callSite) - instantiatedType.id
Expand All @@ -128,7 +128,7 @@ class PointsToBasedCGState[PointsToSet <: PointsToSetLike[_, _, PointsToSet]](
_pointsToDependees.contains(dependee)
}

final def hasPointsToDependency(depender: CallSiteT, dependee: Entity): Boolean = {
final def hasPointsToDependency(depender: CallSite, dependee: Entity): Boolean = {
_dependerToDependees.contains(depender) && _dependerToDependees(depender).contains(dependee)
}

Expand Down Expand Up @@ -168,19 +168,19 @@ class PointsToBasedCGState[PointsToSet <: PointsToSetLike[_, _, PointsToSet]](
// IMPROVE: In order to be thread-safe, we return an immutable copy of the set.
// However, this is very inefficient!
// The size of the sets is typically 1 or 2, but there are outliers with up to 100 elements.
final def dependersOf(dependee: Entity): Set[CallSiteT] = {
final def dependersOf(dependee: Entity): Set[CallSite] = {
_dependeeToDependers(dependee).toSet
}

// maps a definition site to the ids of the potential (not yet resolved) objecttypes
private[this] val _virtualCallSites: mutable.Map[CallSiteT, IntTrieSet] = mutable.Map.empty
private[this] val _virtualCallSites: mutable.Map[CallSite, IntTrieSet] = mutable.Map.empty

def typesForCallSite(callSite: CallSiteT): IntTrieSet = {
def typesForCallSite(callSite: CallSite): IntTrieSet = {
_virtualCallSites(callSite)
}

def addPotentialTypesOfCallSite(
callSite: CallSiteT, potentialTypes: IntTrieSet
callSite: CallSite, potentialTypes: IntTrieSet
): Unit = {
_virtualCallSites(callSite) =
_virtualCallSites.getOrElse(callSite, IntTrieSet.empty) ++ potentialTypes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ trait PointsToBasedThreadStartAnalysis
override val apiMethod: DeclaredMethod = threadStartMethod

override type State = PointsToBasedCGState[PointsToSet]
override type DependerType = CallSiteT
override type DependerType = CallSite

override def processNewCaller(
caller: DefinedMethod,
Expand Down Expand Up @@ -110,7 +110,7 @@ trait PointsToBasedThreadStartAnalysis
val seenTypes = if (oldEOptP.hasUBP) oldEOptP.ub.numTypes else 0

for (cs ← relevantCallSites) {
val pc = cs._1
val pc = cs.pc
val receiver = state.tac.stmts(
state.tac.properStmtIndexForPC(pc)
).asInstanceMethodCall.receiver
Expand Down Expand Up @@ -172,7 +172,7 @@ trait PointsToBasedThreadStartAnalysis
indirectCalls
)

val callSite = (
val callSite = CallSite(
pc,
"start",
MethodDescriptor.NoArgsAndReturnVoid,
Expand Down Expand Up @@ -378,7 +378,7 @@ trait PointsToBasedThreadStartAnalysis
}

@inline protected[this] def currentPointsTo(
depender: CallSiteT,
depender: CallSite,
dependee: Entity,
typeFilter: ReferenceType ⇒ Boolean = PointsToSetLike.noFilter
)(implicit state: State): PointsToSet = {
Expand Down
Loading