From 556fc2b2c3ec28ac00124b7ee1b2f14a1b06f8de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Fri, 29 Nov 2024 16:26:29 +0100 Subject: [PATCH 01/10] Add JDK 11+ initial entrypoints and instantiated types. Mark System streams as instantiated by default --- OPAL/br/src/main/resources/reference.conf | 10 +++++++-- .../cg/xta/InstantiatedTypesAnalysis.scala | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/OPAL/br/src/main/resources/reference.conf b/OPAL/br/src/main/resources/reference.conf index c1dd17039f..003ac3581c 100644 --- a/OPAL/br/src/main/resources/reference.conf +++ b/OPAL/br/src/main/resources/reference.conf @@ -75,7 +75,10 @@ org.opalj { {declaringClass = "java/lang/ClassLoader", name = "checkPackageAccess", descriptor = "(Ljava/lang/Class;Ljava/security/ProtectionDomain;)V"}, {declaringClass = "java/lang/ClassLoader", name = "addClass", descriptor = "(Ljava/lang/Class;)V"}, {declaringClass = "java/lang/ClassLoader", name = "findNative", descriptor = "(Ljava/lang/ClassLoader;Ljava/lang/String;)J"}, - {declaringClass = "java/security/PrivilegedActionException", name = "", descriptor = "(Ljava/lang/Exception;)V"} + {declaringClass = "java/security/PrivilegedActionException", name = "", descriptor = "(Ljava/lang/Exception;)V"}, + {declaringClass = "java/lang/System", name = "initPhase1", descriptor = "()V"}, + {declaringClass = "java/lang/System", name = "initPhase2", descriptor = "(ZZ)I"} + {declaringClass = "java/lang/System", name = "initPhase3", descriptor = "()V"} ] # additional entry points can be specified by adding a respective tuple that must consist of # a class name and a method name and can be refined by also defining a method descriptor. @@ -97,7 +100,10 @@ org.opalj { #analysis = "org.opalj.br.analyses.cg.LibraryInstantiatedTypesFinder" instantiatedTypes = [ - + "java/io/PrintStream", + "java/io/InputStream", + "java/lang/ClassLoader", + "java/lang/Thread" ] } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala index 037fca32cb..c249ed7a11 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala @@ -322,10 +322,32 @@ class InstantiatedTypesAnalysisScheduler( } } + // Marks the field of class java/lang/System with the given name as instantiated by default + // This only works if the RTJar is loaded - otherwise, the initial instantiated types being + // set for 'ExternalWorld' should take care of making these types accessible. + def initializeSystemField(fieldName: String): Unit = { + p.classFile(ObjectType.System).foreach { systemCf => + systemCf.findField(fieldName).foreach { systemField => + if(systemField.fieldType.isReferenceType) { + val declaredField = declaredFields(systemField) + initialize(selectSetEntity(declaredField), UIDSet(systemField.fieldType.asReferenceType)) + } + } + } + } + // Some cooperative analyses originally meant for RTA may require the global type set // to be pre-initialized. Strings and classes can be introduced via constants anywhere. // TODO Only introduce these types to the per-entity type sets where constants are used initialize(p, UIDSet(ObjectType.String, ObjectType.Class)) + initialize(ExternalWorld, initialInstantiatedTypes) + + // During system initialization, some native methods are called to set certain fields in + // the java/lang/System class, namely "PrintStream out", "InputStream in" and "PrintStream + // err" - since we can't detect those native calls, we manually mark them as instantiated. + initializeSystemField("out") + initializeSystemField("in") + initializeSystemField("err") def isRelevantArrayType(rt: Type): Boolean = rt.isArrayType && rt.asArrayType.elementType.isObjectType From 9d29f26197108bb749c3b2f08aa6c4374e30cacf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Fri, 29 Nov 2024 16:59:58 +0100 Subject: [PATCH 02/10] Fix formatting issue --- .../analyses/cg/xta/InstantiatedTypesAnalysis.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala index c249ed7a11..fc8e4a9c13 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala @@ -327,12 +327,12 @@ class InstantiatedTypesAnalysisScheduler( // set for 'ExternalWorld' should take care of making these types accessible. def initializeSystemField(fieldName: String): Unit = { p.classFile(ObjectType.System).foreach { systemCf => - systemCf.findField(fieldName).foreach { systemField => - if(systemField.fieldType.isReferenceType) { - val declaredField = declaredFields(systemField) - initialize(selectSetEntity(declaredField), UIDSet(systemField.fieldType.asReferenceType)) - } - } + systemCf.findField(fieldName).foreach { systemField => + if (systemField.fieldType.isReferenceType) { + val declaredField = declaredFields(systemField) + initialize(selectSetEntity(declaredField), UIDSet(systemField.fieldType.asReferenceType)) + } + } } } From b702a0156031cf284e3898f579f25a6d073f8b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Mon, 2 Dec 2024 17:07:08 +0100 Subject: [PATCH 03/10] Introduce instantiated fields finder and project information key, refactor config so that instantiated fields can be user supplied --- OPAL/br/src/main/resources/reference.conf | 19 ++- .../cg/InitialInstantiatedFieldsKey.scala | 35 ++++ .../cg/InstantiatedFieldsFinder.scala | 155 ++++++++++++++++++ .../cg/rta/InstantiatedTypesAnalysis.scala | 8 +- .../cg/xta/InstantiatedTypesAnalysis.scala | 32 ++-- 5 files changed, 222 insertions(+), 27 deletions(-) create mode 100644 OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala create mode 100644 OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala diff --git a/OPAL/br/src/main/resources/reference.conf b/OPAL/br/src/main/resources/reference.conf index 003ac3581c..a00b54972f 100644 --- a/OPAL/br/src/main/resources/reference.conf +++ b/OPAL/br/src/main/resources/reference.conf @@ -100,12 +100,27 @@ org.opalj { #analysis = "org.opalj.br.analyses.cg.LibraryInstantiatedTypesFinder" instantiatedTypes = [ - "java/io/PrintStream", - "java/io/InputStream", "java/lang/ClassLoader", "java/lang/Thread" ] } + + InitialInstantiatedFieldsKey { + analysis = "org.opalj.br.analyses.cg.DefaultInstantiatedFieldsFinder" + + instantiatedFields = [ + {declaringClass = "java/lang/System", name = "out"}, + {declaringClass = "java/lang/System", name = "in", typeHint = "java/io/BufferedInputStream"}, + {declaringClass = "java/lang/System", name = "err"} + ] + # additional initialized fields can be specified here. Each tuple must contain of a class name and a field + # name - all fields matching this specification will be considered instantiated. By default, only the declared + # type of the field will be considered to be instantiated for the respective field. You can change this + # behavior by adding the optional field "typeHint" to the declaration. This must contain the FQN of the type to + # be considered for the field - if it is not a subtype of the field type, it will be ignored. Similarly to + # the initial types, you can suffix the FQN with a "+" to indicate that all subtypes of the given type + # shall be considered, too. + } } } } diff --git a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala new file mode 100644 index 0000000000..d678a1f929 --- /dev/null +++ b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala @@ -0,0 +1,35 @@ +package org.opalj.br.analyses.cg + +import net.ceedubs.ficus.Ficus._ +import org.opalj.br.{Field, ReferenceType} +import org.opalj.br.analyses.{ProjectInformationKey, ProjectInformationKeys, SomeProject} +import org.opalj.collection.immutable.UIDSet + +object InitialInstantiatedFieldsKey extends ProjectInformationKey[Iterable[(Field, UIDSet[ReferenceType])], Nothing]{ + + final val ConfigKeyPrefix = "org.opalj.br.analyses.cg.InitialInstantiatedFieldsKey." + + override def requirements(project: SomeProject): ProjectInformationKeys = Seq.empty + + override def compute(project: SomeProject): Iterable[(Field, UIDSet[ReferenceType])] = { + val key = ConfigKeyPrefix + "analysis" + val configuredAnalysis = project.config.as[Option[String]](key) + if(configuredAnalysis.isEmpty) { + throw new IllegalArgumentException( + "No InitialInstantiatedFieldsKey configuration available; Instantiated fields cannot be computed!" + ) + } + + val fqn = configuredAnalysis.get + val ifFinder = instantiatedFieldsFinder(fqn) + ifFinder.collectInstantiatedFields(project) + } + + private[this] def instantiatedFieldsFinder(fqn: String): InstantiatedFieldsFinder = { + import scala.reflect.runtime.universe._ + val mirror = runtimeMirror(this.getClass.getClassLoader) + val module = mirror.staticModule(fqn) + mirror.reflectModule(module).instance.asInstanceOf[InstantiatedFieldsFinder] + } + +} diff --git a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala new file mode 100644 index 0000000000..dd6f3d7ac5 --- /dev/null +++ b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala @@ -0,0 +1,155 @@ +package org.opalj.br.analyses.cg + +import net.ceedubs.ficus.Ficus._ +import org.opalj.br.{Field, ObjectType, ReferenceType} +import org.opalj.br.analyses.SomeProject +import org.opalj.collection.immutable.UIDSet +import org.opalj.log.{LogContext, OPALLogger} + +/** + * This trait represents objects that detect fields that should be considered to be instantiated no matter what. This + * can be useful e.g. when fields are natively set by the JVM, but also when dealing with libraries. The functionality + * is similar to the InstantiatedTypesFinder. + * + * @author Johannes Düsing + */ +sealed trait InstantiatedFieldsFinder { + def collectInstantiatedFields(project: SomeProject): Iterable[(Field, UIDSet[ReferenceType])] = Iterable.empty +} + +/** + * The default instantiated fields finder does not consider any fields to be instantiated. + */ +trait DefaultInstantiatedFieldsFinder extends InstantiatedFieldsFinder { + override def collectInstantiatedFields(project: SomeProject): Iterable[(Field, UIDSet[ReferenceType])] = + super.collectInstantiatedFields(project) +} + +/** + * A trait that considers all library fields to be instantiated with their declared type IF library class files are + * interfaces only. A fully sound over-approximation would have to consider all subtypes of each field to possibly + * be instantiated as well - this is not done here to somewhat retain precision, especially for RTA. + */ +trait SoundLibraryInstantiatedFieldsFinder extends InstantiatedFieldsFinder { + + override def collectInstantiatedFields(project: SomeProject): Iterable[(Field, UIDSet[ReferenceType])] = { + if(!project.libraryClassFilesAreInterfacesOnly) + return super.collectInstantiatedFields(project); + + super.collectInstantiatedFields(project) ++ project + .allLibraryClassFiles + .flatMap(_.fields) + .filter(_.fieldType.isReferenceType) + .map{ field => + (field, UIDSet(field.fieldType.asReferenceType)) + } + } + +} + +/** + * A trait that considers fields to be instantiated based on the configuration of OPAL. Users can supply custom + * values to mark fields as instantiated. Per default the field type is considered, but if users provide a + * "typeHint" property, they can specify exact types and / or subtypes to be considered for the given field. + */ +trait ConfigurationInstantiatedFieldsFinder extends InstantiatedFieldsFinder { + + @inline private[this] def additionalInstantiatedFieldsKey: String = { + InitialInstantiatedFieldsKey.ConfigKeyPrefix + "instantiatedFields" + } + + override def collectInstantiatedFields(project: SomeProject): Iterable[(Field, UIDSet[ReferenceType])] = { + import net.ceedubs.ficus.readers.ArbitraryTypeReader._ + + implicit val logContext: LogContext = project.logContext + var instantiatedFields = Set.empty[(Field, UIDSet[ReferenceType])] + + if(!project.config.hasPath(additionalInstantiatedFieldsKey)){ + OPALLogger.info( + "project configuration", + s"configuration key $additionalInstantiatedFieldsKey is missing; " + + "no additional fields are considered instantiated" + ) + return instantiatedFields + } + + val fieldDefinitions = try { + project.config.as[List[InstantiatedFieldContainer]](additionalInstantiatedFieldsKey) + } catch { + case e: Throwable => + OPALLogger.error( + "project configuration - recoverable", + s"configuration key $additionalInstantiatedFieldsKey is invalid; " + + "see InstantiatedFieldsFinder documentation", + e + ) + return instantiatedFields; + } + + fieldDefinitions foreach { fieldDefinition => + project.classFile(ObjectType(fieldDefinition.declaringClass)) match { + case Some(cf) if cf.findField(fieldDefinition.name).nonEmpty => + + cf.findField(fieldDefinition.name).foreach{ field => + fieldDefinition.typeHint match { + case Some(typeHint) if field.fieldType.isReferenceType => + val considerSubtypes = typeHint.endsWith("+") + val fqn = if(considerSubtypes) typeHint.substring(0, typeHint.length - 1) else typeHint + val hintType = ObjectType(fqn) + + if(field.fieldType.isObjectType && + project.classHierarchy.isASubtypeOf(hintType, field.fieldType.asObjectType).isNo){ + // If the given type hint is not a subtype of the field type, we warn and + // fall back to default behavior + OPALLogger.warn( + "project configuration - recoverable", + s"type hint $typeHint for instantiated field not valid" + ) + instantiatedFields += ((field, UIDSet(field.fieldType.asReferenceType))) + } else { + if (considerSubtypes) + instantiatedFields += ((field, UIDSet.fromSpecific[ReferenceType](project.classHierarchy.allSubtypes(hintType, reflexive = true)))) + else + instantiatedFields += ((field, UIDSet(hintType))) + } + + case None if field.fieldType.isReferenceType => + instantiatedFields += ((field, UIDSet(field.fieldType.asReferenceType))) + case _ => + // This is okay, primitive types don't need to be initialized + } + } + + case Some(_) => + OPALLogger.warn( + "project configuration - recoverable", + s"configured field named ${fieldDefinition.name} not found on" + + s" class ${fieldDefinition.declaringClass}" + ) + case None => + OPALLogger.warn( + "project configuration - recoverable", + s"class ${fieldDefinition.declaringClass} not found for configured instantiated field" + ) + } + } + + super.collectInstantiatedFields(project) ++ instantiatedFields + } + + private case class InstantiatedFieldContainer(declaringClass: String, name: String, typeHint: Option[String]) +} + +object ConfigurationInstatiatedFieldsFinder + extends ConfigurationInstantiatedFieldsFinder + +object DefaultInstantiatedFieldsFinder + extends DefaultInstantiatedFieldsFinder + with ConfigurationInstantiatedFieldsFinder + +object SoundLibraryInstantiatedFieldsFinder + extends SoundLibraryInstantiatedFieldsFinder + with ConfigurationInstantiatedFieldsFinder + + + diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/rta/InstantiatedTypesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/rta/InstantiatedTypesAnalysis.scala index 2f42a93275..987751ad88 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/rta/InstantiatedTypesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/rta/InstantiatedTypesAnalysis.scala @@ -7,13 +7,12 @@ package cg package rta import scala.language.existentials - import org.opalj.br.DeclaredMethod import org.opalj.br.ObjectType import org.opalj.br.ReferenceType import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject -import org.opalj.br.analyses.cg.InitialInstantiatedTypesKey +import org.opalj.br.analyses.cg.{InitialInstantiatedFieldsKey, InitialInstantiatedTypesKey} import org.opalj.br.fpcf.BasicFPCFTriggeredAnalysisScheduler import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysis @@ -237,7 +236,10 @@ object InstantiatedTypesAnalysisScheduler extends BasicFPCFTriggeredAnalysisSche } override def init(p: SomeProject, ps: PropertyStore): Null = { - val initialInstantiatedTypes = UIDSet[ReferenceType](p.get(InitialInstantiatedTypesKey).toSeq: _*) + // Initialize with the initial instantiated types and fields + val initialInstantiatedTypes = UIDSet[ReferenceType](p.get(InitialInstantiatedTypesKey).toSeq: _*) ++ + p.get(InitialInstantiatedFieldsKey).flatMap(_._2) + ps.preInitialize[SomeProject, InstantiatedTypes](p, InstantiatedTypes.key) { case _: EPK[_, _] => InterimEUBP(p, InstantiatedTypes(initialInstantiatedTypes)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala index fc8e4a9c13..aae286f501 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala @@ -7,7 +7,6 @@ package cg package xta import scala.collection.mutable.ArrayBuffer - import org.opalj.br.ArrayType import org.opalj.br.DeclaredMethod import org.opalj.br.Field @@ -19,9 +18,7 @@ import org.opalj.br.analyses.DeclaredFieldsKey import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject -import org.opalj.br.analyses.cg.ClosedPackagesKey -import org.opalj.br.analyses.cg.InitialEntryPointsKey -import org.opalj.br.analyses.cg.InitialInstantiatedTypesKey +import org.opalj.br.analyses.cg.{ClosedPackagesKey, InitialEntryPointsKey, InitialInstantiatedFieldsKey, InitialInstantiatedTypesKey} import org.opalj.br.fpcf.BasicFPCFTriggeredAnalysisScheduler import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysis @@ -302,6 +299,7 @@ class InstantiatedTypesAnalysisScheduler( val declaredFields = p.get(DeclaredFieldsKey) val entryPoints = p.get(InitialEntryPointsKey) val initialInstantiatedTypes = UIDSet[ReferenceType](p.get(InitialInstantiatedTypesKey).toSeq: _*) + val initialInstantiatedFields = p.get(InitialInstantiatedFieldsKey) // While processing entry points and fields, we keep track of all array types we see, as // well as subtypes and lower-dimensional types. These types also need to be @@ -322,18 +320,11 @@ class InstantiatedTypesAnalysisScheduler( } } - // Marks the field of class java/lang/System with the given name as instantiated by default - // This only works if the RTJar is loaded - otherwise, the initial instantiated types being - // set for 'ExternalWorld' should take care of making these types accessible. - def initializeSystemField(fieldName: String): Unit = { - p.classFile(ObjectType.System).foreach { systemCf => - systemCf.findField(fieldName).foreach { systemField => - if (systemField.fieldType.isReferenceType) { - val declaredField = declaredFields(systemField) - initialize(selectSetEntity(declaredField), UIDSet(systemField.fieldType.asReferenceType)) - } - } - } + // Marks a given field as having its declared field type instantiated by default - only if field is of a + // reference type + def initializeField(field: Field, typesToConsider: UIDSet[ReferenceType]): Unit = { + val fieldSetEntity = selectSetEntity(declaredFields(field)) + initialize(fieldSetEntity, typesToConsider) } // Some cooperative analyses originally meant for RTA may require the global type set @@ -342,12 +333,9 @@ class InstantiatedTypesAnalysisScheduler( initialize(p, UIDSet(ObjectType.String, ObjectType.Class)) initialize(ExternalWorld, initialInstantiatedTypes) - // During system initialization, some native methods are called to set certain fields in - // the java/lang/System class, namely "PrintStream out", "InputStream in" and "PrintStream - // err" - since we can't detect those native calls, we manually mark them as instantiated. - initializeSystemField("out") - initializeSystemField("in") - initializeSystemField("err") + // During system initialization, some native methods are called to set certain fields. + // These fields can be set as instantiated via the configuration, see InitialFieldsFinder. + initialInstantiatedFields.foreach{ case (f: Field, t: UIDSet[ReferenceType]) => initializeField(f, t) } def isRelevantArrayType(rt: Type): Boolean = rt.isArrayType && rt.asArrayType.elementType.isObjectType From 532bd64057ee20d11ffd3f7f53459d511e1ffac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Mon, 2 Dec 2024 17:12:25 +0100 Subject: [PATCH 04/10] Formatting, file headers --- .../cg/InitialInstantiatedFieldsKey.scala | 19 ++- .../cg/InstantiatedFieldsFinder.scala | 143 +++++++++--------- .../cg/rta/InstantiatedTypesAnalysis.scala | 5 +- .../cg/xta/InstantiatedTypesAnalysis.scala | 8 +- 4 files changed, 98 insertions(+), 77 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala index d678a1f929..264b70ce75 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala @@ -1,11 +1,20 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.br.analyses.cg -import net.ceedubs.ficus.Ficus._ -import org.opalj.br.{Field, ReferenceType} -import org.opalj.br.analyses.{ProjectInformationKey, ProjectInformationKeys, SomeProject} +import org.opalj.br.Field +import org.opalj.br.ReferenceType +import org.opalj.br.analyses.ProjectInformationKey +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject import org.opalj.collection.immutable.UIDSet -object InitialInstantiatedFieldsKey extends ProjectInformationKey[Iterable[(Field, UIDSet[ReferenceType])], Nothing]{ +import net.ceedubs.ficus.Ficus._ + +/** + * The ProjectInformationKey to iterate all fields that shall be considered to be instantiated by default. Each field + * is associated with a set of ReferenceTypes to be considered instantiated for this field. + */ +object InitialInstantiatedFieldsKey extends ProjectInformationKey[Iterable[(Field, UIDSet[ReferenceType])], Nothing] { final val ConfigKeyPrefix = "org.opalj.br.analyses.cg.InitialInstantiatedFieldsKey." @@ -14,7 +23,7 @@ object InitialInstantiatedFieldsKey extends ProjectInformationKey[Iterable[(Fiel override def compute(project: SomeProject): Iterable[(Field, UIDSet[ReferenceType])] = { val key = ConfigKeyPrefix + "analysis" val configuredAnalysis = project.config.as[Option[String]](key) - if(configuredAnalysis.isEmpty) { + if (configuredAnalysis.isEmpty) { throw new IllegalArgumentException( "No InitialInstantiatedFieldsKey configuration available; Instantiated fields cannot be computed!" ) diff --git a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala index dd6f3d7ac5..1a7868252d 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala @@ -1,10 +1,15 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj.br.analyses.cg -import net.ceedubs.ficus.Ficus._ -import org.opalj.br.{Field, ObjectType, ReferenceType} +import org.opalj.br.Field +import org.opalj.br.ObjectType +import org.opalj.br.ReferenceType import org.opalj.br.analyses.SomeProject import org.opalj.collection.immutable.UIDSet -import org.opalj.log.{LogContext, OPALLogger} +import org.opalj.log.LogContext +import org.opalj.log.OPALLogger + +import net.ceedubs.ficus.Ficus._ /** * This trait represents objects that detect fields that should be considered to be instantiated no matter what. This @@ -33,16 +38,14 @@ trait DefaultInstantiatedFieldsFinder extends InstantiatedFieldsFinder { trait SoundLibraryInstantiatedFieldsFinder extends InstantiatedFieldsFinder { override def collectInstantiatedFields(project: SomeProject): Iterable[(Field, UIDSet[ReferenceType])] = { - if(!project.libraryClassFilesAreInterfacesOnly) + if (!project.libraryClassFilesAreInterfacesOnly) return super.collectInstantiatedFields(project); super.collectInstantiatedFields(project) ++ project .allLibraryClassFiles .flatMap(_.fields) .filter(_.fieldType.isReferenceType) - .map{ field => - (field, UIDSet(field.fieldType.asReferenceType)) - } + .map { field => (field, UIDSet(field.fieldType.asReferenceType)) } } } @@ -64,74 +67,81 @@ trait ConfigurationInstantiatedFieldsFinder extends InstantiatedFieldsFinder { implicit val logContext: LogContext = project.logContext var instantiatedFields = Set.empty[(Field, UIDSet[ReferenceType])] - if(!project.config.hasPath(additionalInstantiatedFieldsKey)){ + if (!project.config.hasPath(additionalInstantiatedFieldsKey)) { OPALLogger.info( "project configuration", s"configuration key $additionalInstantiatedFieldsKey is missing; " + - "no additional fields are considered instantiated" + "no additional fields are considered instantiated" ) return instantiatedFields } - val fieldDefinitions = try { - project.config.as[List[InstantiatedFieldContainer]](additionalInstantiatedFieldsKey) - } catch { - case e: Throwable => - OPALLogger.error( - "project configuration - recoverable", - s"configuration key $additionalInstantiatedFieldsKey is invalid; " + - "see InstantiatedFieldsFinder documentation", - e - ) - return instantiatedFields; - } + val fieldDefinitions = + try { + project.config.as[List[InstantiatedFieldContainer]](additionalInstantiatedFieldsKey) + } catch { + case e: Throwable => + OPALLogger.error( + "project configuration - recoverable", + s"configuration key $additionalInstantiatedFieldsKey is invalid; " + + "see InstantiatedFieldsFinder documentation", + e + ) + return instantiatedFields; + } fieldDefinitions foreach { fieldDefinition => - project.classFile(ObjectType(fieldDefinition.declaringClass)) match { - case Some(cf) if cf.findField(fieldDefinition.name).nonEmpty => - - cf.findField(fieldDefinition.name).foreach{ field => - fieldDefinition.typeHint match { - case Some(typeHint) if field.fieldType.isReferenceType => - val considerSubtypes = typeHint.endsWith("+") - val fqn = if(considerSubtypes) typeHint.substring(0, typeHint.length - 1) else typeHint - val hintType = ObjectType(fqn) - - if(field.fieldType.isObjectType && - project.classHierarchy.isASubtypeOf(hintType, field.fieldType.asObjectType).isNo){ - // If the given type hint is not a subtype of the field type, we warn and - // fall back to default behavior - OPALLogger.warn( - "project configuration - recoverable", - s"type hint $typeHint for instantiated field not valid" - ) - instantiatedFields += ((field, UIDSet(field.fieldType.asReferenceType))) - } else { - if (considerSubtypes) - instantiatedFields += ((field, UIDSet.fromSpecific[ReferenceType](project.classHierarchy.allSubtypes(hintType, reflexive = true)))) - else - instantiatedFields += ((field, UIDSet(hintType))) - } - - case None if field.fieldType.isReferenceType => - instantiatedFields += ((field, UIDSet(field.fieldType.asReferenceType))) - case _ => + project.classFile(ObjectType(fieldDefinition.declaringClass)) match { + case Some(cf) if cf.findField(fieldDefinition.name).nonEmpty => + cf.findField(fieldDefinition.name).foreach { field => + fieldDefinition.typeHint match { + case Some(typeHint) if field.fieldType.isReferenceType => + val considerSubtypes = typeHint.endsWith("+") + val fqn = if (considerSubtypes) typeHint.substring(0, typeHint.length - 1) else typeHint + val hintType = ObjectType(fqn) + + if (field.fieldType.isObjectType && + project.classHierarchy.isASubtypeOf(hintType, field.fieldType.asObjectType).isNo + ) { + // If the given type hint is not a subtype of the field type, we warn and + // fall back to default behavior + OPALLogger.warn( + "project configuration - recoverable", + s"type hint $typeHint for instantiated field not valid" + ) + instantiatedFields += ((field, UIDSet(field.fieldType.asReferenceType))) + } else { + if (considerSubtypes) + instantiatedFields += (( + field, + UIDSet.fromSpecific[ReferenceType](project.classHierarchy.allSubtypes( + hintType, + reflexive = true + )) + )) + else + instantiatedFields += ((field, UIDSet(hintType))) + } + + case None if field.fieldType.isReferenceType => + instantiatedFields += ((field, UIDSet(field.fieldType.asReferenceType))) + case _ => // This is okay, primitive types don't need to be initialized - } - } - - case Some(_) => - OPALLogger.warn( - "project configuration - recoverable", - s"configured field named ${fieldDefinition.name} not found on" + - s" class ${fieldDefinition.declaringClass}" - ) - case None => - OPALLogger.warn( - "project configuration - recoverable", - s"class ${fieldDefinition.declaringClass} not found for configured instantiated field" - ) - } + } + } + + case Some(_) => + OPALLogger.warn( + "project configuration - recoverable", + s"configured field named ${fieldDefinition.name} not found on" + + s" class ${fieldDefinition.declaringClass}" + ) + case None => + OPALLogger.warn( + "project configuration - recoverable", + s"class ${fieldDefinition.declaringClass} not found for configured instantiated field" + ) + } } super.collectInstantiatedFields(project) ++ instantiatedFields @@ -150,6 +160,3 @@ object DefaultInstantiatedFieldsFinder object SoundLibraryInstantiatedFieldsFinder extends SoundLibraryInstantiatedFieldsFinder with ConfigurationInstantiatedFieldsFinder - - - diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/rta/InstantiatedTypesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/rta/InstantiatedTypesAnalysis.scala index 987751ad88..eb54bb6e7e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/rta/InstantiatedTypesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/rta/InstantiatedTypesAnalysis.scala @@ -7,12 +7,14 @@ package cg package rta import scala.language.existentials + import org.opalj.br.DeclaredMethod import org.opalj.br.ObjectType import org.opalj.br.ReferenceType import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject -import org.opalj.br.analyses.cg.{InitialInstantiatedFieldsKey, InitialInstantiatedTypesKey} +import org.opalj.br.analyses.cg.InitialInstantiatedFieldsKey +import org.opalj.br.analyses.cg.InitialInstantiatedTypesKey import org.opalj.br.fpcf.BasicFPCFTriggeredAnalysisScheduler import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysis @@ -240,7 +242,6 @@ object InstantiatedTypesAnalysisScheduler extends BasicFPCFTriggeredAnalysisSche val initialInstantiatedTypes = UIDSet[ReferenceType](p.get(InitialInstantiatedTypesKey).toSeq: _*) ++ p.get(InitialInstantiatedFieldsKey).flatMap(_._2) - ps.preInitialize[SomeProject, InstantiatedTypes](p, InstantiatedTypes.key) { case _: EPK[_, _] => InterimEUBP(p, InstantiatedTypes(initialInstantiatedTypes)) case eps => throw new IllegalStateException(s"unexpected property: $eps") diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala index aae286f501..7ec9fa1009 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala @@ -7,6 +7,7 @@ package cg package xta import scala.collection.mutable.ArrayBuffer + import org.opalj.br.ArrayType import org.opalj.br.DeclaredMethod import org.opalj.br.Field @@ -18,7 +19,10 @@ import org.opalj.br.analyses.DeclaredFieldsKey import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject -import org.opalj.br.analyses.cg.{ClosedPackagesKey, InitialEntryPointsKey, InitialInstantiatedFieldsKey, InitialInstantiatedTypesKey} +import org.opalj.br.analyses.cg.ClosedPackagesKey +import org.opalj.br.analyses.cg.InitialEntryPointsKey +import org.opalj.br.analyses.cg.InitialInstantiatedFieldsKey +import org.opalj.br.analyses.cg.InitialInstantiatedTypesKey import org.opalj.br.fpcf.BasicFPCFTriggeredAnalysisScheduler import org.opalj.br.fpcf.ContextProviderKey import org.opalj.br.fpcf.FPCFAnalysis @@ -335,7 +339,7 @@ class InstantiatedTypesAnalysisScheduler( // During system initialization, some native methods are called to set certain fields. // These fields can be set as instantiated via the configuration, see InitialFieldsFinder. - initialInstantiatedFields.foreach{ case (f: Field, t: UIDSet[ReferenceType]) => initializeField(f, t) } + initialInstantiatedFields.foreach { case (f: Field, t: UIDSet[ReferenceType]) => initializeField(f, t) } def isRelevantArrayType(rt: Type): Boolean = rt.isArrayType && rt.asArrayType.elementType.isObjectType From 990cc3160682a42779038658f39139cf6e39262f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Tue, 3 Dec 2024 13:53:15 +0100 Subject: [PATCH 05/10] Add initial instantiated fields to CFA_1_0 and CFA_1_1 CG analyses --- .../AllocationSiteBasedPointsToAnalysis.scala | 37 +++++++++++++++++++ .../pointsto/TypeBasedPointsToAnalysis.scala | 20 ++++++++++ 2 files changed, 57 insertions(+) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AllocationSiteBasedPointsToAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AllocationSiteBasedPointsToAnalysis.scala index 659f584fab..5fb231590e 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AllocationSiteBasedPointsToAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AllocationSiteBasedPointsToAnalysis.scala @@ -5,9 +5,19 @@ package fpcf package analyses package pointsto +import org.opalj.br.ReferenceType +import org.opalj.br.analyses.DeclaredFieldsKey import org.opalj.br.analyses.SomeProject +import org.opalj.br.analyses.cg.InitialInstantiatedFieldsKey +import org.opalj.br.fpcf.properties.NoContext import org.opalj.br.fpcf.properties.pointsto.AllocationSitePointsToSet +import org.opalj.br.fpcf.properties.pointsto.AllocationSitePointsToSet1 +import org.opalj.br.fpcf.properties.pointsto.NoAllocationSites +import org.opalj.br.fpcf.properties.pointsto.allocationSiteToLong +import org.opalj.fpcf.EPK +import org.opalj.fpcf.InterimEUBP import org.opalj.fpcf.PropertyMetaInformation +import org.opalj.fpcf.PropertyStore class AllocationSiteBasedPointsToAnalysis private[analyses] ( final val project: SomeProject @@ -18,4 +28,31 @@ object AllocationSiteBasedPointsToAnalysisScheduler extends AbstractPointsToAnal override val propertyKind: PropertyMetaInformation = AllocationSitePointsToSet override val createAnalysis: SomeProject => AllocationSiteBasedPointsToAnalysis = new AllocationSiteBasedPointsToAnalysis(_) + + override def init(p: SomeProject, ps: PropertyStore): Null = { + val declaredFields = p.get(DeclaredFieldsKey) + val initialFields = p.get(InitialInstantiatedFieldsKey) + + val dummyAllocationSitesPerType: Map[ReferenceType, Long] = initialFields + .flatMap(_._2) + .toSet + .map { refType: ReferenceType => (refType, allocationSiteToLong(NoContext, 0, refType)) } + .toMap + + initialFields.foreach { case (field, types) => + val fieldPointsToSet = + types.foldLeft[AllocationSitePointsToSet](NoAllocationSites) { case (result, currType) => + result.included(AllocationSitePointsToSet1(dummyAllocationSitesPerType(currType), currType)) + } + val declaredField = declaredFields(field) + + ps.preInitialize(declaredField, AllocationSitePointsToSet.key) { + case _: EPK[_, _] => InterimEUBP(declaredField, fieldPointsToSet) + case eps => throw new IllegalStateException(s"unexpected property: $eps") + } + + } + + super.init(p, ps) + } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/TypeBasedPointsToAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/TypeBasedPointsToAnalysis.scala index b120cd78ca..1e26e5c767 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/TypeBasedPointsToAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/TypeBasedPointsToAnalysis.scala @@ -5,9 +5,14 @@ package fpcf package analyses package pointsto +import org.opalj.br.analyses.DeclaredFieldsKey import org.opalj.br.analyses.SomeProject +import org.opalj.br.analyses.cg.InitialInstantiatedFieldsKey import org.opalj.br.fpcf.properties.pointsto.TypeBasedPointsToSet +import org.opalj.fpcf.EPK +import org.opalj.fpcf.InterimEUBP import org.opalj.fpcf.PropertyMetaInformation +import org.opalj.fpcf.PropertyStore /** * An andersen-style points-to analysis, i.e. points-to sets are modeled as subsets. @@ -35,4 +40,19 @@ object TypeBasedPointsToAnalysisScheduler extends AbstractPointsToAnalysisSchedu override val propertyKind: PropertyMetaInformation = TypeBasedPointsToSet override val createAnalysis: SomeProject => TypeBasedPointsToAnalysis = new TypeBasedPointsToAnalysis(_) + + override def init(p: SomeProject, ps: PropertyStore): Null = { + val declFields = p.get(DeclaredFieldsKey) + + p + .get(InitialInstantiatedFieldsKey) + .foreach { case (field, types) => + val declField = declFields(field) + ps.preInitialize(declField, TypeBasedPointsToSet.key) { + case _: EPK[_, _] => InterimEUBP(declField, TypeBasedPointsToSet(types)) + case eps => throw new IllegalStateException(s"unexpected property: $eps") + } + } + super.init(p, ps) + } } From 5950967fbba354dba4d38daa9fcf91bfc844b815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Tue, 3 Dec 2024 17:08:19 +0100 Subject: [PATCH 06/10] Fix package declaration format --- .../opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala | 5 ++++- .../org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala index 264b70ce75..8959f5d655 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala @@ -1,5 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.br.analyses.cg +package org.opalj +package br +package analyses +package cg import org.opalj.br.Field import org.opalj.br.ReferenceType diff --git a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala index 1a7868252d..fae26b6ef4 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala @@ -1,5 +1,8 @@ /* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj.br.analyses.cg +package org.opalj +package br +package analyses +package cg import org.opalj.br.Field import org.opalj.br.ObjectType From a8f0db1f09a71e97fd9cd8393d65b2bf2aa1d272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Tue, 17 Dec 2024 15:41:55 +0100 Subject: [PATCH 07/10] Remove InstantiatedFieldsFinder, add (first shot at) proper configured types analysis for XTA family --- OPAL/br/src/main/resources/reference.conf | 17 -- .../cg/InitialInstantiatedFieldsKey.scala | 47 ---- .../cg/InstantiatedFieldsFinder.scala | 165 ------------ OPAL/tac/src/main/resources/reference.conf | 75 ++++++ .../cg/PropagationBasedCallGraphKeys.scala | 4 +- .../cg/rta/InstantiatedTypesAnalysis.scala | 5 +- ...tiveMethodsInstantiatedTypesAnalysis.scala | 238 ++++++++++++++++++ ...redNativeMethodsTypePropagationState.scala | 66 +++++ .../cg/xta/InstantiatedTypesAnalysis.scala | 13 - .../AllocationSiteBasedPointsToAnalysis.scala | 36 --- .../pointsto/TypeBasedPointsToAnalysis.scala | 20 -- 11 files changed, 382 insertions(+), 304 deletions(-) delete mode 100644 OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala delete mode 100644 OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala create mode 100644 OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsTypePropagationState.scala diff --git a/OPAL/br/src/main/resources/reference.conf b/OPAL/br/src/main/resources/reference.conf index a00b54972f..9d24c92c2a 100644 --- a/OPAL/br/src/main/resources/reference.conf +++ b/OPAL/br/src/main/resources/reference.conf @@ -104,23 +104,6 @@ org.opalj { "java/lang/Thread" ] } - - InitialInstantiatedFieldsKey { - analysis = "org.opalj.br.analyses.cg.DefaultInstantiatedFieldsFinder" - - instantiatedFields = [ - {declaringClass = "java/lang/System", name = "out"}, - {declaringClass = "java/lang/System", name = "in", typeHint = "java/io/BufferedInputStream"}, - {declaringClass = "java/lang/System", name = "err"} - ] - # additional initialized fields can be specified here. Each tuple must contain of a class name and a field - # name - all fields matching this specification will be considered instantiated. By default, only the declared - # type of the field will be considered to be instantiated for the respective field. You can change this - # behavior by adding the optional field "typeHint" to the declaration. This must contain the FQN of the type to - # be considered for the field - if it is not a subtype of the field type, it will be ignored. Similarly to - # the initial types, you can suffix the FQN with a "+" to indicate that all subtypes of the given type - # shall be considered, too. - } } } } diff --git a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala deleted file mode 100644 index 8959f5d655..0000000000 --- a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InitialInstantiatedFieldsKey.scala +++ /dev/null @@ -1,47 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package br -package analyses -package cg - -import org.opalj.br.Field -import org.opalj.br.ReferenceType -import org.opalj.br.analyses.ProjectInformationKey -import org.opalj.br.analyses.ProjectInformationKeys -import org.opalj.br.analyses.SomeProject -import org.opalj.collection.immutable.UIDSet - -import net.ceedubs.ficus.Ficus._ - -/** - * The ProjectInformationKey to iterate all fields that shall be considered to be instantiated by default. Each field - * is associated with a set of ReferenceTypes to be considered instantiated for this field. - */ -object InitialInstantiatedFieldsKey extends ProjectInformationKey[Iterable[(Field, UIDSet[ReferenceType])], Nothing] { - - final val ConfigKeyPrefix = "org.opalj.br.analyses.cg.InitialInstantiatedFieldsKey." - - override def requirements(project: SomeProject): ProjectInformationKeys = Seq.empty - - override def compute(project: SomeProject): Iterable[(Field, UIDSet[ReferenceType])] = { - val key = ConfigKeyPrefix + "analysis" - val configuredAnalysis = project.config.as[Option[String]](key) - if (configuredAnalysis.isEmpty) { - throw new IllegalArgumentException( - "No InitialInstantiatedFieldsKey configuration available; Instantiated fields cannot be computed!" - ) - } - - val fqn = configuredAnalysis.get - val ifFinder = instantiatedFieldsFinder(fqn) - ifFinder.collectInstantiatedFields(project) - } - - private[this] def instantiatedFieldsFinder(fqn: String): InstantiatedFieldsFinder = { - import scala.reflect.runtime.universe._ - val mirror = runtimeMirror(this.getClass.getClassLoader) - val module = mirror.staticModule(fqn) - mirror.reflectModule(module).instance.asInstanceOf[InstantiatedFieldsFinder] - } - -} diff --git a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala deleted file mode 100644 index fae26b6ef4..0000000000 --- a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/InstantiatedFieldsFinder.scala +++ /dev/null @@ -1,165 +0,0 @@ -/* BSD 2-Clause License - see OPAL/LICENSE for details. */ -package org.opalj -package br -package analyses -package cg - -import org.opalj.br.Field -import org.opalj.br.ObjectType -import org.opalj.br.ReferenceType -import org.opalj.br.analyses.SomeProject -import org.opalj.collection.immutable.UIDSet -import org.opalj.log.LogContext -import org.opalj.log.OPALLogger - -import net.ceedubs.ficus.Ficus._ - -/** - * This trait represents objects that detect fields that should be considered to be instantiated no matter what. This - * can be useful e.g. when fields are natively set by the JVM, but also when dealing with libraries. The functionality - * is similar to the InstantiatedTypesFinder. - * - * @author Johannes Düsing - */ -sealed trait InstantiatedFieldsFinder { - def collectInstantiatedFields(project: SomeProject): Iterable[(Field, UIDSet[ReferenceType])] = Iterable.empty -} - -/** - * The default instantiated fields finder does not consider any fields to be instantiated. - */ -trait DefaultInstantiatedFieldsFinder extends InstantiatedFieldsFinder { - override def collectInstantiatedFields(project: SomeProject): Iterable[(Field, UIDSet[ReferenceType])] = - super.collectInstantiatedFields(project) -} - -/** - * A trait that considers all library fields to be instantiated with their declared type IF library class files are - * interfaces only. A fully sound over-approximation would have to consider all subtypes of each field to possibly - * be instantiated as well - this is not done here to somewhat retain precision, especially for RTA. - */ -trait SoundLibraryInstantiatedFieldsFinder extends InstantiatedFieldsFinder { - - override def collectInstantiatedFields(project: SomeProject): Iterable[(Field, UIDSet[ReferenceType])] = { - if (!project.libraryClassFilesAreInterfacesOnly) - return super.collectInstantiatedFields(project); - - super.collectInstantiatedFields(project) ++ project - .allLibraryClassFiles - .flatMap(_.fields) - .filter(_.fieldType.isReferenceType) - .map { field => (field, UIDSet(field.fieldType.asReferenceType)) } - } - -} - -/** - * A trait that considers fields to be instantiated based on the configuration of OPAL. Users can supply custom - * values to mark fields as instantiated. Per default the field type is considered, but if users provide a - * "typeHint" property, they can specify exact types and / or subtypes to be considered for the given field. - */ -trait ConfigurationInstantiatedFieldsFinder extends InstantiatedFieldsFinder { - - @inline private[this] def additionalInstantiatedFieldsKey: String = { - InitialInstantiatedFieldsKey.ConfigKeyPrefix + "instantiatedFields" - } - - override def collectInstantiatedFields(project: SomeProject): Iterable[(Field, UIDSet[ReferenceType])] = { - import net.ceedubs.ficus.readers.ArbitraryTypeReader._ - - implicit val logContext: LogContext = project.logContext - var instantiatedFields = Set.empty[(Field, UIDSet[ReferenceType])] - - if (!project.config.hasPath(additionalInstantiatedFieldsKey)) { - OPALLogger.info( - "project configuration", - s"configuration key $additionalInstantiatedFieldsKey is missing; " + - "no additional fields are considered instantiated" - ) - return instantiatedFields - } - - val fieldDefinitions = - try { - project.config.as[List[InstantiatedFieldContainer]](additionalInstantiatedFieldsKey) - } catch { - case e: Throwable => - OPALLogger.error( - "project configuration - recoverable", - s"configuration key $additionalInstantiatedFieldsKey is invalid; " + - "see InstantiatedFieldsFinder documentation", - e - ) - return instantiatedFields; - } - - fieldDefinitions foreach { fieldDefinition => - project.classFile(ObjectType(fieldDefinition.declaringClass)) match { - case Some(cf) if cf.findField(fieldDefinition.name).nonEmpty => - cf.findField(fieldDefinition.name).foreach { field => - fieldDefinition.typeHint match { - case Some(typeHint) if field.fieldType.isReferenceType => - val considerSubtypes = typeHint.endsWith("+") - val fqn = if (considerSubtypes) typeHint.substring(0, typeHint.length - 1) else typeHint - val hintType = ObjectType(fqn) - - if (field.fieldType.isObjectType && - project.classHierarchy.isASubtypeOf(hintType, field.fieldType.asObjectType).isNo - ) { - // If the given type hint is not a subtype of the field type, we warn and - // fall back to default behavior - OPALLogger.warn( - "project configuration - recoverable", - s"type hint $typeHint for instantiated field not valid" - ) - instantiatedFields += ((field, UIDSet(field.fieldType.asReferenceType))) - } else { - if (considerSubtypes) - instantiatedFields += (( - field, - UIDSet.fromSpecific[ReferenceType](project.classHierarchy.allSubtypes( - hintType, - reflexive = true - )) - )) - else - instantiatedFields += ((field, UIDSet(hintType))) - } - - case None if field.fieldType.isReferenceType => - instantiatedFields += ((field, UIDSet(field.fieldType.asReferenceType))) - case _ => - // This is okay, primitive types don't need to be initialized - } - } - - case Some(_) => - OPALLogger.warn( - "project configuration - recoverable", - s"configured field named ${fieldDefinition.name} not found on" + - s" class ${fieldDefinition.declaringClass}" - ) - case None => - OPALLogger.warn( - "project configuration - recoverable", - s"class ${fieldDefinition.declaringClass} not found for configured instantiated field" - ) - } - } - - super.collectInstantiatedFields(project) ++ instantiatedFields - } - - private case class InstantiatedFieldContainer(declaringClass: String, name: String, typeHint: Option[String]) -} - -object ConfigurationInstatiatedFieldsFinder - extends ConfigurationInstantiatedFieldsFinder - -object DefaultInstantiatedFieldsFinder - extends DefaultInstantiatedFieldsFinder - with ConfigurationInstantiatedFieldsFinder - -object SoundLibraryInstantiatedFieldsFinder - extends SoundLibraryInstantiatedFieldsFinder - with ConfigurationInstantiatedFieldsFinder diff --git a/OPAL/tac/src/main/resources/reference.conf b/OPAL/tac/src/main/resources/reference.conf index b21ce227c9..7711453c22 100644 --- a/OPAL/tac/src/main/resources/reference.conf +++ b/OPAL/tac/src/main/resources/reference.conf @@ -1329,6 +1329,81 @@ org.opalj { // java.lang.System // // // ////////////////////////////////////////////////////////////////////////////////////////// + { + cf = "java/lang/System", + name = "initPhase1", + desc = "()V", + pointsTo = [ + { + lhs = { + cf = "java/lang/System", + name = "initPhase1", + desc = "()V" + }, + rhs = { + cf = "java/lang/System", + name = "initPhase1", + desc = "()V" + instantiatedType = "Ljava/io/BufferedInputStream;" + } + } + ], + methodInvocations = [ + { + cf = "java/lang/System", + name = "setIn0", + desc = "(Ljava/io/InputStream;)V" + }, + { + cf = "java/lang/System", + name = "setOut0", + desc = "(Ljava/io/PrintStream;)V" + }, + { + cf = "java/lang/System", + name = "setErr0", + desc = "(Ljava/io/PrintStream;)V" + }, + { + cf = "java/lang/System", + name = "newPrintStream", + desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream" + } + ] + }, + { + cf = "java/lang/System", + name = "newPrintStream", + desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream", + pointsTo = [ + { + lhs = { + cf = "java/lang/System", + name = "newPrintStream", + desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream" + }, + rhs = { + cf = "java/lang/System", + name = "newPrintStream", + desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream", + instantiatedType = "Ljava/io/PrintStream" + } + }, + { + lhs = { + cf = "java/lang/System", + name = "newPrintStream", + desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream" + }, + rhs = { + cf = "java/lang/System", + name = "newPrintStream", + desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream", + instantiatedType = "Ljava/io/BufferedOutputStream" + } + } + ] + }, { // assigns its parameter to the field System.in cf = "java/lang/System", name = "setIn0", desc = "(Ljava/io/InputStream;)V", diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/cg/PropagationBasedCallGraphKeys.scala b/OPAL/tac/src/main/scala/org/opalj/tac/cg/PropagationBasedCallGraphKeys.scala index 0230f80c05..d1c1a478ef 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/cg/PropagationBasedCallGraphKeys.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/cg/PropagationBasedCallGraphKeys.scala @@ -9,7 +9,7 @@ import org.opalj.br.analyses.cg.InitialInstantiatedTypesKey import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.properties.SimpleContextsKey import org.opalj.tac.fpcf.analyses.cg.PropagationBasedTypeIterator -import org.opalj.tac.fpcf.analyses.cg.rta.ConfiguredNativeMethodsInstantiatedTypesAnalysisScheduler +import org.opalj.tac.fpcf.analyses.cg.xta.ConfiguredNativeMethodsInstantiatedTypesAnalysisScheduler import org.opalj.tac.fpcf.analyses.cg.xta.ArrayInstantiationsAnalysisScheduler import org.opalj.tac.fpcf.analyses.cg.xta.CTASetEntitySelector import org.opalj.tac.fpcf.analyses.cg.xta.FTASetEntitySelector @@ -61,7 +61,7 @@ trait PropagationBasedCallGraphKey extends CallGraphKey { new InstantiatedTypesAnalysisScheduler(theTypeSetEntitySelector), new ArrayInstantiationsAnalysisScheduler(theTypeSetEntitySelector), new TypePropagationAnalysisScheduler(theTypeSetEntitySelector), - ConfiguredNativeMethodsInstantiatedTypesAnalysisScheduler, + new ConfiguredNativeMethodsInstantiatedTypesAnalysisScheduler(theTypeSetEntitySelector), TriggeredFieldAccessInformationAnalysis, ReflectionRelatedFieldAccessesAnalysisScheduler ) ::: (if (isLibrary) List(LibraryInstantiatedTypesBasedEntryPointsAnalysis) else Nil) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/rta/InstantiatedTypesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/rta/InstantiatedTypesAnalysis.scala index eb54bb6e7e..2f42a93275 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/rta/InstantiatedTypesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/rta/InstantiatedTypesAnalysis.scala @@ -13,7 +13,6 @@ import org.opalj.br.ObjectType import org.opalj.br.ReferenceType import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject -import org.opalj.br.analyses.cg.InitialInstantiatedFieldsKey import org.opalj.br.analyses.cg.InitialInstantiatedTypesKey import org.opalj.br.fpcf.BasicFPCFTriggeredAnalysisScheduler import org.opalj.br.fpcf.ContextProviderKey @@ -238,9 +237,7 @@ object InstantiatedTypesAnalysisScheduler extends BasicFPCFTriggeredAnalysisSche } override def init(p: SomeProject, ps: PropertyStore): Null = { - // Initialize with the initial instantiated types and fields - val initialInstantiatedTypes = UIDSet[ReferenceType](p.get(InitialInstantiatedTypesKey).toSeq: _*) ++ - p.get(InitialInstantiatedFieldsKey).flatMap(_._2) + val initialInstantiatedTypes = UIDSet[ReferenceType](p.get(InitialInstantiatedTypesKey).toSeq: _*) ps.preInitialize[SomeProject, InstantiatedTypes](p, InstantiatedTypes.key) { case _: EPK[_, _] => InterimEUBP(p, InstantiatedTypes(initialInstantiatedTypes)) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala new file mode 100644 index 0000000000..15792d5b8a --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala @@ -0,0 +1,238 @@ +package org.opalj +package tac +package fpcf +package analyses +package cg +package xta + +import org.opalj.br.{DeclaredMethod, FieldType, Method, ObjectType, ReferenceType} +import org.opalj.br.analyses.{DeclaredFields, DeclaredFieldsKey, DeclaredMethodsKey, ProjectInformationKeys, SomeProject, VirtualFormalParametersKey} +import org.opalj.br.fpcf.BasicFPCFTriggeredAnalysisScheduler +import org.opalj.br.fpcf.properties.cg.{Callees, Callers, InstantiatedTypes} +import org.opalj.collection.immutable.UIDSet +import org.opalj.fpcf.{EPS, EUBP, InterimPartialResult, PartialResult, ProperPropertyComputationResult, PropertyBounds, PropertyKind, PropertyStore, Results, SomeEPS, SomePartialResult} +import org.opalj.log.OPALLogger +import org.opalj.tac.fpcf.analyses.{AllocationSiteDescription, ConfiguredMethods, MethodDescription, PointsToRelation, StaticFieldDescription} +import org.opalj.tac.fpcf.properties.TACAI + +import scala.collection.mutable.ArrayBuffer + +/** + * Handles the effect of certain (configured native methods) to the set of instantiated types. + * + * @author Johannes Düsing + */ +class ConfiguredNativeMethodsInstantiatedTypesAnalysis private[analyses] ( + final val project: SomeProject, + final val typeSetEntitySelector: TypeSetEntitySelector, + ) extends ReachableMethodAnalysis { + + private[this] val declaredFields: DeclaredFields = p.get(DeclaredFieldsKey) + private[this] val virtualFormalParameters = project.get(VirtualFormalParametersKey) + + private type State = ConfiguredNativeMethodsTypePropagationState[ContextType] + + + // TODO remove dependency to classes in pointsto package + private[this] val nativeMethodData: Map[DeclaredMethod, Array[PointsToRelation]] = + ConfiguredMethods + .reader + .read(p.config, "org.opalj.fpcf.analyses.ConfiguredNativeMethodsAnalysis") + .nativeMethods + .filter(_.pointsTo.isDefined) + .map { v => (v.method, v.pointsTo.get) } + .toMap + + override final val processesMethodsWithoutBody: Boolean = true + + override def processMethod(callContext: ContextType, tacEP: EPS[Method, TACAI]): ProperPropertyComputationResult = { + if(!nativeMethodData.contains(callContext.method)) { + // We have nothing to contribute to this method + return Results() + } + + val configuredData = nativeMethodData(callContext.method) + // Method may be without body (native) or not - we want both to work + val typeSetEntity = typeSetEntitySelector(callContext.method) + val instantiatedTypesEOptP = propertyStore(typeSetEntity, InstantiatedTypes.key) + + implicit val state: ConfiguredNativeMethodsTypePropagationState[ContextType] = new ConfiguredNativeMethodsTypePropagationState( + callContext, + configuredData, + typeSetEntity, + instantiatedTypesEOptP + ) + + implicit val partialResults: ArrayBuffer[SomePartialResult] = ArrayBuffer.empty[SomePartialResult] + + processParameterAssignments(state.ownInstantiatedTypes) + processStaticConfigurations + + + returnResults(partialResults) + } + + private def processStaticConfigurations(implicit state: State, partialResults: ArrayBuffer[SomePartialResult]): Unit = { + state.configurationData.foreach { + case PointsToRelation(StaticFieldDescription(cf, name, fieldType), asd: AllocationSiteDescription) => + val theField = declaredFields(ObjectType(cf), name, FieldType(fieldType)) + val allocatedType = FieldType(asd.instantiatedType) + + val fieldSetEntity = typeSetEntitySelector(theField) + + if(allocatedType.isReferenceType && theField.fieldType.isReferenceType && + candidateMatchesTypeFilter(allocatedType.asReferenceType, theField.fieldType.asReferenceType)){ + partialResults += PartialResult[TypeSetEntity, InstantiatedTypes]( + fieldSetEntity, InstantiatedTypes.key, InstantiatedTypes.update(fieldSetEntity, UIDSet(allocatedType.asReferenceType)) + ) + } else { + OPALLogger.warn("project configuration", s"configured points to data is invalid for ${state.callContext.method.toJava}") + } + + case PointsToRelation(MethodDescription(cf, name, desc), asd: AllocationSiteDescription) => + val theMethod = state.callContext.method + + if(theMethod.declaringClassType.fqn != cf || theMethod.name != name || theMethod.descriptor.toJVMDescriptor != desc){ + OPALLogger.warn("project configuration", s"configured points to data is invalid for ${state.callContext.method.toJava}") + } else { + val allocatedType = FieldType(asd.instantiatedType) + val methodSetEntity = state.typeSetEntity + + if(allocatedType.isReferenceType){ + partialResults += PartialResult[TypeSetEntity, InstantiatedTypes]( + methodSetEntity, InstantiatedTypes.key, InstantiatedTypes.update(methodSetEntity, UIDSet(allocatedType.asReferenceType)) + ) + } + } + + case _ => + } + } + + private def processParameterAssignments(typesToConsider: UIDSet[ReferenceType])(implicit state: State, partialResults: ArrayBuffer[SomePartialResult]): Unit = { + state.configurationData.foreach { + + case PointsToRelation(StaticFieldDescription(cf, name, fieldType), pd: ParameterDescription) => + val theField = declaredFields(ObjectType(cf), name, FieldType(fieldType)) + val fieldSetEntity = typeSetEntitySelector(theField) + val theParameter = pd.fp(state.callContext.method, virtualFormalParameters) + + val theParameterType = if(theParameter.origin == -1) { + ObjectType(pd.cf) + } else { + val paramIdx = -theParameter.origin - 2 + state.callContext.method.descriptor.parameterType(paramIdx) + } + + if(theField.fieldType.isReferenceType && theParameterType.isReferenceType && + candidateMatchesTypeFilter(theParameterType.asReferenceType, theField.fieldType.asReferenceType)){ + val filteredTypes = typesToConsider.foldLeft(UIDSet.newBuilder[ReferenceType]) { (builder, newType) => + if(candidateMatchesTypeFilter(newType, theParameterType.asReferenceType)){ + builder += newType + } + builder + }.result() + + partialResults += PartialResult[TypeSetEntity, InstantiatedTypes]( + fieldSetEntity, InstantiatedTypes.key, InstantiatedTypes.update(fieldSetEntity, filteredTypes) + ) + } else { + OPALLogger.warn("project configuration", s"configured points to data is invalid for ${state.callContext.method.toJava}") + } + case _ => + } + } + + private def returnResults(partialResults: IterableOnce[SomePartialResult])(implicit state: State): ProperPropertyComputationResult = { + // Always re-register the continuation. It is impossible for all dependees to be final in XTA/... + Results( + InterimPartialResult(state.dependees, c(state)), + partialResults + ) + } + + private def c(state: State)(eps: SomeEPS): ProperPropertyComputationResult = eps match { + + case EUBP(e: TypeSetEntity, _: InstantiatedTypes) if e == state.typeSetEntity => + val theEPS = eps.asInstanceOf[EPS[TypeSetEntity, InstantiatedTypes]] + + val previouslySeenTypes = state.ownInstantiatedTypes.size + state.updateOwnInstantiatedTypesDependee(theEPS) + val unseenTypes = UIDSet(theEPS.ub.dropOldest(previouslySeenTypes).toSeq: _*) + + implicit val partialResults: ArrayBuffer[SomePartialResult] = ArrayBuffer.empty[SomePartialResult] + + processParameterAssignments(unseenTypes)(state, partialResults) + + returnResults(partialResults)(state) + case _ => + sys.error("received unexpected update") + } + + // Taken from rta TypePropagationAnalysis + private def candidateMatchesTypeFilter(candidateType: ReferenceType, filterType: ReferenceType): Boolean = { + val answer = classHierarchy.isASubtypeOf(candidateType, filterType) + + if (answer.isYesOrNo) { + // Here, we know for sure that the candidate type is or is not a subtype of the filter type. + answer.isYes + } else { + // If the answer is Unknown, we don't know for sure whether the candidate is a subtype of the filter type. + // However, ClassHierarchy returns Unknown even for cases where it is very unlikely that this is the case. + // Therefore, we take some more features into account to make the filtering more precise. + + // Important: This decision is a possible but unlikely cause of unsoundness in the call graph! + + // If the filter type is not a project type (i.e., it is external), we assume that any candidate type + // is a subtype. This can be any external type or project types for which we have incomplete supertype + // information. + // If the filter type IS a project type, we consider the candidate type not to be a subtype since this is + // very likely to be not the case. For the candidate type, there are two options: Either it is an external + // type, in which case the candidate type could only be a subtype if project types are available in the + // external type's project at compile time. This is very unlikely since external types are almost always + // from libraries (like the JDK) which are not available in the analysis context, and which were almost + // certainly compiled separately ("Separate Compilation Assumption"). + // The other option is that the candidate is also a project type, in which case we should have gotten a + // definitive Yes/No answer before. Since we didn't get one, the candidate type probably has a supertype + // which is not a project type. In that case, the above argument applies similarly. + + val filterTypeIsProjectType = if (filterType.isObjectType) { + project.isProjectType(filterType.asObjectType) + } else { + val at = filterType.asArrayType + project.isProjectType(at.elementType.asObjectType) + } + + !filterTypeIsProjectType + } + } +} + + +class ConfiguredNativeMethodsInstantiatedTypesAnalysisScheduler(setEntitySelector: TypeSetEntitySelector) extends BasicFPCFTriggeredAnalysisScheduler { + + override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey, DeclaredFieldsKey, VirtualFormalParametersKey) + + override def uses: Set[PropertyBounds] = + PropertyBounds.ubs(Callees, TACAI, InstantiatedTypes) + + override def derivesCollaboratively: Set[PropertyBounds] = + PropertyBounds.ubs(InstantiatedTypes) + + override def derivesEagerly: Set[PropertyBounds] = Set.empty + + override def register( + p: SomeProject, + ps: PropertyStore, + unused: Null + ): ConfiguredNativeMethodsInstantiatedTypesAnalysis = { + val analysis = new ConfiguredNativeMethodsInstantiatedTypesAnalysis(p, setEntitySelector) + + ps.registerTriggeredComputation(Callers.key, analysis.analyze) + + analysis + } + + override def triggeredBy: PropertyKind = Callers.key + +} \ No newline at end of file diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsTypePropagationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsTypePropagationState.scala new file mode 100644 index 0000000000..9d41c54a57 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsTypePropagationState.scala @@ -0,0 +1,66 @@ +package org.opalj +package tac +package fpcf +package analyses +package cg +package xta + +import org.opalj.br.ReferenceType +import org.opalj.br.fpcf.properties.Context +import org.opalj.br.fpcf.properties.cg.InstantiatedTypes +import org.opalj.collection.immutable.UIDSet +import org.opalj.fpcf.{EOptionP, SomeEOptionP} + +final class ConfiguredNativeMethodsTypePropagationState[ContextType <: Context]( + val callContext: ContextType, + val configurationData: Array[PointsToRelation], + val typeSetEntity: TypeSetEntity, + private[this] var _ownInstantiatedTypesDependee: EOptionP[TypeSetEntity, InstantiatedTypes], + ) extends BaseAnalysisState { + + + ///////////////////////////////////////////// + // // + // own types (method) // + // // + ///////////////////////////////////////////// + + def updateOwnInstantiatedTypesDependee(eps: EOptionP[TypeSetEntity, InstantiatedTypes]): Unit = { + _ownInstantiatedTypesDependee = eps + } + + def ownInstantiatedTypes: UIDSet[ReferenceType] = { + if (_ownInstantiatedTypesDependee.hasUBP) + _ownInstantiatedTypesDependee.ub.types + else + UIDSet.empty + } + + def newInstantiatedTypes(seenTypes: Int): IterableOnce[ReferenceType] = { + if (_ownInstantiatedTypesDependee.hasUBP) { + _ownInstantiatedTypesDependee.ub.dropOldest(seenTypes) + } else { + UIDSet.empty + } + } + + + ///////////////////////////////////////////// + // // + // general dependency management // + // // + ///////////////////////////////////////////// + + override def hasOpenDependencies: Boolean = { + super.hasOpenDependencies || _ownInstantiatedTypesDependee.isRefinable + } + + override def dependees: Set[SomeEOptionP] = { + var dependees = super.dependees + + dependees += _ownInstantiatedTypesDependee + + dependees + } + +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala index 7ec9fa1009..696b4587df 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/InstantiatedTypesAnalysis.scala @@ -21,7 +21,6 @@ import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.analyses.cg.ClosedPackagesKey import org.opalj.br.analyses.cg.InitialEntryPointsKey -import org.opalj.br.analyses.cg.InitialInstantiatedFieldsKey import org.opalj.br.analyses.cg.InitialInstantiatedTypesKey import org.opalj.br.fpcf.BasicFPCFTriggeredAnalysisScheduler import org.opalj.br.fpcf.ContextProviderKey @@ -303,7 +302,6 @@ class InstantiatedTypesAnalysisScheduler( val declaredFields = p.get(DeclaredFieldsKey) val entryPoints = p.get(InitialEntryPointsKey) val initialInstantiatedTypes = UIDSet[ReferenceType](p.get(InitialInstantiatedTypesKey).toSeq: _*) - val initialInstantiatedFields = p.get(InitialInstantiatedFieldsKey) // While processing entry points and fields, we keep track of all array types we see, as // well as subtypes and lower-dimensional types. These types also need to be @@ -324,23 +322,12 @@ class InstantiatedTypesAnalysisScheduler( } } - // Marks a given field as having its declared field type instantiated by default - only if field is of a - // reference type - def initializeField(field: Field, typesToConsider: UIDSet[ReferenceType]): Unit = { - val fieldSetEntity = selectSetEntity(declaredFields(field)) - initialize(fieldSetEntity, typesToConsider) - } - // Some cooperative analyses originally meant for RTA may require the global type set // to be pre-initialized. Strings and classes can be introduced via constants anywhere. // TODO Only introduce these types to the per-entity type sets where constants are used initialize(p, UIDSet(ObjectType.String, ObjectType.Class)) initialize(ExternalWorld, initialInstantiatedTypes) - // During system initialization, some native methods are called to set certain fields. - // These fields can be set as instantiated via the configuration, see InitialFieldsFinder. - initialInstantiatedFields.foreach { case (f: Field, t: UIDSet[ReferenceType]) => initializeField(f, t) } - def isRelevantArrayType(rt: Type): Boolean = rt.isArrayType && rt.asArrayType.elementType.isObjectType diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AllocationSiteBasedPointsToAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AllocationSiteBasedPointsToAnalysis.scala index 5fb231590e..c0aaccce00 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AllocationSiteBasedPointsToAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AllocationSiteBasedPointsToAnalysis.scala @@ -5,19 +5,9 @@ package fpcf package analyses package pointsto -import org.opalj.br.ReferenceType -import org.opalj.br.analyses.DeclaredFieldsKey import org.opalj.br.analyses.SomeProject -import org.opalj.br.analyses.cg.InitialInstantiatedFieldsKey -import org.opalj.br.fpcf.properties.NoContext import org.opalj.br.fpcf.properties.pointsto.AllocationSitePointsToSet -import org.opalj.br.fpcf.properties.pointsto.AllocationSitePointsToSet1 -import org.opalj.br.fpcf.properties.pointsto.NoAllocationSites -import org.opalj.br.fpcf.properties.pointsto.allocationSiteToLong -import org.opalj.fpcf.EPK -import org.opalj.fpcf.InterimEUBP import org.opalj.fpcf.PropertyMetaInformation -import org.opalj.fpcf.PropertyStore class AllocationSiteBasedPointsToAnalysis private[analyses] ( final val project: SomeProject @@ -29,30 +19,4 @@ object AllocationSiteBasedPointsToAnalysisScheduler extends AbstractPointsToAnal override val createAnalysis: SomeProject => AllocationSiteBasedPointsToAnalysis = new AllocationSiteBasedPointsToAnalysis(_) - override def init(p: SomeProject, ps: PropertyStore): Null = { - val declaredFields = p.get(DeclaredFieldsKey) - val initialFields = p.get(InitialInstantiatedFieldsKey) - - val dummyAllocationSitesPerType: Map[ReferenceType, Long] = initialFields - .flatMap(_._2) - .toSet - .map { refType: ReferenceType => (refType, allocationSiteToLong(NoContext, 0, refType)) } - .toMap - - initialFields.foreach { case (field, types) => - val fieldPointsToSet = - types.foldLeft[AllocationSitePointsToSet](NoAllocationSites) { case (result, currType) => - result.included(AllocationSitePointsToSet1(dummyAllocationSitesPerType(currType), currType)) - } - val declaredField = declaredFields(field) - - ps.preInitialize(declaredField, AllocationSitePointsToSet.key) { - case _: EPK[_, _] => InterimEUBP(declaredField, fieldPointsToSet) - case eps => throw new IllegalStateException(s"unexpected property: $eps") - } - - } - - super.init(p, ps) - } } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/TypeBasedPointsToAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/TypeBasedPointsToAnalysis.scala index 1e26e5c767..b120cd78ca 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/TypeBasedPointsToAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/TypeBasedPointsToAnalysis.scala @@ -5,14 +5,9 @@ package fpcf package analyses package pointsto -import org.opalj.br.analyses.DeclaredFieldsKey import org.opalj.br.analyses.SomeProject -import org.opalj.br.analyses.cg.InitialInstantiatedFieldsKey import org.opalj.br.fpcf.properties.pointsto.TypeBasedPointsToSet -import org.opalj.fpcf.EPK -import org.opalj.fpcf.InterimEUBP import org.opalj.fpcf.PropertyMetaInformation -import org.opalj.fpcf.PropertyStore /** * An andersen-style points-to analysis, i.e. points-to sets are modeled as subsets. @@ -40,19 +35,4 @@ object TypeBasedPointsToAnalysisScheduler extends AbstractPointsToAnalysisSchedu override val propertyKind: PropertyMetaInformation = TypeBasedPointsToSet override val createAnalysis: SomeProject => TypeBasedPointsToAnalysis = new TypeBasedPointsToAnalysis(_) - - override def init(p: SomeProject, ps: PropertyStore): Null = { - val declFields = p.get(DeclaredFieldsKey) - - p - .get(InitialInstantiatedFieldsKey) - .foreach { case (field, types) => - val declField = declFields(field) - ps.preInitialize(declField, TypeBasedPointsToSet.key) { - case _: EPK[_, _] => InterimEUBP(declField, TypeBasedPointsToSet(types)) - case eps => throw new IllegalStateException(s"unexpected property: $eps") - } - } - super.init(p, ps) - } } From 9f1bd02ba169c1cb1c30850a107549128c36411c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Tue, 17 Dec 2024 15:44:11 +0100 Subject: [PATCH 08/10] Formatting --- .../cg/PropagationBasedCallGraphKeys.scala | 2 +- ...tiveMethodsInstantiatedTypesAnalysis.scala | 146 ++++++++++++------ ...redNativeMethodsTypePropagationState.scala | 15 +- 3 files changed, 106 insertions(+), 57 deletions(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/cg/PropagationBasedCallGraphKeys.scala b/OPAL/tac/src/main/scala/org/opalj/tac/cg/PropagationBasedCallGraphKeys.scala index d1c1a478ef..9b57e062c0 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/cg/PropagationBasedCallGraphKeys.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/cg/PropagationBasedCallGraphKeys.scala @@ -9,8 +9,8 @@ import org.opalj.br.analyses.cg.InitialInstantiatedTypesKey import org.opalj.br.fpcf.FPCFAnalysisScheduler import org.opalj.br.fpcf.properties.SimpleContextsKey import org.opalj.tac.fpcf.analyses.cg.PropagationBasedTypeIterator -import org.opalj.tac.fpcf.analyses.cg.xta.ConfiguredNativeMethodsInstantiatedTypesAnalysisScheduler import org.opalj.tac.fpcf.analyses.cg.xta.ArrayInstantiationsAnalysisScheduler +import org.opalj.tac.fpcf.analyses.cg.xta.ConfiguredNativeMethodsInstantiatedTypesAnalysisScheduler import org.opalj.tac.fpcf.analyses.cg.xta.CTASetEntitySelector import org.opalj.tac.fpcf.analyses.cg.xta.FTASetEntitySelector import org.opalj.tac.fpcf.analyses.cg.xta.InstantiatedTypesAnalysisScheduler diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala index 15792d5b8a..6ee0721107 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala @@ -5,34 +5,58 @@ package analyses package cg package xta -import org.opalj.br.{DeclaredMethod, FieldType, Method, ObjectType, ReferenceType} -import org.opalj.br.analyses.{DeclaredFields, DeclaredFieldsKey, DeclaredMethodsKey, ProjectInformationKeys, SomeProject, VirtualFormalParametersKey} +import scala.collection.mutable.ArrayBuffer + +import org.opalj.br.DeclaredMethod +import org.opalj.br.FieldType +import org.opalj.br.Method +import org.opalj.br.ObjectType +import org.opalj.br.ReferenceType +import org.opalj.br.analyses.DeclaredFields +import org.opalj.br.analyses.DeclaredFieldsKey +import org.opalj.br.analyses.DeclaredMethodsKey +import org.opalj.br.analyses.ProjectInformationKeys +import org.opalj.br.analyses.SomeProject +import org.opalj.br.analyses.VirtualFormalParametersKey import org.opalj.br.fpcf.BasicFPCFTriggeredAnalysisScheduler -import org.opalj.br.fpcf.properties.cg.{Callees, Callers, InstantiatedTypes} +import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.br.fpcf.properties.cg.Callers +import org.opalj.br.fpcf.properties.cg.InstantiatedTypes import org.opalj.collection.immutable.UIDSet -import org.opalj.fpcf.{EPS, EUBP, InterimPartialResult, PartialResult, ProperPropertyComputationResult, PropertyBounds, PropertyKind, PropertyStore, Results, SomeEPS, SomePartialResult} +import org.opalj.fpcf.EPS +import org.opalj.fpcf.EUBP +import org.opalj.fpcf.InterimPartialResult +import org.opalj.fpcf.PartialResult +import org.opalj.fpcf.ProperPropertyComputationResult +import org.opalj.fpcf.PropertyBounds +import org.opalj.fpcf.PropertyKind +import org.opalj.fpcf.PropertyStore +import org.opalj.fpcf.Results +import org.opalj.fpcf.SomeEPS +import org.opalj.fpcf.SomePartialResult import org.opalj.log.OPALLogger -import org.opalj.tac.fpcf.analyses.{AllocationSiteDescription, ConfiguredMethods, MethodDescription, PointsToRelation, StaticFieldDescription} +import org.opalj.tac.fpcf.analyses.AllocationSiteDescription +import org.opalj.tac.fpcf.analyses.ConfiguredMethods +import org.opalj.tac.fpcf.analyses.MethodDescription +import org.opalj.tac.fpcf.analyses.PointsToRelation +import org.opalj.tac.fpcf.analyses.StaticFieldDescription import org.opalj.tac.fpcf.properties.TACAI -import scala.collection.mutable.ArrayBuffer - /** * Handles the effect of certain (configured native methods) to the set of instantiated types. * * @author Johannes Düsing */ class ConfiguredNativeMethodsInstantiatedTypesAnalysis private[analyses] ( - final val project: SomeProject, - final val typeSetEntitySelector: TypeSetEntitySelector, - ) extends ReachableMethodAnalysis { + final val project: SomeProject, + final val typeSetEntitySelector: TypeSetEntitySelector +) extends ReachableMethodAnalysis { private[this] val declaredFields: DeclaredFields = p.get(DeclaredFieldsKey) private[this] val virtualFormalParameters = project.get(VirtualFormalParametersKey) private type State = ConfiguredNativeMethodsTypePropagationState[ContextType] - // TODO remove dependency to classes in pointsto package private[this] val nativeMethodData: Map[DeclaredMethod, Array[PointsToRelation]] = ConfiguredMethods @@ -46,7 +70,7 @@ class ConfiguredNativeMethodsInstantiatedTypesAnalysis private[analyses] ( override final val processesMethodsWithoutBody: Boolean = true override def processMethod(callContext: ContextType, tacEP: EPS[Method, TACAI]): ProperPropertyComputationResult = { - if(!nativeMethodData.contains(callContext.method)) { + if (!nativeMethodData.contains(callContext.method)) { // We have nothing to contribute to this method return Results() } @@ -56,19 +80,19 @@ class ConfiguredNativeMethodsInstantiatedTypesAnalysis private[analyses] ( val typeSetEntity = typeSetEntitySelector(callContext.method) val instantiatedTypesEOptP = propertyStore(typeSetEntity, InstantiatedTypes.key) - implicit val state: ConfiguredNativeMethodsTypePropagationState[ContextType] = new ConfiguredNativeMethodsTypePropagationState( - callContext, - configuredData, - typeSetEntity, - instantiatedTypesEOptP - ) + implicit val state: ConfiguredNativeMethodsTypePropagationState[ContextType] = + new ConfiguredNativeMethodsTypePropagationState( + callContext, + configuredData, + typeSetEntity, + instantiatedTypesEOptP + ) implicit val partialResults: ArrayBuffer[SomePartialResult] = ArrayBuffer.empty[SomePartialResult] processParameterAssignments(state.ownInstantiatedTypes) processStaticConfigurations - returnResults(partialResults) } @@ -80,27 +104,40 @@ class ConfiguredNativeMethodsInstantiatedTypesAnalysis private[analyses] ( val fieldSetEntity = typeSetEntitySelector(theField) - if(allocatedType.isReferenceType && theField.fieldType.isReferenceType && - candidateMatchesTypeFilter(allocatedType.asReferenceType, theField.fieldType.asReferenceType)){ + if (allocatedType.isReferenceType && theField.fieldType.isReferenceType && + candidateMatchesTypeFilter(allocatedType.asReferenceType, theField.fieldType.asReferenceType) + ) { partialResults += PartialResult[TypeSetEntity, InstantiatedTypes]( - fieldSetEntity, InstantiatedTypes.key, InstantiatedTypes.update(fieldSetEntity, UIDSet(allocatedType.asReferenceType)) + fieldSetEntity, + InstantiatedTypes.key, + InstantiatedTypes.update(fieldSetEntity, UIDSet(allocatedType.asReferenceType)) ) } else { - OPALLogger.warn("project configuration", s"configured points to data is invalid for ${state.callContext.method.toJava}") + OPALLogger.warn( + "project configuration", + s"configured points to data is invalid for ${state.callContext.method.toJava}" + ) } case PointsToRelation(MethodDescription(cf, name, desc), asd: AllocationSiteDescription) => val theMethod = state.callContext.method - if(theMethod.declaringClassType.fqn != cf || theMethod.name != name || theMethod.descriptor.toJVMDescriptor != desc){ - OPALLogger.warn("project configuration", s"configured points to data is invalid for ${state.callContext.method.toJava}") + if ( + theMethod.declaringClassType.fqn != cf || theMethod.name != name || theMethod.descriptor.toJVMDescriptor != desc + ) { + OPALLogger.warn( + "project configuration", + s"configured points to data is invalid for ${state.callContext.method.toJava}" + ) } else { val allocatedType = FieldType(asd.instantiatedType) val methodSetEntity = state.typeSetEntity - if(allocatedType.isReferenceType){ + if (allocatedType.isReferenceType) { partialResults += PartialResult[TypeSetEntity, InstantiatedTypes]( - methodSetEntity, InstantiatedTypes.key, InstantiatedTypes.update(methodSetEntity, UIDSet(allocatedType.asReferenceType)) + methodSetEntity, + InstantiatedTypes.key, + InstantiatedTypes.update(methodSetEntity, UIDSet(allocatedType.asReferenceType)) ) } } @@ -109,7 +146,10 @@ class ConfiguredNativeMethodsInstantiatedTypesAnalysis private[analyses] ( } } - private def processParameterAssignments(typesToConsider: UIDSet[ReferenceType])(implicit state: State, partialResults: ArrayBuffer[SomePartialResult]): Unit = { + private def processParameterAssignments(typesToConsider: UIDSet[ReferenceType])(implicit + state: State, + partialResults: ArrayBuffer[SomePartialResult] + ): Unit = { state.configurationData.foreach { case PointsToRelation(StaticFieldDescription(cf, name, fieldType), pd: ParameterDescription) => @@ -117,33 +157,42 @@ class ConfiguredNativeMethodsInstantiatedTypesAnalysis private[analyses] ( val fieldSetEntity = typeSetEntitySelector(theField) val theParameter = pd.fp(state.callContext.method, virtualFormalParameters) - val theParameterType = if(theParameter.origin == -1) { + val theParameterType = if (theParameter.origin == -1) { ObjectType(pd.cf) } else { val paramIdx = -theParameter.origin - 2 state.callContext.method.descriptor.parameterType(paramIdx) } - if(theField.fieldType.isReferenceType && theParameterType.isReferenceType && - candidateMatchesTypeFilter(theParameterType.asReferenceType, theField.fieldType.asReferenceType)){ - val filteredTypes = typesToConsider.foldLeft(UIDSet.newBuilder[ReferenceType]) { (builder, newType) => - if(candidateMatchesTypeFilter(newType, theParameterType.asReferenceType)){ - builder += newType - } - builder - }.result() + if (theField.fieldType.isReferenceType && theParameterType.isReferenceType && + candidateMatchesTypeFilter(theParameterType.asReferenceType, theField.fieldType.asReferenceType) + ) { + val filteredTypes = + typesToConsider.foldLeft(UIDSet.newBuilder[ReferenceType]) { (builder, newType) => + if (candidateMatchesTypeFilter(newType, theParameterType.asReferenceType)) { + builder += newType + } + builder + }.result() partialResults += PartialResult[TypeSetEntity, InstantiatedTypes]( - fieldSetEntity, InstantiatedTypes.key, InstantiatedTypes.update(fieldSetEntity, filteredTypes) + fieldSetEntity, + InstantiatedTypes.key, + InstantiatedTypes.update(fieldSetEntity, filteredTypes) ) } else { - OPALLogger.warn("project configuration", s"configured points to data is invalid for ${state.callContext.method.toJava}") + OPALLogger.warn( + "project configuration", + s"configured points to data is invalid for ${state.callContext.method.toJava}" + ) } case _ => } } - private def returnResults(partialResults: IterableOnce[SomePartialResult])(implicit state: State): ProperPropertyComputationResult = { + private def returnResults(partialResults: IterableOnce[SomePartialResult])(implicit + state: State + ): ProperPropertyComputationResult = { // Always re-register the continuation. It is impossible for all dependees to be final in XTA/... Results( InterimPartialResult(state.dependees, c(state)), @@ -208,10 +257,11 @@ class ConfiguredNativeMethodsInstantiatedTypesAnalysis private[analyses] ( } } +class ConfiguredNativeMethodsInstantiatedTypesAnalysisScheduler(setEntitySelector: TypeSetEntitySelector) + extends BasicFPCFTriggeredAnalysisScheduler { -class ConfiguredNativeMethodsInstantiatedTypesAnalysisScheduler(setEntitySelector: TypeSetEntitySelector) extends BasicFPCFTriggeredAnalysisScheduler { - - override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey, DeclaredFieldsKey, VirtualFormalParametersKey) + override def requiredProjectInformation: ProjectInformationKeys = + Seq(DeclaredMethodsKey, DeclaredFieldsKey, VirtualFormalParametersKey) override def uses: Set[PropertyBounds] = PropertyBounds.ubs(Callees, TACAI, InstantiatedTypes) @@ -222,10 +272,10 @@ class ConfiguredNativeMethodsInstantiatedTypesAnalysisScheduler(setEntitySelecto override def derivesEagerly: Set[PropertyBounds] = Set.empty override def register( - p: SomeProject, - ps: PropertyStore, - unused: Null - ): ConfiguredNativeMethodsInstantiatedTypesAnalysis = { + p: SomeProject, + ps: PropertyStore, + unused: Null + ): ConfiguredNativeMethodsInstantiatedTypesAnalysis = { val analysis = new ConfiguredNativeMethodsInstantiatedTypesAnalysis(p, setEntitySelector) ps.registerTriggeredComputation(Callers.key, analysis.analyze) @@ -235,4 +285,4 @@ class ConfiguredNativeMethodsInstantiatedTypesAnalysisScheduler(setEntitySelecto override def triggeredBy: PropertyKind = Callers.key -} \ No newline at end of file +} diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsTypePropagationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsTypePropagationState.scala index 9d41c54a57..2a4a307517 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsTypePropagationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsTypePropagationState.scala @@ -9,15 +9,15 @@ import org.opalj.br.ReferenceType import org.opalj.br.fpcf.properties.Context import org.opalj.br.fpcf.properties.cg.InstantiatedTypes import org.opalj.collection.immutable.UIDSet -import org.opalj.fpcf.{EOptionP, SomeEOptionP} +import org.opalj.fpcf.EOptionP +import org.opalj.fpcf.SomeEOptionP final class ConfiguredNativeMethodsTypePropagationState[ContextType <: Context]( - val callContext: ContextType, - val configurationData: Array[PointsToRelation], - val typeSetEntity: TypeSetEntity, - private[this] var _ownInstantiatedTypesDependee: EOptionP[TypeSetEntity, InstantiatedTypes], - ) extends BaseAnalysisState { - + val callContext: ContextType, + val configurationData: Array[PointsToRelation], + val typeSetEntity: TypeSetEntity, + private[this] var _ownInstantiatedTypesDependee: EOptionP[TypeSetEntity, InstantiatedTypes] +) extends BaseAnalysisState { ///////////////////////////////////////////// // // @@ -44,7 +44,6 @@ final class ConfiguredNativeMethodsTypePropagationState[ContextType <: Context]( } } - ///////////////////////////////////////////// // // // general dependency management // From 175f9147be6d2287a0e8890f9ed3a62c3c875022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Tue, 17 Dec 2024 15:48:27 +0100 Subject: [PATCH 09/10] Add headers to new files, remove unwanted extra whitespace --- .../xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala | 1 + .../cg/xta/ConfiguredNativeMethodsTypePropagationState.scala | 1 + .../analyses/pointsto/AllocationSiteBasedPointsToAnalysis.scala | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala index 6ee0721107..f559be20d5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala @@ -1,3 +1,4 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj package tac package fpcf diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsTypePropagationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsTypePropagationState.scala index 2a4a307517..0e7a7e357a 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsTypePropagationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsTypePropagationState.scala @@ -1,3 +1,4 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj package tac package fpcf diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AllocationSiteBasedPointsToAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AllocationSiteBasedPointsToAnalysis.scala index c0aaccce00..659f584fab 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AllocationSiteBasedPointsToAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/pointsto/AllocationSiteBasedPointsToAnalysis.scala @@ -18,5 +18,4 @@ object AllocationSiteBasedPointsToAnalysisScheduler extends AbstractPointsToAnal override val propertyKind: PropertyMetaInformation = AllocationSitePointsToSet override val createAnalysis: SomeProject => AllocationSiteBasedPointsToAnalysis = new AllocationSiteBasedPointsToAnalysis(_) - } From 98f2fa5e75e1d1fb91466a0fdc11d3c1c040b754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20D=C3=BCsing?= Date: Thu, 19 Dec 2024 11:22:11 +0100 Subject: [PATCH 10/10] Make CG framework process empty entry points. Adapt XTA propagation analysis to work for empty methods as well - same for configured call graph analysis. Currently this breaks CFA, needs to be adapted next --- .../br/analyses/cg/EntryPointFinder.scala | 9 --- OPAL/tac/src/main/resources/reference.conf | 16 ++-- ...iguredNativeMethodsCallGraphAnalysis.scala | 4 +- .../cg/xta/TypePropagationAnalysis.scala | 78 +++++++++++-------- .../cg/xta/TypePropagationState.scala | 7 +- .../cg/xta/TypePropagationTrace.scala | 12 +-- 6 files changed, 66 insertions(+), 60 deletions(-) diff --git a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/EntryPointFinder.scala b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/EntryPointFinder.scala index 97016fb71d..59e51cbb3a 100644 --- a/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/EntryPointFinder.scala +++ b/OPAL/br/src/main/scala/org/opalj/br/analyses/cg/EntryPointFinder.scala @@ -252,15 +252,6 @@ trait ConfigurationEntryPointsFinder extends EntryPointFinder { ) } - if (methods.exists(_.body.isEmpty)) { - OPALLogger.warn( - "project configuration", - s"$typeName has an empty method $name); " + - "entry point ignored" - ) - methods = methods.filter(_.body.isDefined) - } - entryPoints = entryPoints ++ methods case None if !isSubtype => diff --git a/OPAL/tac/src/main/resources/reference.conf b/OPAL/tac/src/main/resources/reference.conf index 7711453c22..0c41e735d8 100644 --- a/OPAL/tac/src/main/resources/reference.conf +++ b/OPAL/tac/src/main/resources/reference.conf @@ -1367,39 +1367,39 @@ org.opalj { { cf = "java/lang/System", name = "newPrintStream", - desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream" + desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream;" } ] }, { cf = "java/lang/System", name = "newPrintStream", - desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream", + desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream;", pointsTo = [ { lhs = { cf = "java/lang/System", name = "newPrintStream", - desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream" + desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream;" }, rhs = { cf = "java/lang/System", name = "newPrintStream", - desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream", - instantiatedType = "Ljava/io/PrintStream" + desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream;", + instantiatedType = "Ljava/io/PrintStream;" } }, { lhs = { cf = "java/lang/System", name = "newPrintStream", - desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream" + desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream;" }, rhs = { cf = "java/lang/System", name = "newPrintStream", - desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream", - instantiatedType = "Ljava/io/BufferedOutputStream" + desc = "(Ljava/io/OutputStream;Ljava/lang/String;)Ljava/io/PrintStream;", + instantiatedType = "Ljava/io/BufferedOutputStream;" } } ] diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/ConfiguredNativeMethodsCallGraphAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/ConfiguredNativeMethodsCallGraphAnalysis.scala index c8ce83bde6..d1cb7049f1 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/ConfiguredNativeMethodsCallGraphAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/ConfiguredNativeMethodsCallGraphAnalysis.scala @@ -82,9 +82,7 @@ class ConfiguredNativeMethodsCallGraphAnalysis private[analyses] ( // we only allow defined methods if (!dm.hasSingleDefinedMethod) return NoResult - val method = dm.definedMethod - - if (!method.isNative || !nativeMethodData.contains(dm)) { + if (!nativeMethodData.contains(dm)) { return NoResult; } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationAnalysis.scala index c9d7318881..aab591f13d 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationAnalysis.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationAnalysis.scala @@ -12,7 +12,6 @@ import scala.jdk.CollectionConverters._ import org.opalj.br.Code import org.opalj.br.DeclaredMethod import org.opalj.br.DefinedField -import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.ObjectType import org.opalj.br.ReferenceType @@ -65,21 +64,25 @@ final class TypePropagationAnalysis private[analyses] ( private[this] implicit val declaredFields: DeclaredFields = project.get(DeclaredFieldsKey) + // We need to also propagate types if the method has no body, e.g. for native methods with configured data + override protected val processesMethodsWithoutBody: Boolean = true + override def processMethod( callContext: ContextType, tacEP: EPS[Method, TACAI] ): ProperPropertyComputationResult = { - val definedMethod = callContext.method.asDefinedMethod - val method = definedMethod.definedMethod + val methodOpt = if (callContext.method.hasSingleDefinedMethod) { + Some(callContext.method.definedMethod) + } else None - val typeSetEntity = selectTypeSetEntity(definedMethod) + val typeSetEntity = selectTypeSetEntity(callContext.method) val instantiatedTypesEOptP = propertyStore(typeSetEntity, InstantiatedTypes.key) - val calleesEOptP = propertyStore(definedMethod, Callees.key) - val readAccessEOptP = propertyStore(method, MethodFieldReadAccessInformation.key) - val writeAccessEOptP = propertyStore(method, MethodFieldWriteAccessInformation.key) + val calleesEOptP = propertyStore(callContext.method, Callees.key) + val readAccessEOptP = methodOpt.map(m => propertyStore(m, MethodFieldReadAccessInformation.key)).orNull + val writeAccessEOptP = methodOpt.map(m => propertyStore(m, MethodFieldWriteAccessInformation.key)).orNull - if (debug) _trace.traceInit(definedMethod) + if (debug) _trace.traceInit(callContext.method) implicit val state: TypePropagationState[ContextType] = new TypePropagationState( callContext, @@ -94,12 +97,15 @@ final class TypePropagationAnalysis private[analyses] ( if (calleesEOptP.hasUBP) processCallees(calleesEOptP.ub) - if (readAccessEOptP.hasUBP) + if (readAccessEOptP != null && readAccessEOptP.hasUBP) processReadAccesses(readAccessEOptP.ub) - if (writeAccessEOptP.hasUBP) + if (writeAccessEOptP != null && writeAccessEOptP.hasUBP) processWriteAccesses(writeAccessEOptP.ub) - processTACStatements - processArrayTypes(state.ownInstantiatedTypes) + + if (state.methodHasBody) { + processTACStatements + processArrayTypes(state.ownInstantiatedTypes) + } returnResults(partialResults.iterator) } @@ -122,7 +128,7 @@ final class TypePropagationAnalysis private[analyses] ( private def c(state: State)(eps: SomeEPS): ProperPropertyComputationResult = eps match { - case EUBP(e: DefinedMethod, _: Callees) => + case EUBP(e: DeclaredMethod, _: Callees) => if (debug) { assert(e == state.callContext.method) _trace.traceCalleesUpdate(e) @@ -255,7 +261,7 @@ final class TypePropagationAnalysis private[analyses] ( state: State, partialResults: ArrayBuffer[SomePartialResult] ): Unit = { - val bytecode = state.callContext.method.definedMethod.body.get + val bytecodeOpt = if (state.methodHasBody) Some(state.callContext.method.definedMethod.body.get) else None for { pc <- callees.callSitePCs(state.callContext) calleeContext <- callees.callees(state.callContext, pc) @@ -273,16 +279,16 @@ final class TypePropagationAnalysis private[analyses] ( // Remember callee (with PC) so we don't have to process it again later. state.addSeenCallee(pc, callee) - maybeRegisterMethodForForwardPropagation(callee, pc, bytecode) + maybeRegisterMethodForForwardPropagation(callee, pc, bytecodeOpt) - maybeRegisterMethodForBackwardPropagation(callee, pc, bytecode) + maybeRegisterMethodForBackwardPropagation(callee, pc, bytecodeOpt) } } private def processReadAccesses( readAccesses: MethodFieldReadAccessInformation )(implicit state: State, partialResults: ArrayBuffer[SomePartialResult]): Unit = { - val bytecode = state.callContext.method.definedMethod.body.get + val bytecodeOpt = if (state.methodHasBody) Some(state.callContext.method.definedMethod.body.get) else None for { pc <- readAccesses.getAccessSites(state.callContext) numDirectAccess = readAccesses.numDirectAccesses(state.callContext, pc) @@ -298,10 +304,11 @@ final class TypePropagationAnalysis private[analyses] ( // Internally, generic fields have type "Object" due to type erasure. In many cases // (but not all!), the Java compiler will place the "actual" return type within a checkcast // instruction right after the field read instruction. - val nextInstruction = bytecode.instructions(bytecode.pcOfNextInstruction(pc)) + val nextInstructionOpt = + bytecodeOpt.map(bytecode => bytecode.instructions(bytecode.pcOfNextInstruction(pc))) val mostPreciseFieldType = - if (nextInstruction.isCheckcast) - nextInstruction.asInstanceOf[CHECKCAST].referenceType + if (nextInstructionOpt.isDefined && nextInstructionOpt.get.isCheckcast) + nextInstructionOpt.get.asInstanceOf[CHECKCAST].referenceType else declaredField.fieldType.asReferenceType @@ -353,9 +360,9 @@ final class TypePropagationAnalysis private[analyses] ( } private def maybeRegisterMethodForForwardPropagation( - callee: DeclaredMethod, - pc: Int, - bytecode: Code + callee: DeclaredMethod, + pc: Int, + bytecodeOpt: Option[Code] )( implicit state: State, @@ -371,7 +378,9 @@ final class TypePropagationAnalysis private[analyses] ( // If the call is not static, we need to take the implicit "this" parameter into account. if (callee.hasSingleDefinedMethod && !callee.definedMethod.isStatic || - !callee.hasSingleDefinedMethod && !bytecode.instructions(pc).isInvokeStatic + // If we can't ask the target if it is static, we look in the bytecode. If we do not have any bytecode, + // we soundly assume that the call might be virtual, and we need to add "this". + !callee.hasSingleDefinedMethod && !bytecodeOpt.exists(_.instructions(pc).isInvokeStatic) ) { params += callee.declaringClassType } @@ -386,15 +395,17 @@ final class TypePropagationAnalysis private[analyses] ( } private def maybeRegisterMethodForBackwardPropagation( - callee: DeclaredMethod, - pc: Int, - bytecode: Code + callee: DeclaredMethod, + pc: Int, + bytecodeOpt: Option[Code] )( implicit state: State, partialResults: ArrayBuffer[SomePartialResult] ): Unit = { - val returnValueIsUsed = { + // If the method body is not available, we have to assume the return value might be used + val returnValueIsUsed = if (!state.methodHasBody) true + else { val tacIndex = state.tac.properStmtIndexForPC(pc) val tacInstr = state.tac.instructions(tacIndex) tacInstr.isAssignment @@ -405,10 +416,13 @@ final class TypePropagationAnalysis private[analyses] ( // (but not all!), the Java compiler will place the "actual" return type within a checkcast // instruction right after the call. val mostPreciseReturnType = { - val nextPc = bytecode.pcOfNextInstruction(pc) - val nextInstruction = bytecode.instructions(nextPc) - if (nextInstruction.isCheckcast) { - nextInstruction.asInstanceOf[CHECKCAST].referenceType + val nextInstructionOpt = bytecodeOpt.map { bytecode => + val nextPc = bytecode.pcOfNextInstruction(pc) + bytecode.instructions(nextPc) + } + // If method body is not available, we will assume the callee descriptor return type as well + if (nextInstructionOpt.exists(_.isCheckcast)) { + nextInstructionOpt.get.asInstanceOf[CHECKCAST].referenceType } else { callee.descriptor.returnType } diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationState.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationState.scala index 034254843f..5656b737e5 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationState.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationState.scala @@ -50,6 +50,9 @@ final class TypePropagationState[ContextType <: Context]( var methodWritesArrays: Boolean = false var methodReadsArrays: Boolean = false + def methodHasBody: Boolean = + callContext.method.hasSingleDefinedMethod && callContext.method.definedMethod.body.isDefined + ///////////////////////////////////////////// // // // own types (method) // @@ -114,7 +117,7 @@ final class TypePropagationState[ContextType <: Context]( var seenIndirectWriteAccesses: IntMap[Int] = IntMap.empty def readAccessDependee: Option[EOptionP[Method, MethodFieldReadAccessInformation]] = { - if (_readAccessDependee.isRefinable) Some(_readAccessDependee) else None + if (_readAccessDependee != null && _readAccessDependee.isRefinable) Some(_readAccessDependee) else None } def updateReadAccessDependee(readAccessDependee: EOptionP[Method, MethodFieldReadAccessInformation]): Unit = { @@ -122,7 +125,7 @@ final class TypePropagationState[ContextType <: Context]( } def writeAccessDependee: Option[EOptionP[Method, MethodFieldWriteAccessInformation]] = { - if (_writeAccessDependee.isRefinable) Some(_writeAccessDependee) else None + if (_writeAccessDependee != null && _writeAccessDependee.isRefinable) Some(_writeAccessDependee) else None } def updateWriteAccessDependee(writeAccessDependee: EOptionP[Method, MethodFieldWriteAccessInformation]): Unit = { diff --git a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationTrace.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationTrace.scala index 376490ed97..5419694008 100644 --- a/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationTrace.scala +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/TypePropagationTrace.scala @@ -59,7 +59,7 @@ private[xta] class TypePropagationTrace { } private def simplifiedName(e: Any): String = e match { - case defM: DefinedMethod => s"${simplifiedName(defM.declaringClassType)}.${defM.name}(...)" + case defM: DeclaredMethod => s"${simplifiedName(defM.declaringClassType)}.${defM.name}(...)" case m: Method => s"${simplifiedName(m.classFile.thisType)}.${m.name}(...)" case rt: ReferenceType => rt.toJava.substring(rt.toJava.lastIndexOf('.') + 1) case _ => e.toString @@ -67,7 +67,7 @@ private[xta] class TypePropagationTrace { @elidable(elidable.ASSERTION) def traceInit( - method: DefinedMethod + method: DeclaredMethod )(implicit ps: PropertyStore, typeIterator: TypeIterator): Unit = { val initialTypes = { val typeEOptP = ps(method, InstantiatedTypes.key) @@ -78,16 +78,16 @@ private[xta] class TypePropagationTrace { val calleesEOptP = ps(method, Callees.key) if (calleesEOptP.hasUBP) calleesEOptP.ub.callSites(typeIterator.newContext(method)).flatMap(_._2) - else Iterator.empty + else Iterator.empty[Context] } traceMsg( - s"init: ${simplifiedName(method)} (initial types: {${initialTypes.map(simplifiedName).mkString(", ")}}, initial callees: {${initialCallees.map(simplifiedName).mkString(", ")}})" + s"init: ${simplifiedName(method)} (initial types: {${initialTypes.map(simplifiedName).mkString(", ")}}, initial callees: {${initialCallees.map(c => simplifiedName(c)).mkString(", ")}})" ) _trace.events += TypePropagationTrace.Init(method, initialTypes, initialCallees.toSet) } @elidable(elidable.ASSERTION) - def traceCalleesUpdate(receiver: DefinedMethod): Unit = { + def traceCalleesUpdate(receiver: DeclaredMethod): Unit = { traceMsg(s"callee property update: ${simplifiedName(receiver)}") _trace.events += TypePropagationTrace.CalleesUpdate(receiver) } @@ -125,7 +125,7 @@ object TypePropagationTrace { trait Event { val typePropagations: mutable.ArrayBuffer[TypePropagation] = new mutable.ArrayBuffer[TypePropagation]() } - case class Init(method: DefinedMethod, initialTypes: UIDSet[ReferenceType], initialCallees: Set[Context]) + case class Init(method: DeclaredMethod, initialTypes: UIDSet[ReferenceType], initialCallees: Set[Context]) extends Event trait UpdateEvent extends Event case class TypeSetUpdate(receiver: Entity, source: Entity, sourceTypes: UIDSet[ReferenceType]) extends UpdateEvent