diff --git a/OPAL/br/src/main/resources/reference.conf b/OPAL/br/src/main/resources/reference.conf index c1dd17039f..9d24c92c2a 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,8 @@ org.opalj { #analysis = "org.opalj.br.analyses.cg.LibraryInstantiatedTypesFinder" instantiatedTypes = [ - + "java/lang/ClassLoader", + "java/lang/Thread" ] } } 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 5c87b632ec..ca9704c7a3 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 @@ -258,15 +258,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 2827c51178..265274ec08 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..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.rta.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 @@ -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/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/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala new file mode 100644 index 0000000000..ecd9c83813 --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsInstantiatedTypesAnalysis.scala @@ -0,0 +1,295 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +package org.opalj +package tac +package fpcf +package analyses +package cg +package xta + +import scala.collection.mutable.ArrayBuffer + +import org.opalj.br.ClassType +import org.opalj.br.DeclaredMethod +import org.opalj.br.FieldType +import org.opalj.br.Method +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 +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 +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 +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 + +/** + * 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 def processMethodWithoutBody(callContext: ContextType): ProperPropertyComputationResult = { + processMethodInternal(callContext) + } + + override def processMethod(callContext: ContextType, tacEP: EPS[Method, TACAI]): ProperPropertyComputationResult = { + processMethodInternal(callContext) + } + + private def processMethodInternal(callContext: ContextType): 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(ClassType(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(ClassType(cf), name, FieldType(fieldType)) + val fieldSetEntity = typeSetEntitySelector(theField) + val theParameter = pd.fp(state.callContext.method, virtualFormalParameters) + + val theParameterType = if (theParameter.origin == -1) { + ClassType(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.isClassType) { + project.isProjectType(filterType.asClassType) + } else { + val at = filterType.asArrayType + project.isProjectType(at.elementType.asClassType) + } + + !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 + +} 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..0e7a7e357a --- /dev/null +++ b/OPAL/tac/src/main/scala/org/opalj/tac/fpcf/analyses/cg/xta/ConfiguredNativeMethodsTypePropagationState.scala @@ -0,0 +1,66 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ +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 +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 { + + ///////////////////////////////////////////// + // // + // 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 d110284f82..371a71cbb1 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 @@ -326,6 +326,7 @@ class InstantiatedTypesAnalysisScheduler( // 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(ClassType.String, ClassType.Class)) + initialize(ExternalWorld, initialInstantiatedTypes) def isRelevantArrayType(rt: Type): Boolean = rt.isArrayType && rt.asArrayType.elementType.isClassType 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 c22d363d2c..9514f86e1a 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 @@ -13,7 +13,6 @@ import org.opalj.br.ClassType 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.ReferenceType import org.opalj.br.analyses.DeclaredFields @@ -32,6 +31,7 @@ import org.opalj.collection.immutable.UIDSet import org.opalj.fpcf.Entity import org.opalj.fpcf.EPS import org.opalj.fpcf.EUBP +import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimPartialResult import org.opalj.fpcf.PartialResult import org.opalj.fpcf.ProperPropertyComputationResult @@ -42,6 +42,7 @@ import org.opalj.fpcf.Results import org.opalj.fpcf.SomeEPS import org.opalj.fpcf.SomePartialResult import org.opalj.tac.cg.TypeIteratorKey +import org.opalj.tac.fpcf.properties.NoTACAI import org.opalj.tac.fpcf.properties.TACAI /** @@ -65,21 +66,27 @@ 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. + // Those methods set the TAC EPS to null. Access to state.tac will always be guarded by if(state.methodHasBody). + override def processMethodWithoutBody(callContext: ContextType): ProperPropertyComputationResult = + processMethod(callContext, FinalEP(null, NoTACAI)) + 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 +101,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 +132,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 +265,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) @@ -275,16 +285,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) @@ -300,10 +310,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 @@ -355,9 +366,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, @@ -373,7 +384,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 } @@ -388,15 +401,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 @@ -407,10 +422,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..76498bcf87 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 @@ -15,7 +15,6 @@ import java.time.Instant import scala.collection.mutable import org.opalj.br.DeclaredMethod -import org.opalj.br.DefinedMethod import org.opalj.br.Method import org.opalj.br.ReferenceType import org.opalj.br.fpcf.properties.Context @@ -59,15 +58,15 @@ private[xta] class TypePropagationTrace { } private def simplifiedName(e: Any): String = e match { - case defM: DefinedMethod => 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 + 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 } @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 +77,18 @@ 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(simplifiedName).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 +126,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