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 418d383
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 81 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 @@ -330,18 +329,18 @@ class InstantiatedTypesAnalysisScheduler(
}

// Initial assignments of ObjectTypes
val objectTypeAssignments = initialInstantiatedTypes.filter(iit typeFilters.exists(tf
p.classHierarchy.isSubtypeOf(iit, tf)))
val objectTypeAssignments = initialInstantiatedTypes.filter(iit
typeFilters.result().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 +371,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 +413,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 +449,6 @@ class InstantiatedTypesAnalysisScheduler(
}
}

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

import scala.collection.JavaConverters.asScalaIteratorConverter

import org.opalj.br.ArrayType
import org.opalj.br.Code
import org.opalj.br.DeclaredMethod
Expand All @@ -23,6 +25,7 @@ import org.opalj.br.fpcf.properties.cg.InstantiatedTypes
import org.opalj.br.instructions.CHECKCAST
import org.opalj.br.instructions.INVOKESTATIC
import org.opalj.collection.immutable.UIDSet
import org.opalj.collection.mutable.RefArrayBuffer
import org.opalj.fpcf.EPS
import org.opalj.fpcf.EUBP
import org.opalj.fpcf.Entity
Expand All @@ -37,8 +40,6 @@ import org.opalj.fpcf.SomeEPS
import org.opalj.fpcf.SomePartialResult
import org.opalj.tac.fpcf.properties.TACAI

import scala.collection.mutable.ListBuffer

/**
* This analysis handles the type propagation of XTA, MTA, FTA and CTA call graph
* algorithms.
Expand Down Expand Up @@ -69,20 +70,20 @@ final class TypePropagationAnalysis private[analyses] (

implicit val state: TypePropagationState =
new TypePropagationState(definedMethod, typeSetEntity, tacEP, instantiatedTypesEOptP, calleesEOptP)
implicit val partialResults: ListBuffer[SomePartialResult] = new ListBuffer[SomePartialResult]()
implicit val partialResults: RefArrayBuffer[SomePartialResult] = RefArrayBuffer.empty[SomePartialResult]

if (calleesEOptP.hasUBP)
processCallees(calleesEOptP.ub)
processTACStatements
processArrayTypes(state.ownInstantiatedTypes)

returnResults(partialResults)
returnResults(partialResults.iterator())
}

/**
* Processes the method upon initialization. Finds field/array accesses and wires up dependencies accordingly.
*/
private def processTACStatements(implicit state: State, partialResults: ListBuffer[SomePartialResult]): Unit = {
private def processTACStatements(implicit state: State, partialResults: RefArrayBuffer[SomePartialResult]): Unit = {
val bytecode = state.method.definedMethod.body.get
val tac = state.tac
tac.stmts.foreach {
Expand Down Expand Up @@ -155,9 +156,9 @@ final class TypePropagationAnalysis private[analyses] (
state: State
): ProperPropertyComputationResult = {
state.updateCalleeDependee(eps)
implicit val partialResults: ListBuffer[SomePartialResult] = new ListBuffer[SomePartialResult]()
implicit val partialResults: RefArrayBuffer[SomePartialResult] = RefArrayBuffer.empty[SomePartialResult]
processCallees(eps.ub)
returnResults(partialResults)
returnResults(partialResults.iterator())
}

private def handleUpdateOfOwnTypeSet(
Expand All @@ -170,8 +171,8 @@ final class TypePropagationAnalysis private[analyses] (
state.updateOwnInstantiatedTypesDependee(eps)
val unseenTypes = UIDSet(eps.ub.dropOldest(previouslySeenTypes).toSeq: _*)

implicit val partialResults: ListBuffer[SomePartialResult] = new ListBuffer[SomePartialResult]()
for (fpe state.forwardPropagationEntities) {
implicit val partialResults: RefArrayBuffer[SomePartialResult] = RefArrayBuffer.empty[SomePartialResult]
for (fpe state.forwardPropagationEntities.iterator().asScala) {
val filters = state.forwardPropagationFilters(fpe)
val propagation = propagateTypes(fpe, unseenTypes, filters)
if (propagation.isDefined)
Expand All @@ -180,7 +181,7 @@ final class TypePropagationAnalysis private[analyses] (

processArrayTypes(unseenTypes)

returnResults(partialResults)
returnResults(partialResults.iterator())
}

private def handleUpdateOfBackwardPropagationTypeSet(
Expand All @@ -205,7 +206,7 @@ final class TypePropagationAnalysis private[analyses] (
)(
implicit
state: State,
partialResults: ListBuffer[SomePartialResult]
partialResults: RefArrayBuffer[SomePartialResult]
): Unit = {
for (t unseenTypes if t.isArrayType; at = t.asArrayType if at.elementType.isReferenceType) {
if (state.methodWritesArrays) {
Expand All @@ -231,7 +232,7 @@ final class TypePropagationAnalysis private[analyses] (
)(
implicit
state: State,
partialResults: ListBuffer[SomePartialResult]
partialResults: RefArrayBuffer[SomePartialResult]
): Unit = {
val bytecode = state.method.definedMethod.body.get
for {
Expand Down Expand Up @@ -263,7 +264,7 @@ final class TypePropagationAnalysis private[analyses] (
)(
implicit
state: State,
partialResults: ListBuffer[SomePartialResult]
partialResults: RefArrayBuffer[SomePartialResult]
): Unit = {
val params = UIDSet.newBuilder[ReferenceType]

Expand Down Expand Up @@ -299,7 +300,7 @@ final class TypePropagationAnalysis private[analyses] (
)(
implicit
state: State,
partialResults: ListBuffer[SomePartialResult]
partialResults: RefArrayBuffer[SomePartialResult]
): Unit = {
val returnValueIsUsed = {
val tacIndex = state.tac.properStmtIndexForPC(pc)
Expand Down Expand Up @@ -334,7 +335,7 @@ final class TypePropagationAnalysis private[analyses] (
)(
implicit
state: State,
partialResults: ListBuffer[SomePartialResult]
partialResults: RefArrayBuffer[SomePartialResult]
): Unit = {
// Propagation from and to the same entity can be ignored.
val typeSetEntity = selectTypeSetEntity(e)
Expand All @@ -356,7 +357,7 @@ final class TypePropagationAnalysis private[analyses] (
)(
implicit
state: State,
partialResults: ListBuffer[SomePartialResult]
partialResults: RefArrayBuffer[SomePartialResult]
): Unit = {
val typeSetEntity = selectTypeSetEntity(e)
if (typeSetEntity == state.typeSetEntity) {
Expand Down
Loading

0 comments on commit 418d383

Please sign in to comment.