/* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj package tac package fpcf package analyses package cg import scala.annotation.tailrec import org.opalj.br.ClassType import org.opalj.br.DeclaredMethod import org.opalj.br.ElementReferenceType import org.opalj.br.Method import org.opalj.br.MethodDescriptor import org.opalj.br.MethodDescriptor.JustReturnsObject import org.opalj.br.MethodDescriptor.NoArgsAndReturnVoid import org.opalj.br.ReferenceType import org.opalj.br.analyses.DeclaredMethodsKey import org.opalj.br.analyses.ProjectInformationKeys import org.opalj.br.analyses.SomeProject import org.opalj.br.fpcf.BasicFPCFEagerAnalysisScheduler import org.opalj.br.fpcf.FPCFAnalysis import org.opalj.br.fpcf.properties.cg.Callees import org.opalj.br.fpcf.properties.cg.Callers import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.Entity import org.opalj.fpcf.EPS import org.opalj.fpcf.FinalEP import org.opalj.fpcf.InterimPartialResult import org.opalj.fpcf.ProperPropertyComputationResult import org.opalj.fpcf.PropertyBounds import org.opalj.fpcf.PropertyComputationResult import org.opalj.fpcf.PropertyStore import org.opalj.fpcf.Results import org.opalj.fpcf.SomeEPS import org.opalj.tac.cg.TypeIteratorKey import org.opalj.tac.fpcf.properties.TACAI import org.opalj.tac.fpcf.properties.TheTACAI import org.opalj.value.ASObjectValue import org.opalj.value.ValueInformation /** * Analysis handling the specifics of java.io.ObjectOutputStream.writeObject. * This method may invoke writeObject, writeReplace or writeExternal on its parameter. * * @author Florian Kuebler * @author Dominik Helm */ class OOSWriteObjectAnalysis private[analyses] ( override val project: SomeProject ) extends TACAIBasedAPIBasedAnalysis with TypeConsumerAnalysis { override val apiMethod: DeclaredMethod = declaredMethods( ClassType.ObjectOutputStream, "", ClassType.ObjectOutputStream, "writeObject", MethodDescriptor.JustTakesObject ) final val WriteExternalDescriptor = MethodDescriptor.JustTakes(ClassType.ObjectOutput) final val WriteObjectDescriptor = MethodDescriptor.JustTakes(ClassType.ObjectOutputStream) override def processNewCaller( calleeContext: ContextType, callerContext: ContextType, pc: Int, tac: TACode[TACMethodParameter, V], receiverOption: Option[Expr[V]], params: Seq[Option[Expr[V]]], tgtVarOption: Option[V], isDirect: Boolean ): ProperPropertyComputationResult = { val indirectCalls = new IndirectCalls() if (params.nonEmpty && params.head.isDefined) { implicit val stmts: Array[Stmt[V]] = tac.stmts val param = params.head.get.asVar val receiver = persistentUVar(param) val parameters = Seq(receiverOption.flatMap(os => persistentUVar(os.asVar))) implicit val state: TACAIBasedCGState[ContextType] = new TACAIBasedCGState[ContextType]( callerContext, FinalEP(callerContext.method.definedMethod, TheTACAI(tac)) ) handleOOSWriteObject(callerContext, param, pc, receiver, parameters, indirectCalls) returnResult(param, receiver, parameters, indirectCalls)(using state) } else { indirectCalls.addIncompleteCallSite(pc) Results(indirectCalls.partialResults(callerContext)) } } def c( receiverVar: V, receiver: Option[(ValueInformation, IntTrieSet)], parameters: Seq[Option[(ValueInformation, IntTrieSet)]], state: TACAIBasedCGState[ContextType] )(eps: SomeEPS): ProperPropertyComputationResult = { val pc = state.dependersOf(eps.toEPK).head.asInstanceOf[Int] // ensures, that we only add new vm reachable methods val indirectCalls = new IndirectCalls() typeIterator.continuation(receiverVar, eps.asInstanceOf[EPS[Entity, PropertyType]]) { newType => handleType(newType, state.callContext, pc, receiver, parameters, indirectCalls) }(using state) if (eps.isFinal) { state.removeDependee(eps.toEPK) } else { state.updateDependency(eps) } returnResult(receiverVar, receiver, parameters, indirectCalls)(using state) } def returnResult( receiverVar: V, receiver: Option[(ValueInformation, IntTrieSet)], parameters: Seq[Option[(ValueInformation, IntTrieSet)]], indirectCalls: IndirectCalls )(implicit state: TACAIBasedCGState[ContextType]): ProperPropertyComputationResult = { val results = indirectCalls.partialResults(state.callContext) if (state.hasOpenDependencies) Results( InterimPartialResult(state.dependees, c(receiverVar, receiver, parameters, state)), results ) else Results(results) } private def handleOOSWriteObject( callContext: ContextType, param: V, callPC: Int, receiver: Option[(ValueInformation, IntTrieSet)], parameters: Seq[Option[(ValueInformation, IntTrieSet)]], indirectCalls: IndirectCalls )(implicit state: TACAIBasedCGState[ContextType]): Unit = { typeIterator.foreachType( param, typeIterator.typesProperty(param, callContext, callPC.asInstanceOf[Entity], state.tac.stmts) ) { tpe => handleType(tpe, callContext, callPC, receiver, parameters, indirectCalls) } } private def handleType( tpe: ReferenceType, callContext: ContextType, callPC: Int, receiver: Option[(ValueInformation, IntTrieSet)], parameters: Seq[Option[(ValueInformation, IntTrieSet)]], indirectCalls: IndirectCalls ): Unit = { if (tpe.isArrayType && !tpe.asArrayType.elementType.isClassType) { indirectCalls.addIncompleteCallSite(callPC) return; } val paramType = if (tpe.isArrayType) tpe.asArrayType.elementType.asClassType else tpe.asClassType if (classHierarchy.isSubtypeOf(paramType, ClassType.Externalizable)) { val writeExternalMethod = project.instanceCall( paramType, paramType, "writeExternal", WriteExternalDescriptor ) indirectCalls.addCallOrFallback( callContext, callPC, writeExternalMethod, ClassType.Externalizable.packageName, ClassType.Externalizable, "writeExternal", WriteExternalDescriptor, parameters, receiver ) } else { val writeObjectMethod = project.specialCall( paramType, paramType, isInterface = false, "writeObject", WriteObjectDescriptor ) if (writeObjectMethod.hasValue) indirectCalls.addCallOrFallback( callContext, callPC, writeObjectMethod, ClassType.Object.packageName, ClassType.Object, "writeObject", WriteObjectDescriptor, parameters, receiver ) } val writeReplaceMethod = project.specialCall( paramType, paramType, isInterface = false, "writeReplace", WriteObjectDescriptor ) indirectCalls.addCallOrFallback( callContext, callPC, writeReplaceMethod, ClassType.Object.packageName, ClassType.Object, "writeReplace", WriteObjectDescriptor, parameters, receiver ) } } /** * Analysis handling the specifics of java.io.ObjectInputStream.readObject. * This method may instantiate new objects and invoke readObject, readResolve, readExternal or * validateObject on them. * * @author Florian Kuebler */ class OISReadObjectAnalysis private[analyses] ( final val project: SomeProject ) extends TACAIBasedAPIBasedAnalysis with TypeConsumerAnalysis { final val ReadObjectDescriptor = MethodDescriptor.JustTakes(ClassType.ObjectInputStream) final val ReadExternalDescriptor = MethodDescriptor.JustTakes(ClassType.ObjectInput) override val apiMethod: DeclaredMethod = declaredMethods( ClassType.ObjectInputStream, "", ClassType.ObjectInputStream, "readObject", MethodDescriptor.JustReturnsObject ) override def processNewCaller( calleeContext: ContextType, callerContext: ContextType, pc: Int, tac: TACode[TACMethodParameter, V], receiverOption: Option[Expr[V]], params: Seq[Option[Expr[V]]], tgtVarOption: Option[V], isDirect: Boolean ): ProperPropertyComputationResult = { implicit val stmts: Array[Stmt[V]] = tac.stmts val calleesAndCallers = new IndirectCalls() if (tgtVarOption.isDefined) { handleOISReadObject(callerContext, tgtVarOption.get, receiverOption, pc, calleesAndCallers) } else { calleesAndCallers.addIncompleteCallSite(pc) } Results(calleesAndCallers.partialResults(callerContext)) } private def handleOISReadObject( context: ContextType, targetVar: V, inputStream: Option[Expr[V]], pc: Int, calleesAndCallers: IndirectCalls )( implicit stmts: Array[Stmt[V]] ): Unit = { var foundCast = false val parameterList = Seq(inputStream.flatMap(is => persistentUVar(is.asVar))) for { use <- targetVar.usedBy } stmts(use) match { case Checkcast(_, value, ElementReferenceType(castType)) => foundCast = true // for each subtype of the cast type we add calls to the relevant methods for { t <- ch.allSubtypes(castType, reflexive = true) cf <- project.classFile(t) // we ignore cases were no class file exists if !cf.isInterfaceDeclaration if ch.isSubtypeOf(t, ClassType.Serializable) } { val receiver = Some( (ASObjectValue(isNull = No, isPrecise = false, castType), IntTrieSet(pc)) ) if (ch.isSubtypeOf(t, ClassType.Externalizable)) { // call to `readExternal` val readExternal = p.instanceCall(t, t, "readExternal", ReadExternalDescriptor) calleesAndCallers.addCallOrFallback( context, pc, readExternal, ClassType.Externalizable.packageName, ClassType.Externalizable, "readExternal", ReadExternalDescriptor, parameterList, receiver ) // call to no-arg constructor cf.findMethod("", NoArgsAndReturnVoid) foreach { c => calleesAndCallers.addCall(context, pc, declaredMethods(c), Seq.empty, receiver) } } else { // call to `readObject` val readObjectMethod = p.specialCall(t, t, isInterface = false, "readObject", ReadObjectDescriptor) if (readObjectMethod.hasValue) calleesAndCallers.addCallOrFallback( context, pc, readObjectMethod, ClassType.Object.packageName, ClassType.Object, "readObject", ReadObjectDescriptor, parameterList, receiver ) // call to first super no-arg constructor val nonSerializableSuperclass = firstNotSerializableSupertype(t) if (nonSerializableSuperclass.isDefined) { val ctor = p.classFile(nonSerializableSuperclass.get).flatMap { cf => cf.findMethod("", NoArgsAndReturnVoid) } // otherwise an exception will thrown at runtime if (ctor.isDefined) { calleesAndCallers.addCall(context, pc, declaredMethods(ctor.get), Seq.empty, receiver) } } // for the type to be instantiated, we need to call a constructor of the // type t in order to let the instantiated types be correct. Note, that the // JVM would not call the constructor // Note, that we assume that there is a constructor // Note that we have to do a String comparison since methods with ClassType // descriptors are not sorted consistently across runs val constructors = cf.constructors.map[(String, Method)] { ctor => (ctor.descriptor.toJava, ctor) } if (constructors.nonEmpty) { // val constructor = constructors.minBy(t => t._1)._2 val constructor = declaredMethods(t, t.packageName, t, "", MethodDescriptor.NoArgsAndReturnVoid) calleesAndCallers.addCall(context, pc, constructor, Seq.empty, receiver) } } // call to `readResolve` val readResolve = p.specialCall(t, t, isInterface = false, "readResolve", JustReturnsObject) calleesAndCallers.addCallOrFallback( context, pc, readResolve, ClassType.Object.packageName, ClassType.Object, "readResolve", JustReturnsObject, Seq.empty, receiver ) // call to `validateObject` if (ch.isSubtypeOf(t, ClassType.ObjectInputValidation)) { val validateObject = p.instanceCall(t, t, "validateObject", JustReturnsObject) calleesAndCallers.addCallOrFallback( context, pc, validateObject, ClassType.Object.packageName, ClassType.Object, "validateObject", JustReturnsObject, Seq.empty, receiver ) } // IMPROVE: Also handle readObjectNoData method } case _ => } if (!foundCast) { calleesAndCallers.addIncompleteCallSite(pc) } } @tailrec private def firstNotSerializableSupertype(t: ClassType): Option[ClassType] = { ch.superclassType(t) match { case None => None case Some(superType) => if (ch.isSubtypeOf(superType, ClassType.Serializable)) { firstNotSerializableSupertype(superType) } else { Some(superType) } } } } /** * Handles the effect of serialization to the call graph. * As an example models the invocation of constructors when `readObject` is called, if there is a * cast afterward. * * @author Florian Kuebler * @author Dominik Helm */ class SerializationRelatedCallsAnalysis private[analyses] ( final val project: SomeProject ) extends FPCFAnalysis { def process(p: SomeProject): PropertyComputationResult = { val readObjectAnalysis = new OISReadObjectAnalysis(project) val readObjectResult = readObjectAnalysis.registerAPIMethod() val writeObjectAnalysis = new OOSWriteObjectAnalysis(project) val writeObjectResult = writeObjectAnalysis.registerAPIMethod() Results(readObjectResult, writeObjectResult) } } object SerializationRelatedCallsAnalysisScheduler extends BasicFPCFEagerAnalysisScheduler { override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey, TypeIteratorKey) override def uses: Set[PropertyBounds] = PropertyBounds.ubs(Callers, Callees, TACAI) override def uses(p: SomeProject, ps: PropertyStore): Set[PropertyBounds] = { p.get(TypeIteratorKey).usedPropertyKinds } override def derivesCollaboratively: Set[PropertyBounds] = PropertyBounds.ubs(Callers, Callees) override def derivesEagerly: Set[PropertyBounds] = Set.empty override def start(p: SomeProject, ps: PropertyStore, i: Null): FPCFAnalysis = { val analysis = new SerializationRelatedCallsAnalysis(p) ps.scheduleEagerComputationForEntity(p)(analysis.process) analysis } }