From 4e4db16b0cc27f0f9bf57b9239734a997bb8a811 Mon Sep 17 00:00:00 2001 From: Dominik Helm Date: Thu, 8 Apr 2021 12:25:19 +0200 Subject: [PATCH] Use more performant data structures for type analyses --- .../cg/xta/InstantiatedTypesAnalysis.scala | 95 +++++++++++-------- .../cg/xta/TypePropagationAnalysis.scala | 29 +++--- 2 files changed, 68 insertions(+), 56 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala index cc3436aa17..7996d03070 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala @@ -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 @@ -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. @@ -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 @@ -128,7 +126,7 @@ class InstantiatedTypesAnalysis private[analyses] ( continuation(declaredMethod, declaredType, newSeenCallers) ) - Results(reRegistration, partialResults) + Results(reRegistration, partialResults.iterator()) } } @@ -136,7 +134,7 @@ class InstantiatedTypesAnalysis private[analyses] ( 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 != "") { @@ -273,20 +271,21 @@ 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") } @@ -294,7 +293,7 @@ class InstantiatedTypesAnalysisScheduler( // 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 @@ -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 @@ -330,10 +329,10 @@ 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) @@ -341,7 +340,7 @@ class InstantiatedTypesAnalysisScheduler( } // 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. @@ -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) @@ -407,7 +413,7 @@ 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. @@ -415,17 +421,24 @@ class InstantiatedTypesAnalysisScheduler( 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. @@ -436,6 +449,6 @@ class InstantiatedTypesAnalysisScheduler( } } - seenArrayTypes foreach initializeArrayType + seenArrayTypes.result() foreach initializeArrayType } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationAnalysis.scala index 2b4939e0e4..93d9fc3d51 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationAnalysis.scala @@ -23,6 +23,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 @@ -37,8 +38,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. @@ -69,20 +68,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 { @@ -155,9 +154,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( @@ -170,7 +169,7 @@ final class TypePropagationAnalysis private[analyses] ( state.updateOwnInstantiatedTypesDependee(eps) val unseenTypes = UIDSet(eps.ub.dropOldest(previouslySeenTypes).toSeq: _*) - implicit val partialResults: ListBuffer[SomePartialResult] = new ListBuffer[SomePartialResult]() + implicit val partialResults: RefArrayBuffer[SomePartialResult] = RefArrayBuffer.empty[SomePartialResult] for (fpe ← state.forwardPropagationEntities) { val filters = state.forwardPropagationFilters(fpe) val propagation = propagateTypes(fpe, unseenTypes, filters) @@ -180,7 +179,7 @@ final class TypePropagationAnalysis private[analyses] ( processArrayTypes(unseenTypes) - returnResults(partialResults) + returnResults(partialResults.iterator()) } private def handleUpdateOfBackwardPropagationTypeSet( @@ -205,7 +204,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) { @@ -231,7 +230,7 @@ final class TypePropagationAnalysis private[analyses] ( )( implicit state: State, - partialResults: ListBuffer[SomePartialResult] + partialResults: RefArrayBuffer[SomePartialResult] ): Unit = { val bytecode = state.method.definedMethod.body.get for { @@ -263,7 +262,7 @@ final class TypePropagationAnalysis private[analyses] ( )( implicit state: State, - partialResults: ListBuffer[SomePartialResult] + partialResults: RefArrayBuffer[SomePartialResult] ): Unit = { val params = UIDSet.newBuilder[ReferenceType] @@ -299,7 +298,7 @@ final class TypePropagationAnalysis private[analyses] ( )( implicit state: State, - partialResults: ListBuffer[SomePartialResult] + partialResults: RefArrayBuffer[SomePartialResult] ): Unit = { val returnValueIsUsed = { val tacIndex = state.tac.properStmtIndexForPC(pc) @@ -334,7 +333,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) @@ -356,7 +355,7 @@ final class TypePropagationAnalysis private[analyses] ( )( implicit state: State, - partialResults: ListBuffer[SomePartialResult] + partialResults: RefArrayBuffer[SomePartialResult] ): Unit = { val typeSetEntity = selectTypeSetEntity(e) if (typeSetEntity == state.typeSetEntity) {