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
Prev Previous commit
Next Next commit
Improve PR
  • Loading branch information
thanhtung24 committed Nov 3, 2024
commit 97f9c60b4abf276040798c35ef75378437bd180a
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,6 @@ import org.opalj.tac.fpcf.analyses.pointsto.TamiFlexKey
import org.opalj.util.PerformanceEvaluation.time
import org.opalj.util.Seconds

class CallGraphConf(args: Array[String]) {

}

/**
* Computes a call graph and reports its size.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import java.io.File
import java.io.FileOutputStream
import java.io.PrintWriter
import java.util.Calendar

import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigValueFactory
import org.opalj.Commandline_base.commandlines.{AnalysisCommand, AnalysisNameCommand, CallGraphCommand, ClassPathCommand, CloseWorldCommand, DebugCommand, DomainCommand, EagerCommand, EscapeCommand, EvalDirCommand, FieldAssignabilityCommand, IndividualCommand, JDKCommand, LibraryCommand, LibraryDirectoryCommand, MultiProjectsCommand, OpalConf, PackagesCommand, ProjectDirectoryCommand, RaterCommand, SchedulingStrategyCommand, ThreadsNumCommand}
import org.opalj.ai.Domain
import org.opalj.ai.domain.RecordDefUse
import org.opalj.ai.fpcf.properties.AIDomainFactoryKey
Expand Down Expand Up @@ -48,14 +48,40 @@ import org.opalj.br.fpcf.properties.cg.Callers
import org.opalj.br.fpcf.properties.cg.NoCallers
import org.opalj.bytecode.JRELibraryFolder
import org.opalj.collection.immutable.IntTrieSet
import org.opalj.commandlinebase.AnalysisCommand
import org.opalj.commandlinebase.AnalysisNameCommand
import org.opalj.commandlinebase.CallGraphCommand
import org.opalj.commandlinebase.ClassPathCommand
import org.opalj.commandlinebase.CloseWorldCommand
import org.opalj.commandlinebase.DebugCommand
import org.opalj.commandlinebase.DomainCommand
import org.opalj.commandlinebase.EagerCommand
import org.opalj.commandlinebase.EscapeCommand
import org.opalj.commandlinebase.EvalDirCommand
import org.opalj.commandlinebase.FieldAssignabilityCommand
import org.opalj.commandlinebase.IndividualCommand
import org.opalj.commandlinebase.JDKCommand
import org.opalj.commandlinebase.LibraryCommand
import org.opalj.commandlinebase.LibraryDirectoryCommand
import org.opalj.commandlinebase.MultiProjectsCommand
import org.opalj.commandlinebase.OpalConf
import org.opalj.commandlinebase.PackagesCommand
import org.opalj.commandlinebase.ProjectDirectoryCommand
import org.opalj.commandlinebase.RaterCommand
import org.opalj.commandlinebase.SchedulingStrategyCommand
import org.opalj.commandlinebase.ThreadsNumCommand
import org.opalj.fpcf.ComputationSpecification
import org.opalj.fpcf.FinalEP
import org.opalj.fpcf.FinalP
import org.opalj.fpcf.PropertyStore
import org.opalj.fpcf.PropertyStoreContext
import org.opalj.fpcf.seq.PKESequentialPropertyStore
import org.opalj.log.LogContext
import org.opalj.support.parser.{AnalysisCommandExternalParser, CallGraphCommandExternalParser, ClassPathCommandExternalParser, DomainCommandExternalParser, RaterCommandExternalParser}
import org.opalj.support.parser.AnalysisCommandExternalParser
import org.opalj.support.parser.CallGraphCommandExternalParser
import org.opalj.support.parser.ClassPathCommandExternalParser
import org.opalj.support.parser.DomainCommandExternalParser
import org.opalj.support.parser.RaterCommandExternalParser
import org.opalj.tac.cg.CallGraphKey
import org.opalj.tac.fpcf.analyses.LazyFieldImmutabilityAnalysis
import org.opalj.tac.fpcf.analyses.LazyFieldLocalityAnalysis
Expand All @@ -75,6 +101,7 @@ import org.opalj.tac.fpcf.analyses.purity.LazyL1PurityAnalysis
import org.opalj.tac.fpcf.analyses.purity.LazyL2PurityAnalysis
import org.opalj.util.PerformanceEvaluation.time
import org.opalj.util.Seconds

import org.rogach.scallop.ScallopConf

/**
Expand Down Expand Up @@ -111,46 +138,55 @@ class PurityConf(args: Array[String]) extends ScallopConf(args) with OpalConf {
verify()

// Parsed data
var classPathFiles = parseCommandWithExternalParser(classPathCommand, ClassPathCommandExternalParser).getOrElse(null)
var projectDirectory = parseCommandWithInternalParser(projectDirCommand, ProjectDirectoryCommand).getOrElse(null)
var libraryDirectory = parseCommandWithInternalParser(libDirCommand, LibraryDirectoryCommand).getOrElse(null)
var analysisScheduler = parseCommandWithExternalParser(analysisCommand, AnalysisCommandExternalParser).getOrElse(null)
val classPathFiles = parseCommandWithExternalParser(classPathCommand, ClassPathCommandExternalParser).getOrElse(null)
val projectDirectory = parseCommandWithInternalParser(projectDirCommand, ProjectDirectoryCommand).getOrElse(null)
val libraryDirectory = parseCommandWithInternalParser(libDirCommand, LibraryDirectoryCommand).getOrElse(null)
val analysisScheduler =
parseCommandWithExternalParser(analysisCommand, AnalysisCommandExternalParser).getOrElse(null)
var support: Option[List[FPCFAnalysisScheduler]] = None

if (fieldAssignabilityCommand.isDefined && escapeCommand.isDefined && eagerCommand.isDefined && analysisScheduler != null) support = Some(parseArgumentsForSupport(
if (fieldAssignabilityCommand.isDefined && escapeCommand.isDefined && eagerCommand.isDefined && analysisScheduler != null)
support = Some(parseArgumentsForSupport(
analysisCommand.apply(),
fieldAssignabilityCommand.apply(),
escapeCommand.apply(),
eagerCommand.apply(),
analysisScheduler
)) else support = None

var domain = parseCommandWithExternalParser(domainCommand, DomainCommandExternalParser).getOrElse(null)
var rater = parseCommandWithExternalParser(raterCommand, RaterCommandExternalParser).getOrElse(null)
var callGraph = parseCommandWithExternalParser(callGraphCommand, CallGraphCommandExternalParser).getOrElse(null)
var jdk: Boolean = parseCommand(jdkCommand).getOrElse(false)
var individual: Boolean = parseCommand(individualCommand).getOrElse(false)
var closedWorld:Boolean = parseCommand(closedWorldCommand).getOrElse(false)
var library: Boolean = parseCommand(libraryCommand).getOrElse(false)
var debug: Boolean = parseCommand(debugCommand).getOrElse(false)
var multiProjects: Boolean = parseCommand(multiProjectsCommand).getOrElse(false)
var evaluationDir = parseCommandWithInternalParser(evaluationDirCommand, EvalDirCommand).getOrElse(null)
var packages = parseCommandWithInternalParser(packagesCommand, PackagesCommand).getOrElse(null)
var threadsNum: Int = parseCommand(threadsNumCommand).getOrElse(0)
var configurationName = parseCommand(analysisNameCommand).getOrElse(null)
var schedulingStrategy = parseCommand(schedulingStrategyCommand).getOrElse(null)


private def parseArgumentsForSupport(analysis: String, fieldAssignability: String, escape: String, eager: Boolean, analysisScheduler: Any) = {
var support = Nil

if(analysis == "L2") support = List(
LazyFieldImmutabilityAnalysis,
LazyL0CompileTimeConstancyAnalysis,
LazyStaticDataUsageAnalysis,
LazyReturnValueFreshnessAnalysis,
LazyFieldLocalityAnalysis
)
))
else
support = None

val domain = parseCommandWithExternalParser(domainCommand, DomainCommandExternalParser).getOrElse(null)
val rater = parseCommandWithExternalParser(raterCommand, RaterCommandExternalParser).getOrElse(null)
val callGraph = parseCommandWithExternalParser(callGraphCommand, CallGraphCommandExternalParser).getOrElse(null)
val jdk: Boolean = parseCommand(jdkCommand).getOrElse(false)
val individual: Boolean = parseCommand(individualCommand).getOrElse(false)
val closedWorld: Boolean = parseCommand(closedWorldCommand).getOrElse(false)
val library: Boolean = parseCommand(libraryCommand).getOrElse(false)
val debug: Boolean = parseCommand(debugCommand).getOrElse(false)
val multiProjects: Boolean = parseCommand(multiProjectsCommand).getOrElse(false)
val evaluationDir = parseCommandWithInternalParser(evaluationDirCommand, EvalDirCommand).getOrElse(null)
val packages = parseCommandWithInternalParser(packagesCommand, PackagesCommand).getOrElse(null)
val threadsNum: Int = parseCommand(threadsNumCommand).getOrElse(0)
val configurationName = parseCommand(analysisNameCommand).getOrElse(null)
val schedulingStrategy = parseCommand(schedulingStrategyCommand).getOrElse(null)

private def parseArgumentsForSupport(
analysis: String,
fieldAssignability: String,
escape: String,
eager: Boolean,
analysisScheduler: Any
) = {
var support = List()

if (analysis == "L2") support = List(
LazyFieldImmutabilityAnalysis,
LazyL0CompileTimeConstancyAnalysis,
LazyStaticDataUsageAnalysis,
LazyReturnValueFreshnessAnalysis,
LazyFieldLocalityAnalysis
)

if (eager) {
support ::= EagerClassImmutabilityAnalysis
Expand Down Expand Up @@ -192,10 +228,10 @@ class PurityConf(args: Array[String]) extends ScallopConf(args) with OpalConf {
case "none" =>

case null => analysisScheduler match {
case LazyL0PurityAnalysis => support ::= LazyL0FieldAssignabilityAnalysis
case LazyL1PurityAnalysis => support ::= LazyL1FieldAssignabilityAnalysis
case LazyL2PurityAnalysis => support ::= LazyL1FieldAssignabilityAnalysis
}
case LazyL0PurityAnalysis => support ::= LazyL0FieldAssignabilityAnalysis
case LazyL1PurityAnalysis => support ::= LazyL1FieldAssignabilityAnalysis
case LazyL2PurityAnalysis => support ::= LazyL1FieldAssignabilityAnalysis
}

case _ =>
Console.println(s"unknown field assignability analysis: $fieldAssignability")
Expand All @@ -206,7 +242,6 @@ class PurityConf(args: Array[String]) extends ScallopConf(args) with OpalConf {
}
}


/**
* Executes a purity analysis (L2 by default) along with necessary supporting analysis.
*
Expand Down Expand Up @@ -241,7 +276,7 @@ object Purity {
schedulingStrategy: Option[String],
rater: DomainSpecificRater,
callGraphKey: CallGraphKey,
jdk: Boolean,
jdk: Boolean,
individual: Boolean,
numThreads: Int,
closedWorldAssumption: Boolean,
Expand Down Expand Up @@ -579,7 +614,7 @@ object Purity {

val purityConf = new PurityConf(args)

if(args.contains("--help") || args.contains("-h")) {
if (args.contains("--help") || args.contains("-h")) {
return
}

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

import org.opalj.Commandline_base.commandlines.OpalCommandExternalParser
import org.opalj.br.fpcf.FPCFLazyAnalysisScheduler
import org.opalj.br.fpcf.analyses.LazyL0PurityAnalysis
import org.opalj.tac.fpcf.analyses.purity.{LazyL1PurityAnalysis, LazyL2PurityAnalysis}
import org.opalj.commandlinebase.OpalCommandExternalParser
import org.opalj.tac.fpcf.analyses.purity.LazyL1PurityAnalysis
import org.opalj.tac.fpcf.analyses.purity.LazyL2PurityAnalysis

/**
* `AnalysisCommandExternalParser` interprets a command-line argument specifying a purity analysis level
* and returns the corresponding `FPCFLazyAnalysisScheduler`.
*/
object AnalysisCommandExternalParser extends OpalCommandExternalParser[FPCFLazyAnalysisScheduler]{
override def parse[T](arg: T): FPCFLazyAnalysisScheduler = {
val analysisName = arg.asInstanceOf[String]

analysisName match {
case "L0" => LazyL0PurityAnalysis
case "L1" => LazyL1PurityAnalysis
object AnalysisCommandExternalParser extends OpalCommandExternalParser[String, FPCFLazyAnalysisScheduler] {
override def parse(arg: String): FPCFLazyAnalysisScheduler = {
arg match {
case "L0" => LazyL0PurityAnalysis
case "L1" => LazyL1PurityAnalysis
case null | "L2" => LazyL2PurityAnalysis
case _ => throw new IllegalArgumentException(s"Unknown analysis: $analysisName")
case _ => throw new IllegalArgumentException(s"Unknown analysis: $arg")
}
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
package org.opalj.support.parser
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package support
package parser

import org.opalj.Commandline_base.commandlines.OpalCommandExternalParser
import org.opalj.tac.cg.{AllocationSiteBasedPointsToCallGraphKey, CHACallGraphKey, CallGraphKey, RTACallGraphKey}
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[CallGraphKey]{
override def parse[T](arg: T): CallGraphKey = {
val callGraph = arg.asInstanceOf[String]

callGraph match {
case "CHA" => CHACallGraphKey
case "PointsTo" => AllocationSiteBasedPointsToCallGraphKey
object CallGraphCommandExternalParser extends OpalCommandExternalParser[String, CallGraphKey] {
override def parse(arg: String): CallGraphKey = {
arg match {
case "CHA" => CHACallGraphKey
case "PointsTo" => AllocationSiteBasedPointsToCallGraphKey
case "RTA" | null => RTACallGraphKey
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
package org.opalj.support.parser

import org.opalj.Commandline_base.commandlines.OpalCommandExternalParser
import org.opalj.log.OPALLogger.{error, info}
import org.opalj.ba.CODE.logContext
/* 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[Seq[File]]{
override def parse[T](arg: T): Seq[File] = {
val classPath = arg.asInstanceOf[String]
object ClassPathCommandExternalParser extends OpalCommandExternalParser[String, Seq[File]] {
override def parse(arg: String): Seq[File] = {
var cp = IndexedSeq.empty[String]
import scala.collection.immutable.ArraySeq

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

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

info("project configuration", s"the classpath is ${cp.mkString}")
verifyFiles(cp)
Expand All @@ -44,12 +43,12 @@ object ClassPathCommandExternalParser extends OpalCommandExternalParser[Seq[File
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")
!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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
package org.opalj.support.parser
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package support
package parser

import org.opalj.Commandline_base.commandlines.OpalCommandExternalParser
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[Class[_ >: DefaultPerformInvocationsDomainWithCFGAndDefUse[_] <: Domain with RecordDefUse]] {
override def parse[T](arg: T) : Class[_ >: DefaultPerformInvocationsDomainWithCFGAndDefUse[_] <: Domain with RecordDefUse] = {
val domain = arg.asInstanceOf[String]
if (domain == null)
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(domain).asInstanceOf[Class[Domain with RecordDefUse]]
else Class.forName(arg).asInstanceOf[Class[Domain with RecordDefUse]]
}
}
Loading