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 by signature resolution #93

Open
wants to merge 3 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
3 changes: 3 additions & 0 deletions OPAL/br/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ org.opalj {

analyses {
cg {

callBySignatureResolution = false

ClosedPackagesKey {
analysis = "org.opalj.br.analyses.cg.AllPackagesClosed" # considers all packages closed (e.g. suitable when analyzing an application)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.br.analyses.cg

import org.opalj.br.analyses.ProjectIndexKey
import org.opalj.br.analyses.ProjectInformationKey
import org.opalj.br.analyses.ProjectInformationKeys
import org.opalj.br.analyses.SomeProject
import org.opalj.br.Method
import org.opalj.br.ObjectType
import org.opalj.collection.immutable.RefArray

import scala.collection.mutable.ListBuffer
import scala.collection.mutable.OpenHashMap

/**
* The ''key'' object to get the interface methods for which call-by-signature resolution
* needs to be done.
*
* @note To get call-by-signature information use the [[org.opalj.br.analyses.Project]]'s `get`
* method and pass in `this` object.
*
* @see [[CallBySignatureResolution]] for further information.
*
* @author Michael Reif
*/
object CallBySignatureKey extends ProjectInformationKey[CallBySignatureTargets, Nothing] {

override def requirements(project: SomeProject): ProjectInformationKeys = List(
ProjectIndexKey,
ClosedPackagesKey,
ClassExtensibilityKey,
TypeExtensibilityKey,
IsOverridableMethodKey
)

override def compute(p: SomeProject): CallBySignatureTargets = {
val cbsTargets = new OpenHashMap[Method, RefArray[ObjectType]]
val index = p.get(ProjectIndexKey)
val isOverridableMethod = p.get(IsOverridableMethodKey)

for {
classFile ← p.allClassFiles if classFile.isInterfaceDeclaration
method ← classFile.methods
if !method.isPrivate &&
!method.isStatic &&
(classFile.isPublic || isOverridableMethod(method).isYesOrUnknown)
} {
val descriptor = method.descriptor
val methodName = method.name
val declType = classFile.thisType

import p.classHierarchy
val potentialMethods = index.findMethods(methodName, descriptor)

var i = 0
val targets = ListBuffer.empty[ObjectType]
while (i < potentialMethods.size) {
val m = potentialMethods(i)
val cf = m.classFile
val targetType = cf.thisType
val qualified = cf.isClassDeclaration &&
isOverridableMethod(m).isYesOrUnknown &&
classHierarchy.isASubtypeOf(targetType, declType).isNoOrUnknown

if (qualified) {
targets += m.declaringClassFile.thisType
}
i = i + 1
}

cbsTargets.put(method, RefArray.empty ++ targets)
}

new CallBySignatureTargets(cbsTargets)
}
}

class CallBySignatureTargets private[analyses] (
val data: scala.collection.Map[Method, RefArray[ObjectType]]
) {
/**
* Returns all call-by-signature targets of the given method. If the method is not known,
* `null` is returned. If the method is known a non-null (but potentially empty)
* [[org.opalj.collection.immutable.RefArray]] is returned.
*/
def apply(m: Method): RefArray[ObjectType] = data.getOrElse(m, RefArray.empty)
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ trait LibraryEntryPointsFinder extends EntryPointFinder {
// but it soundly overapproximates
subtypeCFOption.forall(_.constructors.exists { c ⇒
c.isPublic || (c.isProtected && isExtensible(st).isYesOrUnknown)
}))
}) || classFile.methods.exists {
m ⇒ m.isStatic && m.isPublic && m.returnType == ot
})
}
} else if (method.isProtected) {
isExtensible(ot).isYesOrUnknown &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ package tac
package cg

import scala.reflect.runtime.universe.runtimeMirror

import scala.collection.JavaConverters._

import org.opalj.log.LogContext
import org.opalj.log.OPALLogger.error
import org.opalj.fpcf.PropertyStore
Expand All @@ -16,6 +14,7 @@ import org.opalj.br.analyses.ProjectInformationKey
import org.opalj.br.analyses.SomeProject
import org.opalj.br.analyses.cg.InitialEntryPointsKey
import org.opalj.br.analyses.ProjectInformationKeys
import org.opalj.br.analyses.cg.CallBySignatureKey
import org.opalj.br.fpcf.FPCFAnalysesManagerKey
import org.opalj.br.fpcf.FPCFAnalysisScheduler
import org.opalj.br.fpcf.PropertyStoreKey
Expand All @@ -30,6 +29,8 @@ import org.opalj.tac.fpcf.analyses.LazyTACAIProvider
*/
trait AbstractCallGraphKey extends ProjectInformationKey[CallGraph, Nothing] {

private[this] val CallBySignatureConfigKey = "org.opalj.br.analyses.cg.callBySignatureResolution"

/**
* Lists the call graph specific schedulers that must be run to compute the respective call
* graph.
Expand All @@ -39,12 +40,14 @@ trait AbstractCallGraphKey extends ProjectInformationKey[CallGraph, Nothing] {
): Traversable[FPCFAnalysisScheduler]

override def requirements(project: SomeProject): ProjectInformationKeys = {

Seq(
DeclaredMethodsKey,
InitialEntryPointsKey,
PropertyStoreKey,
FPCFAnalysesManagerKey
) ++
requiresCallBySignatureKey(project) ++
callGraphSchedulers(project).flatMap(_.requiredProjectInformation) ++
registeredAnalyses(project).flatMap(_.requiredProjectInformation)
}
Expand Down Expand Up @@ -96,4 +99,13 @@ trait AbstractCallGraphKey extends ProjectInformationKey[CallGraph, Nothing] {
None
}
}

private[this] def requiresCallBySignatureKey(p: SomeProject): ProjectInformationKeys = {
val config = p.config
if (config.hasPath(CallBySignatureConfigKey)
&& config.getBoolean(CallBySignatureConfigKey)) {
return Seq(CallBySignatureKey);
}
Seq.empty
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ 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.CallBySignatureKey
import org.opalj.br.analyses.cg.IsOverridableMethodKey
import org.opalj.collection.immutable.RefArray
import org.opalj.tac.fpcf.properties.TACAI

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

private[this] val isMethodOverridable: Method ⇒ Answer = project.get(IsOverridableMethodKey)
private[this] lazy val getCBSTargets = project.get(CallBySignatureKey)
private[this] val resovleCallBySignature =
project.config.getBoolean("org.opalj.br.analyses.cg.callBySignatureResolution")

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

/**
Expand Down Expand Up @@ -79,7 +87,69 @@ trait AbstractCallGraphAnalysis extends ReachableMethodAnalysis {
specializedDeclaringClassType: ReferenceType,
potentialTargets: ForeachRefIterator[ObjectType],
calleesAndCallers: DirectCalls
)(implicit state: State): Unit
)(implicit state: State): Unit = {
val cbsTargets = if (resovleCallBySignature && call.isInterface & call.declaringClass.isObjectType) {
val cf = project.classFile(call.declaringClass.asObjectType)
cf.flatMap { _.findMethod(call.name, call.descriptor) }.map {
getCBSTargets(_)
}.getOrElse(RefArray.empty)
} else RefArray.empty

val targetTypes = potentialTargets ++ cbsTargets.foreachIterator

for (possibleTgtType ← targetTypes) {
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 +421,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
Loading