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

Use more performant data structures for type analyses #91

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 @@ -22,6 +22,7 @@ import org.opalj.br.fpcf.properties.cg.NoCallers
import org.opalj.br.instructions.INVOKESPECIAL
import org.opalj.br.instructions.NEW
import org.opalj.collection.immutable.UIDSet
import org.opalj.collection.mutable.RefArrayBuffer
import org.opalj.fpcf.EOptionP
import org.opalj.fpcf.EPK
import org.opalj.fpcf.EPS
Expand All @@ -37,8 +38,6 @@ import org.opalj.fpcf.PropertyStore
import org.opalj.fpcf.Results
import org.opalj.fpcf.SomeEPS
import org.opalj.fpcf.UBP
import scala.collection.mutable
import scala.collection.mutable.ListBuffer

/**
* Marks types as instantiated if their constructor is invoked. Constructors invoked by subclass
Expand Down Expand Up @@ -106,7 +105,7 @@ class InstantiatedTypesAnalysis private[analyses] (
seenCallers: Set[DeclaredMethod]
): PropertyComputationResult = {
var newSeenCallers = seenCallers
val partialResults = new ListBuffer[PartialResult[TypeSetEntity, InstantiatedTypes]]()
val partialResults = RefArrayBuffer.empty[PartialResult[TypeSetEntity, InstantiatedTypes]]
for {
(caller, _, _) ← callersUB.callers
// if we already analyzed the caller, we do not need to do it twice
Expand All @@ -128,15 +127,15 @@ class InstantiatedTypesAnalysis private[analyses] (
continuation(declaredMethod, declaredType, newSeenCallers)
)

Results(reRegistration, partialResults)
Results(reRegistration, partialResults.iterator())
}
}

private[this] def processSingleCaller(
declaredMethod: DeclaredMethod,
declaredType: ObjectType,
caller: DeclaredMethod,
partialResults: ListBuffer[PartialResult[TypeSetEntity, InstantiatedTypes]]
partialResults: RefArrayBuffer[PartialResult[TypeSetEntity, InstantiatedTypes]]
): Unit = {
// a constructor is called by a non-constructor method, there will be an initialization.
if (caller.name != "<init>") {
Expand Down Expand Up @@ -275,28 +274,29 @@ class InstantiatedTypesAnalysisScheduler(
val packageIsClosed = p.get(ClosedPackagesKey)
val declaredMethods = p.get(DeclaredMethodsKey)
val entryPoints = p.get(InitialEntryPointsKey)
val initialInstantiatedTypes = p.get(InitialInstantiatedTypesKey).toSet
val initialInstantiatedTypes =
UIDSet[ReferenceType](p.get(InitialInstantiatedTypesKey).toSeq: _*)

// While processing entry points and fields, we keep track of all array types we see, as
// well as subtypes and lower-dimensional types. These types also need to be
// pre-initialized. Note: This set only contains ArrayTypes whose element type is an
// ObjectType. Arrays of primitive types can be ignored.
val seenArrayTypes = mutable.Set[ArrayType]()
val seenArrayTypes = UIDSet.newBuilder[ArrayType]

def initialize(setEntity: TypeSetEntity, types: Traversable[ReferenceType]): Unit = {
def initialize(setEntity: TypeSetEntity, types: UIDSet[ReferenceType]): Unit = {
ps.preInitialize(setEntity, InstantiatedTypes.key) {
case UBP(typeSet) ⇒
InterimEUBP(setEntity, typeSet.updated(types))
case _: EPK[_, _] ⇒
InterimEUBP(setEntity, InstantiatedTypes(UIDSet(types.toSeq: _*)))
InterimEUBP(setEntity, InstantiatedTypes(types))
case eps ⇒
sys.error(s"unexpected property: $eps")
}
}

// Some cooperative analyses originally meant for RTA may require the global type set
// to be pre-initialized. For that purpose, an empty type set is sufficient.
initialize(p, Traversable.empty)
initialize(p, UIDSet.empty)

def isRelevantArrayType(rt: Type): Boolean =
rt.isArrayType && rt.asArrayType.elementType.isObjectType
Expand All @@ -307,8 +307,8 @@ class InstantiatedTypesAnalysisScheduler(
ep ← entryPoints;
dm = declaredMethods(ep)
) {
val typeFilters = mutable.Set[ReferenceType]()
val arrayTypeAssignments = mutable.Set[ArrayType]()
val typeFilters = UIDSet.newBuilder[ReferenceType]
val arrayTypeAssignments = UIDSet.newBuilder[ArrayType]

if (!dm.definedMethod.isStatic) {
typeFilters += dm.declaringClassType
Expand All @@ -331,19 +331,21 @@ class InstantiatedTypesAnalysisScheduler(
}
}

val typeFilterSet = typeFilters.result()

// Initial assignments of ObjectTypes
val objectTypeAssignments = initialInstantiatedTypes.filter(iit ⇒ typeFilters.exists(tf ⇒
p.classHierarchy.isSubtypeOf(iit, tf)))
val objectTypeAssignments = initialInstantiatedTypes.filter(iit ⇒
typeFilterSet.exists(tf ⇒ p.classHierarchy.isSubtypeOf(iit, tf)))

val initialAssignment = arrayTypeAssignments ++ objectTypeAssignments
val initialAssignment = objectTypeAssignments ++ arrayTypeAssignments.result()

val dmSetEntity = selectSetEntity(dm)

initialize(dmSetEntity, initialAssignment)
}

// Returns true if the field's type indicates that the field should be pre-initialized.
def fieldIsRelevant(f: Field): Boolean = {
@inline def fieldIsRelevant(f: Field): Boolean = {
// Only fields which are ArrayType or ObjectType are relevant.
f.fieldType.isReferenceType &&
// If the field is an ArrayType, then the array's element type must be an ObjectType.
Expand Down Expand Up @@ -374,25 +376,32 @@ class InstantiatedTypesAnalysisScheduler(
// Assign initial types to all accessable fields.
p.classFile(ot) match {
case Some(cf) ⇒
for (f ← cf.fields if fieldIsRelevant(f) && f.isNotFinal && fieldIsAccessible(f)) {
for (f ← cf.fields if f.isNotFinal && fieldIsRelevant(f) && fieldIsAccessible(f)) {
val fieldType = f.fieldType.asReferenceType

val initialAssignments = fieldType match {
case ot: ObjectType ⇒
initialInstantiatedTypes.filter(
p.classHierarchy.isSubtypeOf(_, ot)
)

case at: ArrayType ⇒
seenArrayTypes += at

val dim = at.dimensions
val et = at.elementType.asObjectType
p.classHierarchy.allSubtypes(et, reflexive = true)
.intersect(initialInstantiatedTypes).map(
ArrayType(dim, _)
)

import p.classHierarchy

val initialAssignments = if (fieldType.isObjectType) {
val ot = fieldType.asObjectType
initialInstantiatedTypes.foldLeft(UIDSet.newBuilder[ReferenceType]) {
(assignments, iit) ⇒
if (classHierarchy.isSubtypeOf(iit, ot)) {
assignments += iit
}
assignments
}.result()
} else {
val at = fieldType.asArrayType
seenArrayTypes += at
val dim = at.dimensions
val et = at.elementType.asObjectType
val allSubtypes = p.classHierarchy.allSubtypes(et, true)
initialInstantiatedTypes.foldLeft(UIDSet.newBuilder[ReferenceType]) {
(assignments, iit) ⇒
if (allSubtypes.contains(iit.asObjectType)) {
assignments += ArrayType(dim, iit)
}
assignments
}.result()
}

val fieldSetEntity = selectSetEntity(f)
Expand All @@ -409,25 +418,32 @@ class InstantiatedTypesAnalysisScheduler(
// and initialize their type sets.

// Remember which ArrayTypes were processed, so we don't do it twice.
val initializedArrayTypes = mutable.Set[ArrayType]()
val initializedArrayTypes = new java.util.HashSet[ArrayType]()

def initializeArrayType(at: ArrayType): Unit = {
// If this type has already been initialized, we skip it.
if (initializedArrayTypes.contains(at)) {
return ;
}

initializedArrayTypes += at
initializedArrayTypes.add(at)

val et = at.elementType.asObjectType
val subtypes = p.classHierarchy.allSubtypes(et, reflexive = true).intersect(initialInstantiatedTypes)
val allSubtypes = p.classHierarchy.allSubtypes(et, true)
val subtypes =
initialInstantiatedTypes.foldLeft(UIDSet.newBuilder[ReferenceType]) { (builder, iit) ⇒
if (allSubtypes.contains(iit.asObjectType)) {
builder += iit
}
builder
}.result()

val dim = at.dimensions
if (dim > 1) {
// Initialize multidimensional ArrayType. E.g., if at == A[][] and A is a supertype of A1,
// we need to assign A[] and A1[] to the type set of A[][].
val assignedArrayTypes = subtypes.map(ArrayType(dim - 1, _))
initialize(at, assignedArrayTypes)
val assignedArrayTypes: UIDSet[ArrayType] = subtypes.map(ArrayType(dim - 1, _))
initialize(at, assignedArrayTypes.asInstanceOf[UIDSet[ReferenceType]])

// After that, we also need to initialize the ArrayTypes which were just assigned. It is possible
// that these were types which were not initially seen when processing entry points and fields.
Expand All @@ -438,6 +454,6 @@ class InstantiatedTypesAnalysisScheduler(
}
}

seenArrayTypes foreach initializeArrayType
seenArrayTypes.result() foreach initializeArrayType
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ package analyses
package cg
package xta

import scala.collection.mutable

import org.opalj.collection.immutable.UIDSet
import org.opalj.fpcf.EOptionP
import org.opalj.fpcf.SomeEOptionP
Expand All @@ -18,6 +16,10 @@ import org.opalj.br.ReferenceType
import org.opalj.br.fpcf.properties.cg.InstantiatedTypes
import org.opalj.tac.fpcf.properties.TACAI

import scala.collection.mutable

import scala.collection.JavaConverters._

/**
* Manages the state of each method analyzed by [[PropagationBasedCallGraphAnalysis]].
*
Expand All @@ -29,10 +31,10 @@ class PropagationBasedCGState(
_instantiatedTypesDependees: Iterable[EOptionP[TypeSetEntity, InstantiatedTypes]]
) extends CGState {

private[this] val _instantiatedTypesDependeeMap: mutable.Map[TypeSetEntity, EOptionP[TypeSetEntity, InstantiatedTypes]] = mutable.Map.empty
private[this] val _instantiatedTypesDependeeMap = new java.util.HashMap[TypeSetEntity, EOptionP[TypeSetEntity, InstantiatedTypes]]()

for (dependee ← _instantiatedTypesDependees) {
_instantiatedTypesDependeeMap.update(dependee.e, dependee)
_instantiatedTypesDependeeMap.put(dependee.e, dependee)
}

private[this] val _virtualCallSites: mutable.LongMap[mutable.Set[CallSiteT]] = mutable.LongMap.empty
Expand All @@ -46,23 +48,25 @@ class PropagationBasedCGState(
def updateInstantiatedTypesDependee(
instantiatedTypesDependee: EOptionP[TypeSetEntity, InstantiatedTypes]
): Unit = {
_instantiatedTypesDependeeMap.update(instantiatedTypesDependee.e, instantiatedTypesDependee)
_instantiatedTypesDependeeMap.put(instantiatedTypesDependee.e, instantiatedTypesDependee)
}

def instantiatedTypes(typeSetEntity: TypeSetEntity): UIDSet[ReferenceType] = {
val typeDependee = _instantiatedTypesDependeeMap(typeSetEntity)
val typeDependee = _instantiatedTypesDependeeMap.get(typeSetEntity)
if (typeDependee.hasUBP)
typeDependee.ub.types
else
UIDSet.empty
}

def instantiatedTypesContains(tpe: ReferenceType): Boolean = {
_instantiatedTypesDependeeMap.keys.exists(instantiatedTypes(_).contains(tpe))
_instantiatedTypesDependeeMap.values().iterator().asScala.exists { eOptP ⇒
instantiatedTypes(eOptP.e).contains(tpe)
}
}

def newInstantiatedTypes(typeSetEntity: TypeSetEntity, seenTypes: Int): TraversableOnce[ReferenceType] = {
val typeDependee = _instantiatedTypesDependeeMap(typeSetEntity)
val typeDependee = _instantiatedTypesDependeeMap.get(typeSetEntity)
if (typeDependee.hasUBP) {
typeDependee.ub.dropOldest(seenTypes)
} else {
Expand Down Expand Up @@ -92,7 +96,7 @@ class PropagationBasedCGState(
}

def removeCallSite(instantiatedType: ObjectType): Unit = {
_virtualCallSites -= instantiatedType.id.toLong
_virtualCallSites.remove(instantiatedType.id.toLong)
}

/////////////////////////////////////////////
Expand All @@ -102,10 +106,11 @@ class PropagationBasedCGState(
/////////////////////////////////////////////

override def hasOpenDependencies: Boolean = {
_instantiatedTypesDependeeMap.exists(_._2.isRefinable) || super.hasOpenDependencies
super.hasOpenDependencies ||
_instantiatedTypesDependeeMap.values().iterator().asScala.exists(_.isRefinable)
}

override def dependees: Set[SomeEOptionP] = {
super.dependees ++ _instantiatedTypesDependeeMap.valuesIterator
super.dependees ++ _instantiatedTypesDependeeMap.values().asScala
}
}
Loading