Skip to content

Commit

Permalink
Use more performant data structures for type analyses
Browse files Browse the repository at this point in the history
  • Loading branch information
errt committed Apr 9, 2021
1 parent 93e0485 commit 6e2fd58
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,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,9 +38,6 @@ 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
* constructors do not result in additional instantiated types.
Expand Down Expand Up @@ -106,7 +104,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 +126,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 @@ -273,28 +271,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 @@ -305,8 +304,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 @@ -329,19 +328,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 @@ -372,25 +373,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 @@ -407,25 +415,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 @@ -436,6 +451,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

0 comments on commit 6e2fd58

Please sign in to comment.