org.opalj.support.debug.InterpretMethods.scala Maven / Gradle / Ivy
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package support
package debug
import java.io.File
import java.net.URL
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicLong
import scala.util.control.ControlThrowable
import scala.xml.NodeSeq
import org.opalj.log.LogContext
import org.opalj.io.writeAndOpen
import org.opalj.br._
import org.opalj.br.analyses._
import org.opalj.ai.util.XHTML
import org.opalj.ai.domain
import org.opalj.ai.Domain
import org.opalj.ai.InstructionCountBoundedAI
/**
* Performs an abstract interpretation of all methods of the given class file(s) using
* a configurable domain.
*
* This class is meant to support the development and testing of new domains.
*
* @author Michael Eichberg
*/
object InterpretMethods extends AnalysisExecutor {
override def analysisSpecificParametersDescription: String =
"[-domain=]\n"+
"[-verbose={true,false} If true, extensive information is shown.]\n"
override def checkAnalysisSpecificParameters(parameters: Seq[String]): Traversable[String] = {
def isDomainParameter(parameter: String) =
parameter.startsWith("-domain=") && parameter.length() > 8
def isVerbose(parameter: String) =
parameter == "-verbose=true" || parameter == "-verbose=false"
parameters match {
case Nil ⇒ Traversable.empty
case Seq(parameter) ⇒
if (isDomainParameter(parameter) || isVerbose(parameter))
Traversable.empty
else
Traversable("unknown parameter: "+parameter)
case Seq(parameter1, parameter2) ⇒
if (!isDomainParameter(parameter1))
Seq("the first parameter does not specify the domain: "+parameter1)
else if (!isVerbose(parameter2))
Seq("the second parameter has to be \"verbose\": "+parameter2)
else
Traversable.empty
}
}
override val analysis = new InterpretMethodsAnalysis[URL]
}
/**
* An analysis that analyzes all methods of all class files of a project using a
* custom domain.
*
* @author Michael Eichberg
*/
class InterpretMethodsAnalysis[Source] extends Analysis[Source, BasicReport] {
override def title: String = "interpret methods"
override def description: String = "performs an abstract interpretation of all methods"
override def analyze(
project: Project[Source],
parameters: Seq[String] = List.empty,
initProgressManagement: (Int) ⇒ ProgressManagement
): BasicReport = {
implicit val logContext = project.logContext
val verbose = parameters.nonEmpty &&
(parameters.head == "-verbose=true" ||
(parameters.size == 2 && parameters.tail.head == "-verbose=true"))
val (message, detailedErrorInformationFile) =
if (parameters.nonEmpty && parameters.head.startsWith("-domain")) {
InterpretMethodsAnalysis.interpret(
project,
Class.forName(parameters.head.substring(8)).asInstanceOf[Class[_ <: Domain]],
verbose,
initProgressManagement,
6d
)
} else {
InterpretMethodsAnalysis.interpret(
project,
classOf[domain.l0.BaseDomain[java.net.URL]],
verbose,
initProgressManagement,
6d
)
}
BasicReport(
message + detailedErrorInformationFile.map(" (See "+_+" for details.)").getOrElse("")
)
}
}
object InterpretMethodsAnalysis {
def println(s: String): Unit = {
Console.out.println(s)
Console.flush()
}
def interpret[Source](
project: Project[Source],
domainClass: Class[_ <: Domain],
beVerbose: Boolean,
initProgressManagement: (Int) ⇒ ProgressManagement,
maxEvaluationFactor: Double = 3d
)(
implicit
logContext: LogContext
): (String, Option[File]) = {
// TODO Add support for reporting the progress and to interrupt the analysis.
import Console.GREEN
import Console.RED
import Console.RESET
import Console.YELLOW
val performanceEvaluationContext = new org.opalj.util.PerformanceEvaluation
import performanceEvaluationContext.getTime
import performanceEvaluationContext.time
val methodsCount = new AtomicInteger(0)
val instructionEvaluationsCount = new AtomicLong(0)
val domainConstructor = domainClass.getConstructor(classOf[Project[URL]], classOf[Method])
def analyzeMethod(
source: String,
method: Method
): Option[(String, ClassFile, Method, Throwable)] = {
val body = method.body.get
try {
if (beVerbose) println(method.toJava(YELLOW+"[started]"+RESET))
val evaluatedCount = time('AI) {
val ai = new InstructionCountBoundedAI[Domain](body, maxEvaluationFactor, true)
val domain = domainConstructor.newInstance(project, method)
val result = ai(method, domain)
if (result.wasAborted) {
if (beVerbose)
println(
method.toJava(
RED+"[aborted after evaluating "+
ai.currentEvaluationCount+
" instructions (size of instructions array="+
body.instructions.size+
"; max="+ai.maxEvaluationCount+")]"+
RESET
)
)
val message = s"evaluation bound (max=${ai.maxEvaluationCount}) exceeded"
throw new InterruptedException(message)
}
val evaluatedCount = ai.currentEvaluationCount.toLong
instructionEvaluationsCount.addAndGet(evaluatedCount)
evaluatedCount
}
val naiveEvaluatedCount = time('NAIVE_AI) {
val ai = new InstructionCountBoundedAI[Domain](body, maxEvaluationFactor, false)
val domain = domainConstructor.newInstance(project, method)
ai(method, domain)
ai.currentEvaluationCount
}
if (beVerbose && naiveEvaluatedCount > evaluatedCount) {
val codeLength = body.instructions.length
val message = method.toJava(
s"evaluation steps (code size:$codeLength): "+
s"$naiveEvaluatedCount (w/o dead variables analysis) vs. $evaluatedCount"
)
println(message)
}
if (beVerbose) println(method.toJava(GREEN+"[finished]"+RESET))
methodsCount.incrementAndGet()
None
} catch {
case ct: ControlThrowable ⇒ throw ct
case t: Throwable ⇒
// basically, we want to catch everything!
val classFile = method.classFile
val source = project.source(classFile.thisType).get.toString
Some((source, classFile, method, t))
}
}
val collectedExceptions = time('OVERALL) {
val results = new ConcurrentLinkedQueue[(String, ClassFile, Method, Throwable)]()
project.parForeachMethodWithBody() { m ⇒
analyzeMethod(m.source.toString, m.method).map(results.add)
}
import scala.collection.JavaConverters._
results.asScala
}
if (collectedExceptions.nonEmpty) {
val header = Generated { new java.util.Date() }
val body = Seq(header) ++
(for ((exResource, exInstances) ← collectedExceptions.groupBy(e ⇒ e._1)) yield {
val exDetails =
exInstances.map { ex ⇒
val (_, classFile, method, throwable) = ex
{ classFile.thisType.fqn }
"{ method.signatureToJava(true) }"
{ "Length: "+method.body.get.instructions.length }
{ XHTML.throwableToXHTML(throwable) }
}
{ exResource }
Number of thrown exceptions: { exInstances.size }
{ exDetails }
})
val node =
XHTML.createXHTML(
Some("Exceptions Thrown During Interpretation"), NodeSeq.fromSeq(body)
)
val file = writeAndOpen(node, "ExceptionsOfCrashedAbstractInterpretations", ".html")
(
"During the interpretation of "+
methodsCount.get+" methods (of "+project.methodsCount+") in "+
project.classFilesCount+" classes (real time: "+getTime('OVERALL).toSeconds+
", ai (∑CPU Times): "+getTime('AI).toSeconds+
")"+collectedExceptions.size+" exceptions occured.",
Some(file)
)
} else {
(
"No exceptions occured during the interpretation of "+
methodsCount.get+" methods (of "+project.methodsCount+") in "+
project.classFilesCount+" classes\nreal time: "+getTime('OVERALL).toSeconds+"\n"+
"ai (∑CPU Times): "+getTime('AI).toSeconds +
s"; evaluated ${instructionEvaluationsCount.get} instructions\n"+
"naive ai (∑CPU Times): "+getTime('NAIVE_AI).toSeconds+"\n",
None
)
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy