
org.opalj.br.fpcf.analyses.StaticDataUsageAnalysis.scala Maven / Gradle / Ivy
The newest version!
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package br
package fpcf
package analyses
import scala.annotation.switch
import org.opalj.fpcf.Entity
import org.opalj.fpcf.EOptionP
import org.opalj.fpcf.EPS
import org.opalj.fpcf.FinalEP
import org.opalj.fpcf.FinalP
import org.opalj.fpcf.InterimEP
import org.opalj.fpcf.InterimLUBP
import org.opalj.fpcf.InterimResult
import org.opalj.fpcf.InterimUBP
import org.opalj.fpcf.ProperPropertyComputationResult
import org.opalj.fpcf.Property
import org.opalj.fpcf.PropertyBounds
import org.opalj.fpcf.PropertyStore
import org.opalj.fpcf.Result
import org.opalj.fpcf.SomeEOptionP
import org.opalj.fpcf.SomeEPS
import org.opalj.br.analyses.DeclaredMethodsKey
import org.opalj.br.analyses.ProjectInformationKeys
import org.opalj.br.analyses.SomeProject
import org.opalj.br.fpcf.properties.CompileTimeConstancy
import org.opalj.br.fpcf.properties.CompileTimeConstantField
import org.opalj.br.fpcf.properties.CompileTimeVaryingField
import org.opalj.br.fpcf.properties.NoVaryingDataUse
import org.opalj.br.fpcf.properties.StaticDataUsage
import org.opalj.br.fpcf.properties.UsesConstantDataOnly
import org.opalj.br.fpcf.properties.UsesNoStaticData
import org.opalj.br.fpcf.properties.UsesVaryingData
import org.opalj.br.instructions._
/**
* A simple analysis that identifies methods that use global state that may vary during one or
* between several program executions.
*
* @author Dominik Helm
*/
class StaticDataUsageAnalysis private[analyses] ( final val project: SomeProject)
extends FPCFAnalysis {
import project.nonVirtualCall
import project.resolveFieldReference
private[this] val declaredMethods = project.get(DeclaredMethodsKey)
/**
* Retrieves and commits the methods static data usage as calculated for its declaring class
* type for the current DefinedMethod that represents the non-overwritten method in a subtype.
*/
def baseMethodStaticDataUsage(dm: DefinedMethod): ProperPropertyComputationResult = {
def c(eps: SomeEOptionP): ProperPropertyComputationResult = eps match {
case FinalP(sdu) => Result(dm, sdu)
case ep @ InterimLUBP(lb, ub) =>
InterimResult(dm, lb, ub, Set(ep), c)
case epk =>
InterimResult(dm, UsesVaryingData, UsesNoStaticData, Set(epk), c)
}
c(propertyStore(declaredMethods(dm.definedMethod), StaticDataUsage.key))
}
/**
* Determines the allocation freeness of the method.
*
* This function encapsulates the continuation.
*/
def determineUsage(definedMethod: DefinedMethod): ProperPropertyComputationResult = {
if (definedMethod.definedMethod.body.isEmpty)
return Result(definedMethod, UsesVaryingData);
val method = definedMethod.definedMethod
val declaringClassType = method.classFile.thisType
// If thhis is not the method's declaration, but a non-overwritten method in a subtype,
// don't re-analyze the code
if (declaringClassType ne definedMethod.declaringClassType)
return baseMethodStaticDataUsage(definedMethod.asDefinedMethod);
val methodDescriptor = method.descriptor
val methodName = method.name
val body = method.body.get
val instructions = body.instructions
val maxPC = instructions.length
var dependees: Set[EOptionP[Entity, Property]] = Set.empty
var maxLevel: StaticDataUsage = UsesNoStaticData
var currentPC = 0
while (currentPC < maxPC) {
val instruction = instructions(currentPC)
(instruction.opcode: @switch) match {
case GETSTATIC.opcode =>
val GETSTATIC(declaringClass, fieldName, fieldType) = instruction
maxLevel = UsesConstantDataOnly
resolveFieldReference(declaringClass, fieldName, fieldType) match {
// ... we have no support for arrays at the moment
case Some(field) =>
propertyStore(field, CompileTimeConstancy.key) match {
case FinalP(CompileTimeConstantField) =>
case _: FinalEP[_, _] =>
return Result(definedMethod, UsesVaryingData);
case ep =>
dependees += ep
}
case _ =>
// We know nothing about the target field (it is not
// found in the scope of the current project).
return Result(definedMethod, UsesVaryingData);
}
case INVOKESPECIAL.opcode | INVOKESTATIC.opcode => instruction match {
case MethodInvocationInstruction(`declaringClassType`, _, `methodName`, `methodDescriptor`) =>
// We have a self-recursive call; such calls do not influence the allocation
// freeness and are ignored.
// Let's continue with the evaluation of the next instruction.
case mii: NonVirtualMethodInvocationInstruction =>
nonVirtualCall(declaringClassType, mii) match {
case Success(callee) =>
/* Recall that self-recursive calls are handled earlier! */
val constantUsage =
propertyStore(declaredMethods(callee), StaticDataUsage.key)
constantUsage match {
case FinalP(UsesNoStaticData) => /* Nothing to do */
case FinalP(UsesConstantDataOnly) =>
maxLevel = UsesConstantDataOnly
// Handling cyclic computations
case ep @ InterimUBP(_: NoVaryingDataUse) =>
dependees += ep
case _: EPS[_, _] =>
return Result(definedMethod, UsesVaryingData);
case epk =>
dependees += epk
}
case _ /* Empty or Failure */ =>
// We know nothing about the target method (it is not
// found in the scope of the current project).
return Result(definedMethod, UsesVaryingData);
}
}
case INVOKEDYNAMIC.opcode | INVOKEVIRTUAL.opcode | INVOKEINTERFACE.opcode =>
// We don't handle these calls here, just treat them as having allocations
return Result(definedMethod, UsesVaryingData);
case _ =>
// Other instructions (IFs, Load/Stores, Arith., etc.) do not use static data
}
currentPC = body.pcOfNextInstruction(currentPC)
}
if (dependees.isEmpty)
return Result(definedMethod, maxLevel);
// This function computes the “static data usage" for a method based on the usage of its
// callees and the compile-time constancy of its static field reads
def c(eps: SomeEPS): ProperPropertyComputationResult = {
// Let's filter the entity.
dependees = dependees.filter(_.e ne eps.e)
(eps: @unchecked) match {
case FinalP(du: NoVaryingDataUse) =>
if (du eq UsesConstantDataOnly) maxLevel = UsesConstantDataOnly
if (dependees.isEmpty)
Result(definedMethod, maxLevel)
else {
InterimResult(
definedMethod, UsesVaryingData, maxLevel,
dependees, c
)
}
case FinalP(UsesVaryingData) => Result(definedMethod, UsesVaryingData)
case FinalP(CompileTimeConstantField) =>
if (dependees.isEmpty)
Result(definedMethod, maxLevel)
else {
InterimResult(
definedMethod, UsesVaryingData, maxLevel,
dependees, c
)
}
case FinalP(CompileTimeVaryingField) => Result(definedMethod, UsesVaryingData)
case InterimUBP(UsesConstantDataOnly) =>
maxLevel = UsesConstantDataOnly
dependees += eps
InterimResult(definedMethod, UsesVaryingData, maxLevel, dependees, c)
case _: InterimEP[_, _] =>
dependees += eps
InterimResult(definedMethod, UsesVaryingData, maxLevel, dependees, c)
}
}
InterimResult(definedMethod, UsesVaryingData, maxLevel, dependees, c)
}
/** Called when the analysis is scheduled lazily. */
def doDetermineUsage(e: Entity): ProperPropertyComputationResult = {
e match {
case m: DefinedMethod => determineUsage(m)
case m: DeclaredMethod => Result(m, UsesVaryingData)
case _ => throw new UnknownError(s"$e is not a method")
}
}
}
trait StaticDataUsageAnalysisScheduler extends FPCFAnalysisScheduler {
override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey)
final def derivedProperty: PropertyBounds = {
// FIXME Just seems to derive the upper bound...
PropertyBounds.lub(StaticDataUsage)
}
final override def uses: Set[PropertyBounds] = Set(
PropertyBounds.lub(StaticDataUsage),
PropertyBounds.lub(CompileTimeConstancy)
)
}
object EagerStaticDataUsageAnalysis
extends StaticDataUsageAnalysisScheduler
with BasicFPCFEagerAnalysisScheduler {
override def derivesEagerly: Set[PropertyBounds] = Set(derivedProperty)
override def derivesCollaboratively: Set[PropertyBounds] = Set.empty
override def start(p: SomeProject, ps: PropertyStore, unused: Null): FPCFAnalysis = {
val analysis = new StaticDataUsageAnalysis(p)
val declaredMethods = p.get(DeclaredMethodsKey).declaredMethods.collect {
case dm if dm.hasSingleDefinedMethod && dm.definedMethod.body.isDefined => dm.asDefinedMethod
}
ps.scheduleEagerComputationsForEntities(declaredMethods)(analysis.determineUsage)
analysis
}
}
object LazyStaticDataUsageAnalysis
extends StaticDataUsageAnalysisScheduler
with BasicFPCFLazyAnalysisScheduler {
override def derivesLazily: Some[PropertyBounds] = Some(derivedProperty)
override def register(p: SomeProject, ps: PropertyStore, unused: Null): FPCFAnalysis = {
val analysis = new StaticDataUsageAnalysis(p)
ps.registerLazyPropertyComputation(StaticDataUsage.key, analysis.doDetermineUsage)
analysis
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy