All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opalj.support.debug.InterpretMethod.scala Maven / Gradle / Ivy

/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.support.debug

import java.util.Date
import java.net.URL

import org.opalj.io.writeAndOpen
import org.opalj.graphs.toDot

import org.opalj.br.Method
import org.opalj.br.analyses.SomeProject
import org.opalj.br.analyses.Project
import org.opalj.ai.InterpretationFailedException
import org.opalj.ai.AI
import org.opalj.ai.Domain
import org.opalj.ai.MultiTracer
import org.opalj.ai.BaseAI
import org.opalj.ai.util
import org.opalj.ai.AITracer
import org.opalj.ai.domain.l0.BaseDomain
import org.opalj.ai.domain.TheCode
import org.opalj.ai.util.XHTML
import org.opalj.ai.common.XHTML.dump
import org.opalj.ai.common.XHTML.dumpAIState
import org.opalj.ai.domain.RecordCFG
import org.opalj.ai.domain.RecordDefUse

/**
 * A small basic framework that facilitates the abstract interpretation of a
 * specific method using a configurable domain.
 *
 * @author Michael Eichberg
 */
object InterpretMethod {

    private class IMAI(IdentifyDeadVariables: Boolean) extends AI[Domain](IdentifyDeadVariables) {

        override def isInterrupted: Boolean = Thread.interrupted()

        val consoleTracer: AITracer = new ConsoleTracer { override val printOIDs = true }
        //new ConsoleEvaluationTracer {}

        val xHTMLTracer: XHTMLTracer = new XHTMLTracer {}

        override val tracer = Some(new MultiTracer(consoleTracer, xHTMLTracer))
    }

    /**
     * Traces the interpretation of a single method and prints out the results.
     *
     * @param args The first element must be the name of a class file, a jar file
     *      or a directory containing the former. The second element must
     *      denote the name of a class and the third must denote the name of a method
     *      of the respective class. If the method is overloaded the first method
     *      is returned.
     */
    def main(args: Array[String]): Unit = {
        import Console.RED
        import Console.RESET
        import language.existentials

        def printUsage(issue: Option[String]): Unit = {
            issue.foreach(issue ⇒ println(s"Failure: $issue."))
            println("You have to specify the following parameters.")
            println("\t1: a jar/class file or a directory containing jar/class files.")
            println("\t2: the name of a class.")
            println("\t3: the simple name or signature of a method of the specified class.")
            println("\t4[Optional]: -domain=CLASS the name of the class of the configurable domain to use.")
            println("\t5[Optional]: -trace={true,false} default:true")
            println("\t6[Optional]: -identifyDeadVariables={true,false} default:true")
        }

        if (args.length < 3 || args.length > 5) {
            printUsage(Some("wrong number of parameters"))
            return ;
        }
        var remainingArgs = args.toList
        val fileName = remainingArgs.head; remainingArgs = remainingArgs.tail
        val className = remainingArgs.head; remainingArgs = remainingArgs.tail
        val methodName = remainingArgs.head; remainingArgs = remainingArgs.tail
        val domainClass = {
            if (remainingArgs.nonEmpty && remainingArgs.head.startsWith("-domain=")) {
                val domainRawClass = Class.forName(remainingArgs.head.substring(8))
                val clazz = domainRawClass.asInstanceOf[Class[_ <: Domain]]
                remainingArgs = remainingArgs.tail
                clazz
            } else // default domain
                classOf[BaseDomain[java.net.URL]]
        }
        val doTrace = {
            if (remainingArgs.nonEmpty && remainingArgs.head.startsWith("-trace=")) {
                val result = remainingArgs.head == "-trace=true" || remainingArgs.head == "-trace=1"
                remainingArgs = remainingArgs.tail
                result
            } else
                true
        }
        val doIdentifyDeadVariables = {
            if (remainingArgs.nonEmpty && remainingArgs.head.startsWith("-identifyDeadVariables=")) {
                val result = (
                    remainingArgs.head == "-identifyDeadVariables=true" ||
                    remainingArgs.head == "-identifyDeadVariables=1"
                )
                remainingArgs = remainingArgs.tail
                result
            } else
                true
        }
        if (remainingArgs.nonEmpty) {
            printUsage(Some(remainingArgs.mkString("unexpected arguments: ", ", ", "")))
            return ;
        }

        def createDomain[Source: reflect.ClassTag](project: SomeProject, method: Method): Domain = {

            scala.util.control.Exception.ignoring(classOf[NoSuchMethodException]) {
                val constructor = domainClass.getConstructor(classOf[Object])
                return constructor.newInstance(method.classFile);
            }

            val constructor = domainClass.getConstructor(classOf[Project[URL]], classOf[Method])
            constructor.newInstance(project, method)
        }

        val file = new java.io.File(fileName)
        if (!file.exists()) {
            println(RED+"[error] The file does not exist: "+fileName+"."+RESET)
            return ;
        }

        val project =
            try {
                Project(file)
            } catch {
                case e: Exception ⇒
                    println(RED+"[error] Cannot process file: "+e.getMessage+"."+RESET)
                    return ;
            }

        val classFile = {
            val fqn = className.replace('.', '/')
            project.allClassFiles.find(_.fqn == fqn).getOrElse {
                println(RED+"[error] Cannot find the class: "+className+"."+RESET)
                return ;
            }
        }

        val method =
            (
                if (methodName.contains("("))
                    classFile.methods.find(m ⇒ m.descriptor.toJava(m.name).contains(methodName))
                else
                    classFile.methods.find(_.name == methodName)
            ) match {
                    case Some(method) ⇒
                        if (method.body.isDefined)
                            method
                        else {
                            println(RED+
                                "[error] The method: "+methodName+" does not have a body"+RESET)
                            return ;
                        }
                    case None ⇒
                        println(RED+
                            "[error] Cannot find the method: "+methodName+"."+RESET +
                            classFile.methods.map(m ⇒ m.descriptor.toJava(m.name)).toSet.
                            toSeq.sorted.mkString(" Candidates: ", ", ", "."))
                        return ;
                }

        var ai: AI[Domain] = null;
        try {
            val result =
                if (doTrace) {
                    ai = new IMAI(doIdentifyDeadVariables)
                    ai(method, createDomain(project, method))
                } else {
                    val body = method.body.get
                    println("Starting abstract interpretation of: ")
                    println("\t"+classFile.thisType.toJava+"{")
                    println("\t\t"+method.signatureToJava(true)+
                        "[instructions="+body.instructions.size+
                        "; #max_stack="+body.maxStack+
                        "; #locals="+body.maxLocals+"]")
                    println("\t}")
                    ai = new BaseAI(doIdentifyDeadVariables)
                    val result = ai(method, createDomain(project, method))
                    println("Finished abstract interpretation.")
                    result
                }

            if (result.domain.isInstanceOf[RecordCFG]) {
                val evaluatedInstructions = result.evaluatedInstructions
                val cfgDomain = result.domain.asInstanceOf[RecordCFG]

                val cfgAsDotGraph = toDot(Set(cfgDomain.cfgAsGraph()), ranksep = "0.3").toString
                val cfgFile = writeAndOpen(cfgAsDotGraph, "AICFG", ".gv")
                println("AI CFG: "+cfgFile)

                val domAsDot = cfgDomain.dominatorTree.toDot(evaluatedInstructions.contains)
                val domFile = writeAndOpen(domAsDot, "DominatorTreeOfTheAICFG", ".gv")
                println("AI CFG - Dominator tree: "+domFile)

                val postDomAsDot = cfgDomain.postDominatorTree.toDot(evaluatedInstructions.contains)
                val postDomFile = writeAndOpen(postDomAsDot, "PostDominatorTreeOfTheAICFG", ".gv")
                println("AI CFG - Post-Dominator tree: "+postDomFile)

                val cdg = cfgDomain.pdtBasedControlDependencies
                val rdfAsDotGraph = cdg.toDot(evaluatedInstructions.contains)
                val rdfFile = writeAndOpen(rdfAsDotGraph, "ReverseDominanceFrontiersOfAICFG", ".gv")
                println("AI CFG - Reverse Dominance Frontiers: "+rdfFile)
            }

            if (result.domain.isInstanceOf[RecordDefUse]) {
                val duInfo = result.domain.asInstanceOf[RecordDefUse]
                writeAndOpen(duInfo.dumpDefUseInfo(), "DefUseInfo", ".html")

                val dotGraph = toDot(duInfo.createDefUseGraph(method.body.get)).toString()
                writeAndOpen(dotGraph, "ImplicitDefUseGraph", ".gv")
            }

            writeAndOpen(
                dump(
                    Some(classFile),
                    Some(method),
                    method.body.get,
                    Some(
                        s"Analyzed: ${new Date}
Domain: ${domainClass.getName}
"+ ( if (doIdentifyDeadVariables) "Dead Variables Identification
" else "Dead Variables are not filtered.
" ) + XHTML.instructionsToXHTML("PCs where paths join", result.cfJoins) + ( if (result.subroutinePCs.nonEmpty) { XHTML.instructionsToXHTML( "Subroutine instructions", result.subroutinePCs ) } else { "" } ) + XHTML.evaluatedInstructionsToXHTML(result.evaluatedPCs) ), result.domain )(result.cfJoins, result.operandsArray, result.localsArray), "AIResult", ".html" ) } catch { case ife: InterpretationFailedException ⇒ ai match { case ai: IMAI ⇒ ai.xHTMLTracer.result(null); case _ ⇒ /*nothing to do*/ } def causeToString(ife: InterpretationFailedException, nested: Boolean): String = { val domain = ife.domain val parameters = if (nested) { ife.localsArray(0).toSeq.reverse. filter(_ != null).map(_.toString). mkString("Parameters:", ", ", "
") } else "" val aiState = s"

${domain.getClass.getName}( ${domain.toString} )

"+ parameters+ "Current instruction: "+ife.pc+"
"+ XHTML.evaluatedInstructionsToXHTML(ife.evaluatedPCs) + { if (ife.worklist.nonEmpty) ife.worklist.mkString("Remaining worklist:\n
", ", ", "
") else "Remaining worklist: EMPTY
" } val metaInformation = ife.cause match { case ife: InterpretationFailedException ⇒ aiState + ife.cause. getStackTrace. mkString("\n
  • ", "
  • \n
  • ", "
\n")+ "
"+causeToString(ife, true)+"
" case e: Throwable ⇒ val message = e.getMessage() if (message != null) aiState+"
Underlying cause: "+util.XHTML.htmlify(message) else aiState+"
Underlying cause: " case _ ⇒ aiState } if (nested && ife.domain.isInstanceOf[TheCode]) metaInformation + dumpAIState( ife.domain.asInstanceOf[TheCode].code, ife.domain )(ife.cfJoins, ife.operandsArray, ife.localsArray) else metaInformation } val resultHeader = Some(causeToString(ife, false)) val evaluationDump = dump( Some(classFile), Some(method), method.body.get, resultHeader, ife.domain )(ife.cfJoins, ife.operandsArray, ife.localsArray) writeAndOpen(evaluationDump, "StateOfCrashedAbstractInterpretation", ".html") throw ife } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy