From 497b2c5772c8667d01059a5135122e0f67ea7a64 Mon Sep 17 00:00:00 2001 From: Dominik Helm Date: Thu, 18 Sep 2025 16:21:47 +0200 Subject: [PATCH] Add documentation for Project --- ...ynamicConstantsBytecodeStructureTest.scala | 5 +- ...ynamicRewritingBytecodeStructureTest.scala | 9 +- .../org/opalj/apk/parser/ApkParser.scala | 7 +- .../org/opalj/bi/reader/ClassFileReader.scala | 8 +- .../reader/InvokedynamicRewritingTest.scala | 2 +- .../scala/org/opalj/br/analyses/Project.scala | 441 +++++++++++++----- .../test/scala/org/opalj/br/TestSupport.scala | 3 +- .../instructions/ClassFileFactoryTest.scala | 3 +- 8 files changed, 340 insertions(+), 138 deletions(-) diff --git a/DEVELOPING_OPAL/validate/src/it/scala/org/opalj/br/reader/DynamicConstantsBytecodeStructureTest.scala b/DEVELOPING_OPAL/validate/src/it/scala/org/opalj/br/reader/DynamicConstantsBytecodeStructureTest.scala index 23d0f00bc8..80d5cacf29 100644 --- a/DEVELOPING_OPAL/validate/src/it/scala/org/opalj/br/reader/DynamicConstantsBytecodeStructureTest.scala +++ b/DEVELOPING_OPAL/validate/src/it/scala/org/opalj/br/reader/DynamicConstantsBytecodeStructureTest.scala @@ -16,7 +16,6 @@ import org.opalj.bi.TestResources.locateTestResources import org.opalj.br.analyses.Project import org.opalj.br.instructions.LDCDynamic import org.opalj.br.instructions.WIDE -import org.opalj.log.GlobalLogContext /** * Test that code with dynamic constants is loaded without exceptions and after rewriting is still @@ -76,7 +75,7 @@ class DynamicConstantsBytecodeStructureTest extends AnyFunSpec with Matchers { rewrite = false, logRewrites = false ) - val project = Project(dynamicConstantsJar, GlobalLogContext, config) + val project = Project(dynamicConstantsJar, config) info(project.statistics.toList.map(_.toString).filter(_.startsWith("(Project")).mkString(",")) it("should be able to perform abstract interpretation of rewritten dynamic constants " + @@ -90,7 +89,7 @@ class DynamicConstantsBytecodeStructureTest extends AnyFunSpec with Matchers { rewrite = true, logRewrites = false ) - val project = Project(dynamicConstantsJar, GlobalLogContext, config) + val project = Project(dynamicConstantsJar, config) info(project.statistics.toList.map(_.toString).filter(_.startsWith("(Project")).mkString(",")) it("should be able to rewrite all dynamic constants in the dynamic constants test " + diff --git a/DEVELOPING_OPAL/validate/src/it/scala/org/opalj/br/reader/InvokedynamicRewritingBytecodeStructureTest.scala b/DEVELOPING_OPAL/validate/src/it/scala/org/opalj/br/reader/InvokedynamicRewritingBytecodeStructureTest.scala index dfe555ba36..5fb12f777a 100644 --- a/DEVELOPING_OPAL/validate/src/it/scala/org/opalj/br/reader/InvokedynamicRewritingBytecodeStructureTest.scala +++ b/DEVELOPING_OPAL/validate/src/it/scala/org/opalj/br/reader/InvokedynamicRewritingBytecodeStructureTest.scala @@ -24,7 +24,6 @@ import org.opalj.br.instructions.INVOKESTATIC import org.opalj.br.instructions.WIDE import org.opalj.br.reader.InvokedynamicRewriting.LambdaNameRegEx import org.opalj.br.reader.InvokedynamicRewriting.TargetMethodNameRegEx -import org.opalj.log.GlobalLogContext /** * Test that code with rewritten `invokedynamic` instructions is still valid bytecode. @@ -116,7 +115,7 @@ class InvokedynamicRewritingBytecodeStructureTest extends AnyFunSpec with Matche rewrite = true, logRewrites = false ).withValue(DeleteSynthesizedClassFilesAttributesConfigKey, configValueFalse) - val lambdas = Project(lambdasJar, GlobalLogContext, config) + val lambdas = Project(lambdasJar, config) info(lambdas.statistics.toList.map(_.toString).filter(_.startsWith("(Project")).mkString(",")) it("should be able to perform abstract interpretation of rewritten Java lambda" + @@ -135,7 +134,7 @@ class InvokedynamicRewritingBytecodeStructureTest extends AnyFunSpec with Matche rewrite = true, logRewrites = false ).withValue(DeleteSynthesizedClassFilesAttributesConfigKey, configValueFalse) - val stringConcat = Project(stringConcatJar, GlobalLogContext, config) + val stringConcat = Project(stringConcatJar, config) info(stringConcat.statistics.toList.map(_.toString).filter(_.startsWith("(Project")).mkString(",")) it("should be able to perform abstract interpretation of rewritten Java string concat" + @@ -154,7 +153,7 @@ class InvokedynamicRewritingBytecodeStructureTest extends AnyFunSpec with Matche rewrite = true, logRewrites = false ).withValue(DeleteSynthesizedClassFilesAttributesConfigKey, configValueFalse) - val records = Project(recordsJar, GlobalLogContext, config) + val records = Project(recordsJar, config) info(records.statistics.toList.map(_.toString).filter(_.startsWith("(Project")).mkString(",")) it("should be able to perform abstract interpretation of rewritten Java 16 record" + @@ -173,7 +172,7 @@ class InvokedynamicRewritingBytecodeStructureTest extends AnyFunSpec with Matche rewrite = true, logRewrites = false ).withValue(DeleteSynthesizedClassFilesAttributesConfigKey, configValueFalse) - val jre = Project(jrePath, GlobalLogContext, config) + val jre = Project(jrePath, config) info(jre.statistics.toList.map(_.toString).filter(_.startsWith("(Project")).mkString(",")) it("should be able to perform abstract interpretation of rewritten Java lambda " + "expressions in the JRE") { diff --git a/OPAL/apk/src/main/scala/org/opalj/apk/parser/ApkParser.scala b/OPAL/apk/src/main/scala/org/opalj/apk/parser/ApkParser.scala index 93c5ebf72c..0e1959852d 100644 --- a/OPAL/apk/src/main/scala/org/opalj/apk/parser/ApkParser.scala +++ b/OPAL/apk/src/main/scala/org/opalj/apk/parser/ApkParser.scala @@ -254,12 +254,7 @@ object ApkParser { val jarDir = apkParser.parseDexCode(dexParser)._1 - val project = - Project( - jarDir.toFile, - GlobalLogContext, - projectConfig - ) + val project = Project(jarDir.toFile, projectConfig) project.updateProjectInformationKeyInitializationData(ApkComponentsKey)(_ => apkParser) project.get(ApkComponentsKey) diff --git a/OPAL/bi/src/main/scala/org/opalj/bi/reader/ClassFileReader.scala b/OPAL/bi/src/main/scala/org/opalj/bi/reader/ClassFileReader.scala index 65ec5c5b47..1807d82b15 100644 --- a/OPAL/bi/src/main/scala/org/opalj/bi/reader/ClassFileReader.scala +++ b/OPAL/bi/src/main/scala/org/opalj/bi/reader/ClassFileReader.scala @@ -597,12 +597,12 @@ trait ClassFileReader extends ClassFileReaderConfiguration with Constant_PoolAbs /** * Loads class files from the given file location. * - If the file denotes a single ".class" file this class file is loaded. - * - If the file object denotes a ".jar|.war|.ear|.zip" file, all class files in the - * jar file will be loaded. + * - If the file object denotes a ".jmod|.jar|.war|.ear|.zip" file, all class files in the + * archive file will be loaded. * - If the file object specifies a directory object, all ".class" files * in the directory and in all subdirectories are loaded as well as all - * class files stored in ".jar" files in one of the directories. This class loads - * all class files in parallel. However, this does not effect analyses working on the + * class files stored in archive files in one of the directories. This class loads + * all class files in parallel. However, this does not affect analyses working on the * resulting `List`. */ def ClassFiles( diff --git a/OPAL/br/src/it/scala/org/opalj/br/reader/InvokedynamicRewritingTest.scala b/OPAL/br/src/it/scala/org/opalj/br/reader/InvokedynamicRewritingTest.scala index 804164f4ab..a46e138240 100644 --- a/OPAL/br/src/it/scala/org/opalj/br/reader/InvokedynamicRewritingTest.scala +++ b/OPAL/br/src/it/scala/org/opalj/br/reader/InvokedynamicRewritingTest.scala @@ -77,7 +77,7 @@ abstract class InvokedynamicRewritingTest extends AnyFunSuite { val logContext = new StandardLogContext OPALLogger.register(logContext) - val project = Project(libraryPath, logContext, config) + val project = Project(libraryPath, config, logContext) val proxyFactoryCalls = this.proxyFactoryCalls(project) assert(proxyFactoryCalls.nonEmpty, "there should be calls to the proxy factories") diff --git a/OPAL/br/src/main/scala/org/opalj/br/analyses/Project.scala b/OPAL/br/src/main/scala/org/opalj/br/analyses/Project.scala index 79b7a4f519..bdb5053ae0 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/analyses/Project.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/analyses/Project.scala @@ -87,74 +87,95 @@ import org.opalj.util.PerformanceEvaluation.time * tools such as IDEs. E.g., in Eclipse `IResource`'s are used to identify the * location of a resource (e.g., a source or class file.) * - * @param logContext The logging context associated with this project. Using the logging - * context after the project is no longer referenced (garbage collected) is not - * possible. + * @param all[Project|Library]ClassFiles The class files associated with this project, defined in the analyzed project + * itself and/or in libraries. * - * @param classFilesCount The number of classes (including inner and annoymous classes as - * well as interfaces, annotations, etc.) defined in libraries and in - * the analyzed project. + * @param libraryClassFilesAreInterfacesOnly If `true`, only the public interfaces of the methods of the library's + * classes are available; if `false` all methods are reified including bodies. * - * @param methodsCount The number of methods defined in libraries and in the analyzed project. + * @param allMethods All methods defined by this project as well as the visible methods defined by the libraries. * - * @param fieldsCount The number of fields defined in libraries and in the analyzed project. + * @param allFields All fields defined by this project as well as the reified fields defined in libraries. * - * @param allMethods All methods defined by this project as well as the visible methods - * defined by the libraries. - * @param allFields All fields defined by this project as well as the reified fields defined - * in libraries. - * @param allSourceElements `Iterable` over all source elements of the project. The set of all - * source elements consists of (in this order): all methods + all fields - * + all class files. + * @param allSourceElements `Iterable` over all source elements of the project. The set of all source elements consists + * of (in this order): all methods + all fields + all class files. * - * @param libraryClassFilesAreInterfacesOnly If `true` then only the public interfaces - * of the methods of the library's classes are available; if `false` all methods and - * method bodies are reified. + * @param [project|library]classFilesCount The number of classes (including inner and anonymous classes as well as + * interfaces, annotations, etc.) defined in the analyzed project and/or in libraries. + * + * @param [project|library]methodsCount The number of methods defined in the analyzed project and/or in libraries. + * + * @param [project|library]fieldsCount The number of fields defined in the analyzed project and/or in libraries. + * + * @param codeSize The number of bytes used for method bodies for all reified methods. + * + * @param classHierarchy The class hierarchy of the analyzed project and libraries, including super-/subtype information + * and information on interface implementations. + * + * @param virtualMethodsCount The number of virtual methods (i.e., non-static, instance methods) defined in the analyzed + * project and libraries. + * + * @param instanceMethods All instance methods available on a ClassType, including methods inherited from superclasses + * and default methods from inherited interfaces. + * + * @param overridingMethods All non-abstract methods overriding a given method, including the method itself. + * + * @param nests For all nest members, the respective nest host (see JVM specification, §5.4.4) + * + * @param [Method|Var]HandleSubtypes All subtypes of the class MethodHandle resp. VarHandle + * (cf. JVM specification, §2.9.3) which are defined in the analyzed project and libraries. + * + * @param config The configuration used for this project. Configuration includes transformations to be applied to the + * analyzed code, algorithms used for scheduling analyses, analysis specific configuration and + * pre-configured results. + * + * @param logContext The logging context associated with this project. Using the logging context after the project is no + * longer referenced (garbage collected) is not possible. * * @author Michael Eichberg * @author Marco Torsello */ class Project[Source] private ( - private[this] val projectModules: Map[String, ModuleDefinition[Source]], // just contains "module-info" class files - private[this] val projectClassFiles: Array[ClassFile], // contains no "module-info" class files - private[this] val libraryModules: Map[String, ModuleDefinition[Source]], // just contains "module-info" class files - private[this] val libraryClassFiles: Array[ClassFile], - final val libraryClassFilesAreInterfacesOnly: Boolean, + private[this] val projectClassFiles: Array[ClassFile], // class files of the analyzed projec, contains no "module-info" class files + private[this] val projectModules: Map[String, ModuleDefinition[Source]], // just contains "module-info" class files of the analyzed project + private[this] val libraryClassFiles: Array[ClassFile], // class files of the analyzed projec, contains no "module-info" class files + private[this] val libraryModules: Map[String, ModuleDefinition[Source]], // just contains "module-info" class files defined in libraries private[this] val methodsWithBody: Array[Method], // methods with bodies sorted by size private[this] val methodsWithBodyAndContext: Array[MethodInfo[Source]], // the concrete methods, sorted by size in descending order private[this] val projectTypes: Set[ClassType], // the types defined by the class files belonging to the project's code - private[this] val classTypeToClassFile: Map[ClassType, ClassFile], - private[this] val sources: Map[ClassType, Source], - final val projectClassFilesCount: Int, - final val projectMethodsCount: Int, - final val projectFieldsCount: Int, - final val libraryClassFilesCount: Int, - final val libraryMethodsCount: Int, - final val libraryFieldsCount: Int, - final val codeSize: Long, - final val MethodHandleSubtypes: Set[ClassType], - final val VarHandleSubtypes: Set[ClassType], - final val classFilesCount: Int, - final val methodsCount: Int, - final val fieldsCount: Int, + private[this] val classTypeToClassFile: Map[ClassType, ClassFile], // mapping of types to the class files defining them + private[this] val sources: Map[ClassType, Source], // mapping of class files to their respective sources (usually URL to the defining class file) + final val allClassFiles: Iterable[ClassFile], final val allProjectClassFiles: ArraySeq[ClassFile], final val allLibraryClassFiles: ArraySeq[ClassFile], - final val allClassFiles: Iterable[ClassFile], + final val libraryClassFilesAreInterfacesOnly: Boolean, final val allMethods: Iterable[Method], final val allFields: Iterable[Field], final val allSourceElements: Iterable[SourceElement], - final val virtualMethodsCount: Int, + final val classFilesCount: Int, + final val projectClassFilesCount: Int, + final val libraryClassFilesCount: Int, + final val methodsCount: Int, + final val projectMethodsCount: Int, + final val libraryMethodsCount: Int, + final val fieldsCount: Int, + final val projectFieldsCount: Int, + final val libraryFieldsCount: Int, + final val codeSize: Long, final val classHierarchy: ClassHierarchy, + final val virtualMethodsCount: Int, final val instanceMethods: Map[ClassType, ArraySeq[MethodDeclarationContext]], final val overridingMethods: Map[Method, immutable.Set[Method]], final val nests: Map[ClassType, ClassType], + final val MethodHandleSubtypes: Set[ClassType], + final val VarHandleSubtypes: Set[ClassType], // Note that the referenced array will never shrink! @volatile protected[this] var projectInformation: AtomicReferenceArray[AnyRef] = new AtomicReferenceArray[AnyRef](32) )( implicit - final val logContext: LogContext, - final val config: Config + final val config: Config, + final val logContext: LogContext ) extends si.Project with ProjectLike { /** @@ -183,43 +204,43 @@ class Project[Source] private ( val newLogContext = logContext.successor val newClassHierarchy = classHierarchy.updatedLogContext(newLogContext) new Project( - projectModules, projectClassFiles, - libraryModules, + projectModules, libraryClassFiles, - libraryClassFilesAreInterfacesOnly, + libraryModules, methodsWithBody, methodsWithBodyAndContext, projectTypes, classTypeToClassFile, sources, - projectClassFilesCount, - projectMethodsCount, - projectFieldsCount, - libraryClassFilesCount, - libraryMethodsCount, - libraryFieldsCount, - codeSize, - MethodHandleSubtypes, - VarHandleSubtypes, - classFilesCount, - methodsCount, - fieldsCount, + allClassFiles, allProjectClassFiles, allLibraryClassFiles, - allClassFiles, + libraryClassFilesAreInterfacesOnly, allMethods, allFields, allSourceElements, - virtualMethodsCount, + classFilesCount, + projectClassFilesCount, + libraryClassFilesCount, + methodsCount, + projectMethodsCount, + libraryMethodsCount, + fieldsCount, + projectFieldsCount, + libraryFieldsCount, + codeSize, newClassHierarchy, + virtualMethodsCount, instanceMethods, overridingMethods, nests, + MethodHandleSubtypes, + VarHandleSubtypes, newProjectInformation )( - newLogContext, - config + config, + newLogContext ) } @@ -237,8 +258,15 @@ class Project[Source] private ( final val VarHandleClassFile: Option[ClassFile] = classFile(ClassType.VarHandle) + /** + * All methods in the analyzed project and libraries that have method bodies. + */ final val allMethodsWithBody: ArraySeq[Method] = ArraySeq.unsafeWrapArray(this.methodsWithBody) + /** + * All methods in the analyzed project and libraries that have method bodies, including the source they originate + * from. + */ final val allMethodsWithBodyWithContext: ArraySeq[MethodInfo[Source]] = { ArraySeq.unsafeWrapArray(this.methodsWithBodyAndContext) } @@ -442,15 +470,18 @@ class Project[Source] private ( \* ------------------------------------------------------------------------------------------ */ /** - * Creates a new `Project` which also includes the given class files. + * Creates a new `Project` consisting of the previous project and additional already processed class files. + * + * @param projectClassFilesWithSources The list of additional class files of this project that are considered + * to belong to the application/library that will be analyzed. */ def extend(projectClassFilesWithSources: Iterable[(ClassFile, Source)]): Project[Source] = { Project.extend[Source](this, projectClassFilesWithSources) } /** - * Creates a new `Project` which also includes this as well as the other project's - * class files. + * Creates a new `Project` which includes the class files from this as well as the other project and uses the + * configuration and logging of this project. */ def extend(other: Project[Source]): Project[Source] = { if (this.libraryClassFilesAreInterfacesOnly != other.libraryClassFilesAreInterfacesOnly) { @@ -463,7 +494,7 @@ class Project[Source] private ( } /** - * The number of all source elements (fields, methods and class files). + * The number of all source elements (fields, methods, and class files). */ def sourceElementsCount: Int = fieldsCount + methodsCount + classFilesCount @@ -480,29 +511,38 @@ class Project[Source] private ( parForeachArrayElement(classFiles, NumberOfThreadsForCPUBoundTasks, isInterrupted)(f) } - def parForeachProjectClassFile[T]( + /** + * Performs function `f` in parallel on each class file from the analyzed project and libraries. + */ + def parForeachClassFile[T]( isInterrupted: () => Boolean = defaultIsInterrupted )( f: ClassFile => T ): Unit = { - doParForeachClassFile(this.projectClassFiles, isInterrupted)(f) + parForeachProjectClassFile(isInterrupted)(f) + parForeachLibraryClassFile(isInterrupted)(f) } - def parForeachLibraryClassFile[T]( + /** + * Performs function `f` in parallel on each class file from the analyzed project (excluding library class files). + */ + def parForeachProjectClassFile[T]( isInterrupted: () => Boolean = defaultIsInterrupted )( f: ClassFile => T ): Unit = { - doParForeachClassFile(this.libraryClassFiles, isInterrupted)(f) + doParForeachClassFile(this.projectClassFiles, isInterrupted)(f) } - def parForeachClassFile[T]( + /** + * Performs function `f` in parallel on each class file from the analyzed libraries. + */ + def parForeachLibraryClassFile[T]( isInterrupted: () => Boolean = defaultIsInterrupted )( f: ClassFile => T ): Unit = { - parForeachProjectClassFile(isInterrupted)(f) - parForeachLibraryClassFile(isInterrupted)(f) + doParForeachClassFile(this.libraryClassFiles, isInterrupted)(f) } /** @@ -571,8 +611,8 @@ class Project[Source] private ( } /** - * Iterates over all methods in parallel; actually, the methods belonging to a specific class - * are analyzed sequentially.. + * Iterates over all methods in parallel; actually, the methods belonging to a specific class are processed + * sequentially. */ def parForeachMethod[T]( isInterrupted: () => Boolean = defaultIsInterrupted @@ -659,17 +699,31 @@ class Project[Source] private ( groups } + /** + * All class files of the analyzed project and libraries, together with their sources. + */ + def classFilesWithSources: Iterable[(ClassFile, Source)] = { + projectClassFilesWithSources ++ libraryClassFilesWithSources + } + + /** + * All class files of the analyzed project (excluding library class files), together with their sources. + */ def projectClassFilesWithSources: Iterable[(ClassFile, Source)] = { projectClassFiles.view.map { classFile => (classFile, sources(classFile.thisType)) } } + /** + * All class files of the analyzed libraries, together with their sources. + */ def libraryClassFilesWithSources: Iterable[(ClassFile, Source)] = { libraryClassFiles.view.map { classFile => (classFile, sources(classFile.thisType)) } } - def classFilesWithSources: Iterable[(ClassFile, Source)] = { - projectClassFilesWithSources ++ libraryClassFilesWithSources - } + /** + * Returns `true` iff the given type belongs to the project and not to a library. + */ + def isProjectType(classType: ClassType): Boolean = projectTypes.contains(classType) /** * Returns `true` if the given class file belongs to the library part of the project. @@ -686,32 +740,27 @@ class Project[Source] private ( def isLibraryType(classType: ClassType): Boolean = !projectTypes.contains(classType) /** - * Returns `true` iff the given type belongs to the project and not to a library. + * Returns the source (for example, a `File` object or `URL` object) from which + * the class file was loaded that defines the given class type, if any. */ - def isProjectType(classType: ClassType): Boolean = projectTypes.contains(classType) + def source(classType: ClassType): Option[Source] = sources.get(classType) /** * Returns the source (for example, a `File` object or `URL` object) from which - * the class file was loaded that defines the given class type, if any. - * - * @param classType Some class type. + * the class file was loaded. */ - def source(classType: ClassType): Option[Source] = sources.get(classType) def source(classFile: ClassFile): Option[Source] = source(classFile.thisType) /** * Returns the class file that defines the given `classType`; if any. - * - * @param classType Some class type. */ override def classFile(classType: ClassType): Option[ClassFile] = { classTypeToClassFile.get(classType) } /** - * Returns all available `ClassFile` objects for the given `classTypes` that - * pass the given `filter`. `ClassType`s for which no `ClassFile` is available - * are ignored. + * Returns all available `ClassFile` objects for the given `classTypes` that pass the given `filter`. + * `ClassType`s for which no `ClassFile` is available are ignored. */ def lookupClassFiles( classTypes: Iterable[ClassType] @@ -721,6 +770,9 @@ class Project[Source] private ( classTypes.view.flatMap(classFile) filter (classFileFilter) } + /** + * Determines whether the given ClassType has an instance method of the given name and descriptor. + */ def hasInstanceMethod( receiverType: ClassType, name: String, @@ -904,6 +956,9 @@ class Project[Source] private ( */ object Project { + /** + * The class file reader to use for library class files that should be loaded without method bodies. + */ lazy val JavaLibraryClassFileReader: Java17LibraryFramework.type = Java17LibraryFramework @volatile private[this] var theCache: SoftReference[BytecodeInstructionsCache] = { @@ -923,6 +978,9 @@ object Project { cache } + /** + * The class file reader to use for class files that should be loaded with method bodies. + */ def JavaClassFileReader( implicit theLogContext: LogContext = GlobalLogContext, @@ -1484,20 +1542,55 @@ object Project { // /** - * Given a reference to a class file, jar file or a folder containing jar and class - * files, all class files will be loaded and a project will be returned. + * Creates a `Project` from a class file, jar file, jmod file, or directory containing class-, jar-, and jmod files. * * The global logger will be used for logging messages. + * + * The default configuration (compiled from reference.conf files) will be used for setting up the project. */ def apply(file: File): Project[URL] = { Project.apply(file, OPALLogger.globalLogger()) } + /** + * Creates a `Project` from a class file, jar file, jmod file, or directory containing class-, jar-, and jmod files. + * + * The specified logger will be used for logging messages. + * + * The default configuration (compiled from reference.conf files) will be used for setting up the project. + */ def apply(file: File, projectLogger: OPALLogger): Project[URL] = { apply(JavaClassFileReader().ClassFiles(file), projectLogger = projectLogger) } - def apply(file: File, logContext: LogContext, config: Config): Project[URL] = { + /** + * Creates a `Project` from a class file, jar file, jmod file, or directory containing class-, jar-, and jmod files. + * + * The global log context will be used for logging messages. + * + * The given configuration will be used for setting up the project. + */ + def apply(file: File, config: Config): Project[URL] = { + val reader = JavaClassFileReader(GlobalLogContext, config) + this( + projectClassFilesWithSources = reader.ClassFiles(file), + libraryClassFilesWithSources = Iterable.empty, + libraryClassFilesAreInterfacesOnly = true, + virtualClassFiles = Iterable.empty, + handleInconsistentProject = defaultHandlerForInconsistentProjects, + config = config, + GlobalLogContext + ) + } + + /** + * Creates a `Project` from a class file, jar file, jmod file, or directory containing class-, jar-, and jmod files. + * + * The specified log context will be used for logging messages. + * + * The given configuration will be used for setting up the project. + */ + def apply(file: File, config: Config, logContext: LogContext): Project[URL] = { val reader = JavaClassFileReader(logContext, config) this( projectClassFilesWithSources = reader.ClassFiles(file), @@ -1510,11 +1603,21 @@ object Project { ) } + /** + * Creates a `Project` from several class files, jar files, jmod files, or directories containing class-, jar-, and + * jmod files. + * For files in specified as libary files, method bodies will not be loaded, only the methods' interfaces will be + * available. + * + * The given configuration will be used for setting up the project. + * + * The specified log context will be used for logging messages. + */ def apply( projectFiles: Array[File], libraryFiles: Array[File], - logContext: LogContext, - config: Config + config: Config, + logContext: LogContext ): Project[URL] = { this( JavaClassFileReader(logContext, config).AllClassFiles(projectFiles), @@ -1527,6 +1630,17 @@ object Project { ) } + /** + * Creates a `Project` from already processed class files. + * + * The global logger will be used for logging messages. + * + * The default configuration (compiled from reference.conf files) will be used for setting up the project. + * + * @param projectClassFilesWithSources The list of class files of this project that are considered + * to belong to the application/library that will be analyzed. + * [Thread Safety] The underlying data structure has to support concurrent access. + */ def apply[Source]( projectClassFilesWithSources: Iterable[(ClassFile, Source)] ): Project[Source] = { @@ -1536,6 +1650,17 @@ object Project { ) } + /** + * Creates a `Project` from already processed class files. + * + * The specified logger will be used for logging messages. + * + * The default configuration (compiled from reference.conf files) will be used for setting up the project. + * + * @param projectClassFilesWithSources The list of class files of this project that are considered + * to belong to the application/library that will be analyzed. + * [Thread Safety] The underlying data structure has to support concurrent access. + */ def apply[Source]( projectClassFilesWithSources: Iterable[(ClassFile, Source)], projectLogger: OPALLogger @@ -1548,6 +1673,15 @@ object Project { )(projectLogger = projectLogger) } + /** + * Creates a `Project` from a class file, jar file, jmod file, or directory containing class-, jar-, and jmod files. + * For the file specified as libary files, method bodies will not be loaded, only the methods' interfaces will be + * available. + * + * The global log context will be used for logging messages. + * + * The default configuration (compiled from reference.conf files) will be used for setting up the project. + */ def apply( projectFile: File, libraryFile: File @@ -1571,6 +1705,16 @@ object Project { ) } + /** + * Creates a `Project` from several class files, jar files, jmod files, or directories containing class-, jar-, and + * jmod files. + * For files in specified as libary files, method bodies will not be loaded, only the methods' interfaces will be + * available. + * + * The global logger will be used for logging messages. + * + * The default configuration (compiled from reference.conf files) will be used for setting up the project. + */ def apply( projectFiles: Array[File], libraryFiles: Array[File] @@ -1583,13 +1727,19 @@ object Project { ) } + /** + * Creates a new `Project` consisting of the previous Project and an additional class file, jar file, jmod file, or + * directory containing class-, jar-, and jmod files, which is fully loaded including method bodies. + */ def extend(project: Project[URL], file: File): Project[URL] = { project.extend(JavaClassFileReader().ClassFiles(file)) } /** - * Creates a new `Project` that consists of the class files of the previous - * project and the newly given class files. + * Creates a new `Project` consisting of the previous project and additional already processed class files. + * + * @param projectClassFilesWithSources The list of additional class files of this project that are considered + * to belong to the application/library that will be analyzed. */ def extend[Source]( project: Project[Source], @@ -1607,8 +1757,14 @@ object Project { } /** - * Creates a new `Project` that consists of the class files of the previous - * project and the newly given class files. + * Creates a new `Project` consisting of the previous project and additional already processed class files, + * separated into additional project- and library class files. + * + * @param projectClassFilesWithSources The list of additional class files of this project that are considered + * to belong to the application/library that will be analyzed. + * + * @param libraryClassFilesWithSources The list of additional class files of this project that make up + * the libraries used by the project that will be analyzed. */ private def extend[Source]( project: Project[Source], @@ -1624,6 +1780,22 @@ object Project { )(project.config, OPALLogger.logger(project.logContext.successor)) } + /** + * Creates a `Project` from already processed class files. The flag should be set in accordance to whether the files + * given as library class files have been loaded with method bodies or as interfaces only. + * + * The global logger will be used for logging messages. + * + * The default configuration (compiled from reference.conf files) will be used for setting up the project. + * + * @param projectClassFilesWithSources The list of class files of this project that are considered + * to belong to the application/library that will be analyzed. + * [Thread Safety] The underlying data structure has to support concurrent access. + * + * @param libraryClassFilesWithSources The list of class files of this project that make up + * the libraries used by the project that will be analyzed. + * [Thread Safety] The underlying data structure has to support concurrent access. + */ def apply[Source]( projectClassFilesWithSources: Iterable[(ClassFile, Source)], libraryClassFilesWithSources: Iterable[(ClassFile, Source)], @@ -1661,7 +1833,11 @@ object Project { } /** - * Creates a new Project. + * Creates a new `Project` from already processed class files. + * + * The global logger will be used for logging messages. + * + * The default configuration (compiled from reference.conf files) will be used for setting up the project. * * @param projectClassFilesWithSources The list of class files of this project that are considered * to belong to the application/library that will be analyzed. @@ -1715,6 +1891,41 @@ object Project { ) } + /** + * Creates a new `Project` from already processed class files. + * + * The given configuration will be used for setting up the project. + * + * The specified logger will be used for logging messages. + * + * @param projectClassFilesWithSources The list of class files of this project that are considered + * to belong to the application/library that will be analyzed. + * [Thread Safety] The underlying data structure has to support concurrent access. + * + * @param libraryClassFilesWithSources The list of class files of this project that make up + * the libraries used by the project that will be analyzed. + * [Thread Safety] The underlying data structure has to support concurrent access. + * + * @param libraryClassFilesAreInterfacesOnly If `true` then only the non-private interface of + * of the classes belonging to the library was loaded. I.e., this setting just reflects + * the way how the class files were loaded; it does not change the classes! + * + * @param virtualClassFiles A list of virtual class files that have no direct + * representation in the project. + * Such declarations are created, e.g., to handle `invokedynamic` + * instructions. + * '''In general, such class files should be added using + * `projectClassFilesWithSources` and the `Source` should be the file that + * was the reason for the creation of this additional `ClassFile`.''' + * [Thread Safety] The underlying data structure has to support concurrent access. + * + * @param handleInconsistentProject A function that is called back if the project + * is not consistent. The default behavior + * ([[defaultHandlerForInconsistentProjects]]) is to write a warning + * message to the console. Alternatively it is possible to throw the given + * exception to cancel the loading of the project (which is the only + * meaningful option for several advanced analyses.) + */ def apply[Source]( projectClassFilesWithSources: Iterable[(ClassFile, Source)], libraryClassFilesWithSources: Iterable[(ClassFile, Source)], @@ -2001,39 +2212,39 @@ object Project { val allSourceElements: Iterable[SourceElement] = allMethods ++ allFields ++ allClassFiles val project = new Project( - projectModules, projectClassFilesArray, - libraryModules, + projectModules, libraryClassFilesArray, - libraryClassFilesAreInterfacesOnly, + libraryModules, methodsWithBodySortedBySize, methodsWithBodySortedBySizeWithContext, projectTypes, classTypeToClassFile, sources, - projectClassFilesCount, - projectMethodsCount, - projectFieldsCount, - libraryClassFilesCount, - libraryMethodsCount, - libraryFieldsCount, - codeSize, - MethodHandleSubtypes, - VarHandleSubtypes, - classFilesCount, - methodsCount, - fieldsCount, + allClassFiles, allProjectClassFiles, allLibraryClassFiles, - allClassFiles, + libraryClassFilesAreInterfacesOnly, allMethods, allFields, allSourceElements, - virtualMethodsCount, + classFilesCount, + projectClassFilesCount, + libraryClassFilesCount, + methodsCount, + projectMethodsCount, + libraryMethodsCount, + fieldsCount, + projectFieldsCount, + libraryFieldsCount, + codeSize, classHierarchy, + virtualMethodsCount, Await.result(instanceMethodsFuture, Duration.Inf), Await.result(overridingMethodsFuture, Duration.Inf), - nests + nests, + MethodHandleSubtypes, + VarHandleSubtypes ) time { diff --git a/OPAL/br/src/test/scala/org/opalj/br/TestSupport.scala b/OPAL/br/src/test/scala/org/opalj/br/TestSupport.scala index 2991b857aa..1efad8784a 100644 --- a/OPAL/br/src/test/scala/org/opalj/br/TestSupport.scala +++ b/OPAL/br/src/test/scala/org/opalj/br/TestSupport.scala @@ -23,7 +23,6 @@ import org.opalj.br.reader.readJREClassFiles import org.opalj.br.reader.readRTJarClassFiles import org.opalj.bytecode.JavaBase import org.opalj.bytecode.JRELibraryFolder -import org.opalj.log.GlobalLogContext import org.opalj.util.gc /** @@ -116,7 +115,7 @@ object TestSupport { case None => (allBITestJARs().iterator ++ allBITestProjectFolders().iterator) map { biProjectJAR => val readerFactory = - () => Project(biProjectJAR, GlobalLogContext, configForProject(biProjectJAR)) + () => Project(biProjectJAR, configForProject(biProjectJAR)) (biProjectJAR.getName, readerFactory) } } diff --git a/OPAL/br/src/test/scala/org/opalj/br/instructions/ClassFileFactoryTest.scala b/OPAL/br/src/test/scala/org/opalj/br/instructions/ClassFileFactoryTest.scala index ccb0e7d6fa..9dec6ae38c 100644 --- a/OPAL/br/src/test/scala/org/opalj/br/instructions/ClassFileFactoryTest.scala +++ b/OPAL/br/src/test/scala/org/opalj/br/instructions/ClassFileFactoryTest.scala @@ -20,7 +20,6 @@ import org.opalj.br.TestSupport.biProject import org.opalj.br.analyses.Project import org.opalj.br.reader.InvokedynamicRewriting import org.opalj.collection.immutable.UIDSet -import org.opalj.log.GlobalLogContext /** * @author Arne Lottmann @@ -45,7 +44,7 @@ class ClassFileFactoryTest extends AnyFunSpec with Matchers { .withValue(rewritingConfigKey, ConfigValueFactory.fromAnyRef(java.lang.Boolean.FALSE)) .withValue(logRewritingsConfigKey, ConfigValueFactory.fromAnyRef(java.lang.Boolean.TRUE)) - Project(jarFile, GlobalLogContext, config) + Project(jarFile, config) } val StaticMethods = ClassType("proxy/StaticMethods")