Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improving Command-Parsing in OPAL with Scallop-Library #226

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from
Binary file added ._command lines
Binary file not shown.
483 changes: 229 additions & 254 deletions DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package support
package parser

import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler
import org.opalj.br.fpcf.analyses.LazyL0PurityAnalysis
import org.opalj.tac.fpcf.analyses.purity.LazyL1PurityAnalysis
import org.opalj.tac.fpcf.analyses.purity.LazyL2PurityAnalysis

/**
* The `AnalysisCommandParser` object is responsible for parsing the runner name and analysis level
* to return the corresponding analysis scheduler for purity analysis.
*
* This utility object matches the given runner name (such as "Purity") and analysis level
* (such as "L0", "L1", or "L2") to return the appropriate `FPCFLazyAnalysisScheduler` instance.
* If an invalid combination of runner name or analysis level is provided, an
* `IllegalArgumentException` is thrown.
*
* Example usage:
* {{{
* val scheduler = AnalysisCommandParser.parse("Purity", "L0")
* }}}
*
* @object AnalysisCommandParser
*/
object AnalysisCommandParser {
def parse(runnerName: Option[String], analysisLevel: Option[String]): Option[FPCFLazyAnalysisScheduler] =
runnerName.get match {
case "Purity" =>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the right approach. One would have to change this class for every analysis that should be parsed as an analysis command. Instead, we would need a way to defer this parsing to an existing command, e.g. the FieldAssignabilityCommand, EscapeCommand, or, for the case that is now here, a PurityCommand.

analysisLevel.get match {
case "L0" => Some(LazyL0PurityAnalysis)
case "L1" => Some(LazyL1PurityAnalysis)
case null | "L2" => Some(LazyL2PurityAnalysis)
case _ => throw new IllegalArgumentException(s"Unknown analysis: $runnerName, $analysisLevel")
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package support
package parser

import org.opalj.commandlinebase.OpalCommandExternalParser
import org.opalj.tac.cg.AllocationSiteBasedPointsToCallGraphKey
import org.opalj.tac.cg.CallGraphKey
import org.opalj.tac.cg.CHACallGraphKey
import org.opalj.tac.cg.RTACallGraphKey

/**
* `CallGraphCommandExternalParser` is a parser for selecting a call graph analysis type.
* It maps a command-line argument to the corresponding `CallGraphKey`.
*/
object CallGraphCommandExternalParser extends OpalCommandExternalParser[String, CallGraphKey] {
override def parse(arg: String): CallGraphKey = {
arg match {
case "CHA" => CHACallGraphKey
case "PointsTo" => AllocationSiteBasedPointsToCallGraphKey
case "RTA" | null => RTACallGraphKey
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package support
package parser

import java.io.File

import org.opalj.ba.CODE.logContext
import org.opalj.commandlinebase.OpalCommandExternalParser
import org.opalj.log.OPALLogger.error
import org.opalj.log.OPALLogger.info

/**
* `ClassPathCommandExternalParser` parses and validates a class path string.
* It processes the class path provided as a command-line argument, splitting it by the system path
* separator (":" on UNIX, ";" on Windows), and verifies each entry, returning a sequence of valid
* `File` objects.
*/
object ClassPathCommandExternalParser extends OpalCommandExternalParser[String, Seq[File]] {
override def parse(arg: String): Seq[File] = {
var cp = IndexedSeq.empty[String]

cp = arg.substring(arg.indexOf('=') + 1).split(File.pathSeparator).toIndexedSeq

if (cp.isEmpty) cp = IndexedSeq(System.getProperty("user.dir"))

info("project configuration", s"the classpath is ${cp.mkString}")
verifyFiles(cp)
}

private def verifyFiles(filenames: IndexedSeq[String]): Seq[File] = filenames.flatMap(verifyFile)

private def verifyFile(filename: String): Option[File] = {
val file = new File(filename)

def workingDirectory: String = {
s"(working directory: ${System.getProperty("user.dir")})"
}

if (!file.exists) {
showError(s"File does not exist: $file $workingDirectory.")
None
} else if (!file.canRead) {
showError(s"Cannot read: $file $workingDirectory.")
None
} else if (!file.isDirectory &&
!filename.endsWith(".jar") &&
!filename.endsWith(".ear") &&
!filename.endsWith(".war") &&
!filename.endsWith(".zip") &&
!filename.endsWith(".jmod") &&
!filename.endsWith(".class")
) {
showError(s"Input file is neither a directory nor a class or JAR/JMod file: $file.")
None
} else
Some(file)
}

private def showError(message: String): Unit = error("project configuration", message)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package support
package parser

import org.opalj.ai.Domain
import org.opalj.ai.domain.RecordDefUse
import org.opalj.ai.domain.l2.DefaultPerformInvocationsDomainWithCFGAndDefUse
import org.opalj.commandlinebase.OpalCommandExternalParser

/**
* `DomainCommandExternalParser` is a parser used to resolve and load a specified domain class.
* It maps a command-line argument to a `Class` type that implements `Domain` and `RecordDefUse`, enabling dynamic
* selection of a domain type for AI analysis configurations.
*/

object DomainCommandExternalParser
extends OpalCommandExternalParser[
String,
Class[_ >: DefaultPerformInvocationsDomainWithCFGAndDefUse[_] <: Domain with RecordDefUse]
] {
override def parse(
arg: String
): Class[_ >: DefaultPerformInvocationsDomainWithCFGAndDefUse[_] <: Domain with RecordDefUse] = {
if (arg == null)
classOf[DefaultPerformInvocationsDomainWithCFGAndDefUse[_]]
else Class.forName(arg).asInstanceOf[Class[Domain with RecordDefUse]]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package support
package parser

import org.opalj.commandlinebase.OpalCommandExternalParser
import org.opalj.tac.fpcf.analyses.purity.DomainSpecificRater
import org.opalj.tac.fpcf.analyses.purity.SystemOutLoggingAllExceptionRater

/**
* `RaterCommandExternalParser` is a parser responsible for resolving and loading a specified `DomainSpecificRater`.
* It interprets a command-line argument to load a rater class.
*/
object RaterCommandExternalParser extends OpalCommandExternalParser[String, DomainSpecificRater] {
override def parse(arg: String): DomainSpecificRater = {
if (arg == null) SystemOutLoggingAllExceptionRater
else {
import scala.reflect.runtime.universe.runtimeMirror
val mirror = runtimeMirror(getClass.getClassLoader)
val module = mirror.staticModule(arg)
mirror.reflectModule(module).instance.asInstanceOf[DomainSpecificRater]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.opalj.commandlinebase

object AnalysisCommand {
var name = "analysis"

private object AnalysisLevelCommand extends OpalChoiceCommand {
override var name: String = "analysis level"
override var argName: String = "level"
override var description: String = "<L0|L1|L2> (Default: L2, the most precise analysis configuration)"
override var defaultValue: Option[String] = Some("L2")
override var noshort: Boolean = true
override var choices: Seq[String] = Seq("L0", "L1", "L2")

override def parse[T](arg: T): Any = null
}

private object RunnerCommand extends OpalPlainCommand[String] {
override var name: String = "runner name"
override var argName: String = "runner"
override var description: String = "The name of the runner, for which some analyses should be set up"
override var defaultValue: Option[String] = None
override var noshort: Boolean = true

override def parse[T](arg: T): Any = null
}

def getRunnerCommand(): OpalPlainCommand[String] = RunnerCommand

def getAnalysisLevelCommand(): OpalChoiceCommand = AnalysisLevelCommand
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package commandlinebase

import java.util.Calendar

object AnalysisNameCommand extends OpalPlainCommand[String] {
override var name: String = "analysis name"
override var argName: String = "analysisName"
override var description: String = "analysisName which defines the analysis within the results file"
override var defaultValue: Option[String] = Some(s"RUN-${Calendar.getInstance().getTime.toString}")
override var noshort: Boolean = true

override def parse[T](arg: T): Any = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package commandlinebase

object CallGraphCommand extends OpalPlainCommand[String] {
override var name: String = "callGraph"
override var argName: String = "callGraph"
override var description: String = "<CHA|RTA|PointsTo> (Default: RTA)"
override var defaultValue: Option[String] = Some("RTA")
override var noshort: Boolean = true

override def parse[T](arg: T): Any = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package commandlinebase

object ClassPathCommand extends OpalPlainCommand[String] {
override var name: String = "classPath"
override var argName: String = "classPath"
override var description: String = "Directories or JAR/class files"
override var defaultValue: Option[String] = Some(System.getProperty("user.dir"))
override var noshort: Boolean = true

override def parse[T](arg: T): Any = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package commandlinebase

object CloseWorldCommand extends OpalPlainCommand[Boolean] {
override var name: String = "closeWordAssumption"
override var argName: String = "cwa"
override var description: String = "uses closed world assumption, i.e. no class can be extended"
override var defaultValue: Option[Boolean] = None
override var noshort: Boolean = true

override def parse[T](arg: T): Any = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package commandlinebase

object DebugCommand extends OpalPlainCommand[Boolean] {
override var name: String = "debug"
override var argName: String = "debug"
override var description: String = "enable debug output from PropertyStore"
override var defaultValue: Option[Boolean] = None
override var noshort: Boolean = true

override def parse[T](arg: T): Any = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package commandlinebase

object DomainCommand extends OpalPlainCommand[String] {
override var name: String = "domain"
override var argName: String = "domain"
override var description: String = "class name of the abstract interpretation domain"
override var defaultValue: Option[String] = None
override var noshort: Boolean = true

override def parse[T](arg: T): Any = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package commandlinebase

object EagerCommand extends OpalPlainCommand[Boolean] {
override var name: String = "eager"
override var argName: String = "eager"
override var description: String = "supporting analyses are executed eagerly"
override var defaultValue: Option[Boolean] = Some(false)
override var noshort: Boolean = true

override def parse[T](arg: T): Any = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package commandlinebase

object EscapeCommand extends OpalChoiceCommand {
override var name: String = "escape"
override var argName: String = "escape"
override var description: String = "<none|L0|L1> (Default: L1, the most precise configuration)"
override var defaultValue: Option[String] = Some("L1")
override var noshort: Boolean = true
override var choices: Seq[String] = Seq("none", "L0", "L1")

override def parse[T](arg: T): Any = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package commandlinebase

import java.io.File

object EvalDirCommand extends OpalPlainCommand[String] {
override var name: String = "evaluation directory"
override var argName: String = "evalDir"
override var description: String = "path to evaluation directory"
override var defaultValue: Option[String] = None
override var noshort: Boolean = true

def parse[T](arg: T): Some[File] = {
val evalDir: String = arg.asInstanceOf[String]
val evaluationDir = Some(new File(evalDir))
if (evaluationDir.isDefined && !evaluationDir.get.exists()) evaluationDir.get.mkdir

evaluationDir
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package commandlinebase

object FieldAssignabilityCommand extends OpalChoiceCommand {
override var name: String = "fieldAssignability"
override var argName: String = "fieldAssignability"
override var description: String = "<none|L0|L1|L2> (Default: Depends on analysis level)"
override var defaultValue: Option[String] = None
override var noshort: Boolean = true
override var choices: Seq[String] = Seq("none", "LO", "L1", "L2")

override def parse[T](arg: T): Any = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package commandlinebase

object IndividualCommand extends OpalPlainCommand[Boolean] {
override var name: String = "individual"
override var argName: String = "individual"
override var description: String = "reports the purity result for each method"
override var defaultValue: Option[Boolean] = None
override var noshort: Boolean = true

override def parse[T](arg: T): Any = null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package commandlinebase

object JDKCommand extends OpalPlainCommand[Boolean] {
override var name: String = "noJDK"
override var argName: String = "noJDK"
override var description: String = "do not analyze any JDK methods"
override var defaultValue: Option[Boolean] = Some(false)
override var noshort: Boolean = true

override def parse[T](arg: T): Any = null
}
Loading
Loading