All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opalj.br.analyses.ProjectLike.scala Maven / Gradle / Ivy

The newest version!
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package br
package analyses

import scala.collection.{mutable, Map => SomeMap, Set => SomeSet}
import com.typesafe.config.Config
import org.opalj.log.LogContext
import org.opalj.log.OPALLogger
import org.opalj.log.OPALLogger.error
import org.opalj.log.OPALLogger.info
import org.opalj.collection.immutable.UIDSet
import org.opalj.bi.Java11MajorVersion
import org.opalj.bi.Java1MajorVersion
import org.opalj.br.instructions.FieldAccess
import org.opalj.br.instructions.INVOKEINTERFACE
import org.opalj.br.instructions.INVOKESPECIAL
import org.opalj.br.instructions.INVOKESTATIC
import org.opalj.br.instructions.INVOKEVIRTUAL
import org.opalj.br.instructions.NonVirtualMethodInvocationInstruction
import org.opalj.br.MethodDescriptor.SignaturePolymorphicMethodBoolean
import org.opalj.br.MethodDescriptor.SignaturePolymorphicMethodObject
import org.opalj.br.MethodDescriptor.SignaturePolymorphicMethodVoid

import scala.collection.immutable.ArraySeq
import control.find

/**
 * Enables project wide lookups of methods and fields as required to determine the target(s) of an
 * invoke or field access instruction.
 *
 * @note    The current implementation is based on the '''correct project assumption''';
 *          i.e., if the bytecode of the project as a whole is not valid, the result is generally
 *          undefined.
 *          Just one example of a violation of the assumption would be,
 *          if we have two interfaces which define a non-abstract
 *          method with the same signature and both interfaces are implemented by a third
 *          interface which does not override these methods. In this case the result of a
 *          `resolveMethodReference` is not defined, because the code base as a whole is
 *          not valid.
 *
 * @author Michael Eichberg
 */
abstract class ProjectLike extends ClassFileRepository { project =>

    private[this] final implicit val thisProjectLike: this.type = this

    implicit val classHierarchy: ClassHierarchy
    implicit val config: Config

    protected[this] val allClassFiles: Iterable[ClassFile]

    /**
     * Returns the minimum version number of the JVM required to run the code of the project, i.e.,
     * the maximum class file major version number of any class file in the project.
     */
    def requiredJVMVersion: Int = {
        if (allClassFiles.isEmpty) Java1MajorVersion
        else allClassFiles.maxBy(_.version._2).version._2
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    //
    //
    // RESOLVING FIELD REFERENCES
    //
    //
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    /**
     * @see [[#resolveFieldReference(declaringClassFile:*]]
     */
    final def resolveFieldReference(
        declaringClassType: ObjectType,
        fieldName:          String,
        fieldType:          FieldType
    ): Option[Field] = {
        // for more details see JVM 7/8 Spec. Section 5.4.3.2
        project.classFile(declaringClassType) flatMap { classFile =>
            resolveFieldReference(classFile, fieldName, fieldType)
        }
    }

    /**
     * Resolves a symbolic reference to a field. Basically, the search starts with
     * the given class `c` and then continues with `c`'s superinterfaces before the
     * search is continued with `c`'s superclass (as prescribed by the JVM specification
     * for the resolution of unresolved symbolic references).
     *
     * Resolving a symbolic reference is particularly required to, e.g., get a field's
     * annotations or to get a field's value (if it is `static`, `final` and has a
     * constant value).
     *
     * @note    This implementation does not check for `IllegalAccessError`. This check
     *          needs to be done by the caller. The same applies for the check that the
     *          field is non-static if get-/putfield is used and static if a get-/putstatic is
     *          used to access the field. In the latter case the JVM would throw a
     *          `LinkingException`.
     *          Furthermore, if the field cannot be found, it is the responsibility of the
     *          caller to handle that situation.
     * @note    Resolution is final. I.e., either this algorithm has found the defining field
     *          or the field is not defined by one of the loaded classes. Searching for the
     *          field in subclasses is not meaningful as it is not possible to ''override''
     *          fields.
     *
     * @param   declaringClassFile The class (or a superclass thereof) that is expected
     *          to define the specified field.
     * @param   fieldName The name of the field.
     * @param   fieldType The type of the field (the field descriptor).
     */
    def resolveFieldReference(
        declaringClassFile: ClassFile,
        fieldName:          String,
        fieldType:          FieldType
    ): Option[Field] = {
        // for more details see JVM 7/8 Spec. Section 5.4.3.2
        declaringClassFile findField (fieldName, fieldType) orElse {
            declaringClassFile.interfaceTypes collectFirst { supertype =>
                resolveFieldReference(supertype, fieldName, fieldType) match {
                    case Some(resolvedFieldReference) => resolvedFieldReference
                }
            } orElse {
                declaringClassFile.superclassType flatMap { supertype =>
                    resolveFieldReference(supertype, fieldName, fieldType)
                }
            }
        }
    }

    /**
     * @see [[#resolveFieldReference(declaringClassFile:*]]
     */
    final def resolveFieldReference(fieldAccess: FieldAccess): Option[Field] = {
        resolveFieldReference(fieldAccess.declaringClass, fieldAccess.name, fieldAccess.fieldType)
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    //
    //
    // RESOLVING METHOD REFERENCES / LOCATING THE INVOKED METHOD(S)
    //
    //
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    /**
     * The class file of `java.lang.Object`, if available.
     */
    val ObjectClassFile: Option[ClassFile]

    /**
     * The class file of `java.lang.invoke.MethodHandle`, if available.
     */
    val MethodHandleClassFile: Option[ClassFile]

    /**
     * The class file of `java.lang.invoke.VarHandle`, if available.
     */
    val VarHandleClassFile: Option[ClassFile]

    /**
     * The set of all subtypes of `java.lang.invoke.MethodHandle`; in particular required to resolve
     * signature polymorphic method calls.
     */
    val MethodHandleSubtypes: SomeSet[ObjectType]

    /**
     * The set of all subtypes of `java.lang.invoke.VarHandle`; in particular required to resolve
     * signature polymorphic method calls.
     */
    val VarHandleSubtypes: SomeSet[ObjectType]

    /**
     * Stores for each non-private, non-initializer method the set of methods which override
     * a specific method. If the given method is a concrete method, this method is also
     * included in the set of `overridingMethods`.
     */
    protected[this] val overridingMethods: SomeMap[Method, SomeSet[Method]]

    /**
     * Returns the set of methods which directly override the given method. Note that
     * `overriddenBy` is not context aware. I.e., if a given method `m` is an interface
     * method, then it may happen that we have an implementation of that method
     * in a class which is inherited from a superclass which is not a subtype of the
     * interface. That method - since it is not defined by a subtype of the interface -
     * would not be included in the returned set. An example is shown next:
     * {{{
     * class X { void m(){ System.out.println("X.m"); }
     * interface Y { default void m(){ System.out.println("Y.m"); }
     * class Z extends X implements Y {
     *  // Z inherits m() from X; hence, X.m() (in this context) "overrides" Y.m(), but is not
     *  // returned by this function. To also identify X.m() you have to combine the results
     *  // of overridenBy and instanceMethods(!).
     * }
     * }}}
     */
    def overriddenBy(m: Method): SomeSet[Method] = {
        assert(!m.isPrivate, s"private methods $m cannot be overridden")
        assert(!m.isStatic, s"static methods $m cannot be overridden")
        assert(!m.isInitializer, s"initializers $m cannot be overridden")

        overridingMethods.getOrElse(m, Set.empty)
    }

    /**
     * Returns the set of all non-private, non-abstract, non-static methods that are not
     * initializers and which are potentially callable by clients when we have an object that
     * has the specified type and a method is called using
     * [[org.opalj.br.instructions.INVOKEINTERFACE]], [[org.opalj.br.instructions.INVOKEVIRTUAL]] or
     * [[org.opalj.br.instructions.INVOKEDYNAMIC]].
     *
     * The array of methods is sorted using [[MethodDeclarationContextOrdering]] to
     * enable fast look-up of the target method. (See [[MethodDeclarationContext]]'s
     * `compareAccessibilityAware` method for further details.)
     */
    protected[this] val instanceMethods: SomeMap[ObjectType, ArraySeq[MethodDeclarationContext]]

    /**
     * Returns the nest host (see JVM 11 Spec. 5.4.4) for the given type, if explicitly given. For
     * classes without an explicit NestHost or NestMembers attribute, the type itself is the nest
     * host, but this is NOT recorded in this map.
     */
    val nests: SomeMap[ObjectType, ObjectType]

    /**
     * Tests if the given method belongs to the interface of an '''object''' identified by
     * the given `objectType`.
     * I.e., returns `true` if a virtual method call, where the receiver type is known
     * to have the given `objectType`, would lead to the direct invocation of the given `method`.
     * The given method can be an inherited method, but it will never return `Yes` if
     * the given method is overridden by `objectType` or a supertype of it which is a
     * sub type of the declaring type of `method`.
     *
     * @note    The computation is based on the computed set of [[instanceMethods]] and generally
     *          requires at most O(n log n) steps where n is the number of callable instance
     *          methods of the given object type; the class hierarchy is not traversed.
     */
    def hasVirtualMethod(objectType: ObjectType, method: Method): Answer = {
        //  ... instanceMethods: Map[ObjectType, Array[MethodDeclarationContext]]
        val definedMethodsOption = instanceMethods.get(objectType)
        if (definedMethodsOption.isEmpty) {
            return Unknown;
        }
        val definedMethods: ArraySeq[MethodDeclarationContext] = definedMethodsOption.get
        val declaringPackageName = method.classFile.thisType.packageName

        val result: Option[MethodDeclarationContext] = find(definedMethods) { definedMethodContext =>
            val definedMethod = definedMethodContext.method
            if (definedMethod eq method)
                0
            else {
                val methodComparison = definedMethod compare method
                if (methodComparison == 0) {
                    if (definedMethod.isPrivate) {
                        // If there is a matching private method, the given method could still be
                        // invoked by a virtual call for a supertype
                        return hasVirtualMethod(
                            classFile(objectType).get.superclassType.get,
                            method
                        );
                    } else {
                        // We may have multiple methods with the same signature, but which belong
                        // to different packages!
                        definedMethodContext.packageName compare declaringPackageName
                    }
                } else
                    methodComparison
            }
        }
        Answer(result)
    }

    /**
     * Looks up the method (declaration context) which is accessible/callable by an
     * [[org.opalj.br.instructions.INVOKEVIRTUAL]] or [[org.opalj.br.instructions.INVOKEINTERFACE]]
     * call which was done by a method belonging to `callingContextType`.
     * The `callingContextType` is only relevant in case the target method has default visibility;
     * in this case it is checked whether the caller belongs to the same context.
     *
     * @note    This method uses the pre-computed information about instance methods and,
     *          therefore, does not require a type hierarchy based lookup.
     *
     * @note    It supports the lookup of polymorphic methods.
     *
     * @return  [[Success]] if the method is found; [[Empty$]] if the method cannot be found and
     *          [[Failure$]] if the method cannot be found because the project is
     *          definitively inconsistent. `Failure$` is used on a best-effort basis.
     */
    def lookupVirtualMethod(
        callingContextType: ObjectType,
        receiverType:       ObjectType,
        name:               String,
        descriptor:         MethodDescriptor
    ): Result[MethodDeclarationContext] = {

        def lookupSignaturePolymorphicMethod(descriptor: MethodDescriptor): Result[MethodDeclarationContext] = {
            lookupVirtualMethod(
                callingContextType,
                if (MethodHandleSubtypes.contains(receiverType)) ObjectType.MethodHandle
                else ObjectType.VarHandle,
                name,
                descriptor
            ) match {
                    case r @ Success(mdc) if mdc.method.isNativeAndVarargs => r
                    case _                                                 => Empty
                }
        }

        val definedMethodsOption = instanceMethods.get(receiverType)
        if (definedMethodsOption.isEmpty) {
            return Empty;
        }
        find(definedMethodsOption.get) { mdc =>
            mdc.compareAccessibilityAware(callingContextType.packageName, name, descriptor)
        } match {
            case Some(mdc) => Success(mdc)
            case None =>
                // we have to avoid endless recursion if we can't find the target method
                if ((MethodHandleSubtypes.contains(receiverType) ||
                    VarHandleSubtypes.contains(receiverType)) &&
                    !isSignaturePolymorphic(receiverType, descriptor)) {
                    // At least in Java 15 the signature polymorphic methods are not overloaded and
                    // it actually doesn't make sense to do so. Therefore we decided to only
                    // make this lookup if strictly required.
                    lookupSignaturePolymorphicMethod(SignaturePolymorphicMethodObject) match {
                        case Empty if VarHandleSubtypes.contains(receiverType) =>
                            lookupSignaturePolymorphicMethod(SignaturePolymorphicMethodVoid) match {
                                case Empty =>
                                    lookupSignaturePolymorphicMethod(
                                        SignaturePolymorphicMethodBoolean
                                    )
                                case r => r
                            }
                        case r => r
                    }
                } else {
                    Empty // here, we don't know if the project is incomplete or inconsistent
                }
        }
    }

    /* GENERAL NOTES
     *
     * (Accessibility checks are done by the JVM when the method is resolved; this is, however, not
     * done by the following methods as it does not affect the search for the potential target
     * methods.)
     *
     * Invokestatic     =>  the resolved method is called (if it is accessible, static etc...)
     * Invokespecial    =>  the resolved (interface) method is called if it is a constructor
     *                      call, or the resolved method belongs to the calling class (calls of
     *                      private methods). Otherwise, the calling class' supertypes are
     *                      searched for a method with the same signature up until the resolved
     *                      method. When we search the type hierarchy upwards, we first search
     *                      for a method defined by a superclass (unless the current class defines
     *                      an interface type) before we search the interfaces. In the later
     *                      case we compute the set of the maximally specific matching methods
     *                      and select THE concrete one (it is an error if multiple concrete ones
     *                      exist!)
     * Invokevirtual    =>  the resolved method is called, if the resolved method is signature
     *                      polymorphic; in this case the concrete type of the method receiver
     *                      is not relevant. Otherwise, the type of the receiver is used to
     *                      start searching for the method that is to be invoked. That method
     *                      however, has to override the resolved method (which is generally not
     *                      the case if the method is private; and is only the case if the resolved
     *                      method and the current type belong to the same package in case of
     *                      a method with default visibility).
     * Invokeinterface  =>  the resolved interface method just defines an upper bound; the
     *                      called method is determined as in case of invokevirtual; but
     *                      signature polymorphic calls are not relevant
     */

    /**
     * Tries to resolve a method reference as specified by the JVM specification.
     * I.e., the algorithm tries to find the class that actually declares the referenced
     * method. Resolution of '''signature polymorphic''' method calls is also
     * supported.
     *
     * This method can be used as the basis for the implementation of the semantics
     * of the `invokeXXX` instructions. However, it does not check whether the resolved
     * method can be accessed by the caller or if it is abstract. Additionally, it is still
     * necessary that the caller makes a distinction between the statically
     * (at compile time) identified declaring class and the dynamic type of the receiver
     * in case of `invokevirtual` and `invokeinterface` instructions. I.e.,
     * additional processing is necessary on the client side.
     *
     * @note    This method just resolves a method reference. Additional checks,
     *          such as whether the resolved method is accessible, may be necessary.
     *
     * @param   declaringClassType The type of the object that receives the method call. The
     *          type must be a class type and must not be an interface type.
     *          No check w.r.t. a potential `IncompatibleClassChangeError` is done
     *          by this method.
     * @param   forceLookupInSuperinterfacesOnFailure If true (default: false) the method tries
     *          to look up the method in a super interface if it can't find it in the available
     *          super classes.
     * @return  The resolved method `Some(`'''METHOD'''`)` or `None`.
     *          (To get the defining class file use the project's respective method.)
     */
    def resolveMethodReference(
        declaringClassType:                    ReferenceType,
        name:                                  String,
        descriptor:                            MethodDescriptor,
        forceLookupInSuperinterfacesOnFailure: Boolean          = false
    ): Option[Method] = {
        val receiverType =
            if (declaringClassType.isArrayType) {
                ObjectType.Object
            } else {
                declaringClassType.asObjectType
            }

        resolveClassMethodReference(receiverType, name, descriptor) match {
            case Success(method)                                 => Some(method)
            case Empty if !forceLookupInSuperinterfacesOnFailure => None
            case _ /*Failure | (Empty && lookupInSuperinterfacesOnFailure) */ =>
                val superinterfaceTypes = classHierarchy.allSuperinterfacetypes(receiverType)
                val (_, methods) =
                    findMaximallySpecificSuperinterfaceMethods(
                        superinterfaceTypes, name, descriptor,
                        analyzedSuperinterfaceTypes = UIDSet.empty[ObjectType]
                    )
                // Either it is THE max. specific method or some "arbitrary" method.
                // recall that we already give precedence to non-abstract
                // methods in the find... methods
                methods.headOption
        }
    }

    /**
     * Resolves a method reference to all possible methods. I.e., this is identical to
     * `resolveMethodReference` or `resolveInterfaceMethodReference` for class and interface types
     * respectively except for the case where there are multiple maximally specific interface
     * methods in which case all of them are returned instead of only a single one.
     *
     * @param declaringClassType The type of the object that receives the method call. The type may
     *         be a class or interface type.
     *
     * @return The set of resolved methods; empty if the resolution fails, more than one if
     *         resolution finds several maximally specific interface methods - in the latter case
     *         it is not possible to call the method on objects of the declaring class type, but
     *         only on subclasses overriding the method uniquely.
     */
    def resolveAllMethodReferences(
        declaringClassType: ReferenceType,
        name:               String,
        descriptor:         MethodDescriptor
    ): Set[Method] = {
        val receiverType =
            if (declaringClassType.isArrayType) {
                ObjectType.Object
            } else {
                declaringClassType.asObjectType
            }

        def lookupInObject(): Option[Method] = {
            ObjectClassFile flatMap { classFile =>
                classFile.findMethod(name, descriptor) filter { m => m.isPublic && !m.isStatic }
            }
        }

        project.classFile(receiverType) match {
            case Some(classFile) =>
                val classMethod =
                    if (classFile.isInterfaceDeclaration)
                        Result(classFile.findMethod(name, descriptor) orElse lookupInObject())
                    else
                        resolveClassMethodReference(receiverType, name, descriptor)

                classMethod match {
                    case Success(method) => Set(method)
                    case _ =>
                        val superinterfaces = classHierarchy.allSuperinterfacetypes(receiverType)
                        val (_, methods) = findMaximallySpecificSuperinterfaceMethods(
                            superinterfaces, name, descriptor, UIDSet.empty[ObjectType]
                        )
                        methods
                }

            case None => Set.empty
        }
    }

    /**
     * See [[#resolveMethodReference(declaringClassType:*]] for details.
     */
    def resolveMethodReference(i: INVOKEVIRTUAL): Option[Method] = {
        resolveMethodReference(i.declaringClass, i.name, i.methodDescriptor)
    }

    def resolveInterfaceMethodReference(
        declaringClassType: ObjectType,
        name:               String,
        descriptor:         MethodDescriptor
    ): Option[Method] = {
        def lookupInObject(): Option[Method] = {
            ObjectClassFile flatMap { classFile =>
                classFile.findMethod(name, descriptor) filter { m => m.isPublic && !m.isStatic }
            }
        }

        project.classFile(declaringClassType) flatMap { classFile =>
            assert(classFile.isInterfaceDeclaration)
            classFile.findMethod(name, descriptor) orElse {
                lookupInObject() orElse {
                    classHierarchy.superinterfaceTypes(declaringClassType) flatMap { superT =>
                        val (_, methods) = findMaximallySpecificSuperinterfaceMethods(
                            superT, name, descriptor, UIDSet.empty[ObjectType]
                        )
                        methods.headOption
                    }
                }
            }
        }
    }

    /**
     * See [[#resolveInterfaceMethodReference(declaringClassType:*]] for details.
     */
    def resolveInterfaceMethodReference(i: INVOKEINTERFACE): Option[Method] = {
        resolveInterfaceMethodReference(i.declaringClass, i.name, i.methodDescriptor)
    }

    /**
     * Computes the set of maximally specific superinterface methods with the
     * given name and descriptor.
     *
     * @note    '''This method does not consider methods defined by `java.lang.Object`'''!
     *          Those methods have precedence over respective methods defined by
     *          superinterfaces! A corresponding check needs to be done before calling
     *          this method.
     */
    def findMaximallySpecificSuperinterfaceMethods(
        superinterfaceType:          ObjectType,
        name:                        String,
        descriptor:                  MethodDescriptor,
        analyzedSuperinterfaceTypes: UIDSet[ObjectType] = UIDSet.empty
    ): ( /*analyzed types*/ UIDSet[ObjectType], Set[Method]) = {
        ProjectLike.findMaximallySpecificSuperinterfaceMethods(
            superinterfaceType, name, descriptor, analyzedSuperinterfaceTypes
        )(this.classFile, this.classHierarchy, this.logContext)
    }

    /**
     * Computes the maximally specific superinterface method with the given name
     * and descriptor
     *
     * @param   superinterfaceTypes A set of interfaces which potentially declare a method
     *          with the given name and descriptor.
     */
    def findMaximallySpecificSuperinterfaceMethods(
        superinterfaceTypes:         UIDSet[ObjectType],
        name:                        String,
        descriptor:                  MethodDescriptor,
        analyzedSuperinterfaceTypes: UIDSet[ObjectType]
    ): ( /*analyzed types*/ UIDSet[ObjectType], Set[Method]) = {
        ProjectLike.findMaximallySpecificSuperinterfaceMethods(
            superinterfaceTypes, name, descriptor, analyzedSuperinterfaceTypes
        )(this.classFile, this.classHierarchy, this.logContext)
    }

    /**
     * Resolves a symbolic reference to a method defined by a class (not interface) type.
     *
     * @return  [[org.opalj.Success]]`(method)` if the method was found;
     *          `Empty` if the project is incomplete and the method could not be found;
     *          `Failure` if the method could not be found though the project is seemingly complete.
     *          I.e., if `Failure` is returned the method is not defined by a concrete class
     *          and is either a default method defined by an interface or the analyzed code
     *          basis is inconsistent.
     */
    def resolveClassMethodReference(
        receiverType: ObjectType,
        name:         String,
        descriptor:   MethodDescriptor
    ): Result[Method] = {

        project.classFile(receiverType) match {
            case Some(classFile) =>
                assert(
                    !classFile.isInterfaceDeclaration,
                    {
                        val methodInfo = descriptor.toJava(receiverType.toJava, name)
                        s"the method is defined in an interface $methodInfo"
                    }
                )

                def resolveSuperclassMethodReference(): Result[Method] = {
                    classFile.superclassType match {
                        case Some(superclassType) =>
                            resolveClassMethodReference(superclassType, name, descriptor)
                        case None =>
                            // the current type is java.lang.Object and the method was not found
                            Failure
                    }
                }

                // [FROM THE SPECIFICATION]]
                // A method is signature polymorphic if all of the following conditions hold :
                // - It is declared in the java.lang.invoke.MethodHandle class.
                // - It has a single formal parameter of type Object[].
                // - It has the ACC_VARARGS and ACC_NATIVE flags set.
                val isPotentiallySignaturePolymorphicCall =
                    (receiverType eq ObjectType.MethodHandle) ||
                        (receiverType eq ObjectType.VarHandle)

                if (isPotentiallySignaturePolymorphicCall) {
                    val methods = classFile.findMethod(name)
                    methods match {
                        case List(method) =>
                            if (method.isNativeAndVarargs &&
                                (method.descriptor == SignaturePolymorphicMethodObject ||
                                    method.descriptor == SignaturePolymorphicMethodVoid ||
                                    method.descriptor == SignaturePolymorphicMethodBoolean))
                                Success(method) // the resolved method is signature polymorphic
                            else if (method.descriptor == descriptor)
                                Success(method) // "normal" resolution of a method
                            else
                                resolveSuperclassMethodReference()
                        case _ =>
                            methods.find(m => m.descriptor == descriptor) match {
                                case None                 => resolveSuperclassMethodReference()
                                case Some(resolvedMethod) => Success(resolvedMethod)
                            }
                    }

                } else {
                    classFile.findMethod(name, descriptor) match {
                        case None                 => resolveSuperclassMethodReference()
                        case Some(resolvedMethod) => Success(resolvedMethod)
                    }
                }

            case None => Empty
        }
    }

    /**
     * Returns true if the method defined by the given class type is a signature polymorphic
     * method. (See JVM 9 Spec. for details.)
     */
    //TODO add method that lookup the defining class type
    def isSignaturePolymorphic(definingClassType: ObjectType, method: Method): Boolean = {
        (
            (definingClassType eq ObjectType.MethodHandle) ||
            (definingClassType eq ObjectType.VarHandle)
        ) &&
            method.descriptor.parametersCount == 1 &&
            method.descriptor.parameterType(0) == ArrayType.ArrayOfObject &&
            method.isNativeAndVarargs
    }

    /**
     * Returns true if the descriptor is a signature polymorphic method for the given class type.
     * (See JVM 9 Spec. for details.)
     */
    def isSignaturePolymorphic(
        definingClassType: ObjectType,
        descriptor:        MethodDescriptor
    ): Boolean = {
        (definingClassType eq ObjectType.MethodHandle) &&
            descriptor == SignaturePolymorphicMethodObject ||
            (definingClassType eq ObjectType.VarHandle) &&
            (descriptor == SignaturePolymorphicMethodObject ||
                descriptor == SignaturePolymorphicMethodVoid ||
                descriptor == SignaturePolymorphicMethodBoolean)
    }

    /**
     * Returns true if the signature is a signature polymorphic method for the given class type.
     * (See JVM 9 Spec. for details.)
     */
    def isSignaturePolymorphic(
        definingClassType: ObjectType,
        name:              String,
        descriptor:        MethodDescriptor
    ): Boolean = {
        (definingClassType eq ObjectType.MethodHandle) &&
            descriptor == SignaturePolymorphicMethodObject &&
            (name == "invoke" || name == "invokeExact") ||
            (definingClassType eq ObjectType.VarHandle) &&
            (descriptor == SignaturePolymorphicMethodObject ||
                descriptor == SignaturePolymorphicMethodVoid ||
                descriptor == SignaturePolymorphicMethodBoolean)
    }

    /**
     * Returns the method which will be called by the respective
     * [[org.opalj.br.instructions.INVOKESTATIC]] instruction.
     */
    def staticCall(callerClassType: ObjectType, i: INVOKESTATIC): Result[Method] = {
        staticCall(callerClassType, i.declaringClass, i.isInterface, i.name, i.methodDescriptor)
    }

    /**
     * Returns the method that will be called by the respective invokestatic call.
     *
     * @return  [[org.opalj.Success]] `(method)` if the method was found;
     *          `Failure` if the project is inconsistent.
     *          `Empty` if the method could not be found in the available classes (i.e., the
     *          project is incomplete).
     */
    def staticCall(
        callerClassType:    ObjectType,
        declaringClassType: ObjectType,
        isInterface:        Boolean,
        name:               String,
        descriptor:         MethodDescriptor
    ): Result[Method] = {
        // Recall that the invokestatic instruction:
        // "... gives the name and descriptor of the method as well as a symbolic reference to
        // the class or interface in which the method is to be found.
        // However, in case of interfaces no lookup in superclasses is done!
        if (isInterface) {
            classFile(declaringClassType) match {
                case Some(cf) => cf.findMethod(name, descriptor) match {
                    case Some(method) if method.isAccessibleBy(callerClassType, nests) =>
                        Success(method)
                    case _ => Empty
                }
                case None => Empty
            }
        } else {
            resolveClassMethodReference(declaringClassType, name, descriptor) match {
                case s @ Success(method) =>
                    if (method.isAccessibleBy(callerClassType, nests)) s else Empty
                case e =>
                    e
            }
        }
    }

    def specialCall(callerClassType: ObjectType, i: INVOKESPECIAL): Result[Method] = {
        specialCall(callerClassType, i.declaringClass, i.isInterface, i.name, i.methodDescriptor)
    }

    def nonVirtualCall(
        callerClassType: ObjectType,
        i:               NonVirtualMethodInvocationInstruction
    ): Result[Method] = {
        if (i.opcode == INVOKESPECIAL.opcode) {
            specialCall(callerClassType, i.asINVOKESPECIAL)
        } else { // i.opcode == INVOKESTATIC.opcode
            staticCall(callerClassType, i.asINVOKESTATIC)
        }
    }

    /**
     * Returns the instance method/initializer which is called by an invokespecial instruction.
     *
     * @note    Virtual method call resolution is not necessary; the call target is
     *          either a constructor, a method in the given class or a super method/constructor.
     *          However, in the first and last case it may be possible that we can't find the method
     *          because of an inconsistent or incomplete project.
     *
     * @return  One of the following three values:
     *           - [[org.opalj.Success]] `(method)` if the method was found;
     *           - `Failure` if the project is inconsistent; i.e., the target class file is found,
     *             but the method cannot be found. `Failure` is returned on a best effort basis.
     *           - `Empty`.
     */
    def specialCall(
        callerClassType:           ObjectType,
        initialDeclaringClassType: ObjectType, // an interface or class type to be precise
        isInterface:               Boolean,
        name:                      String, // an interface or class type to be precise
        descriptor:                MethodDescriptor
    ): Result[Method] = {
        /* FROM THE SPEC
            If all of the following are true, let C be the direct superclass of the current class:
            • The resolved method is not an instance initialization method (§2.9.1).
            • If the symbolic reference names a class (not an interface), then that class is a
              superclass of the current class.
            • The ACC_SUPER flag is set for the class file (§4.1).
            Otherwise, let C be the class or interface named by the symbolic reference.
         */
        val declaringClassType =
            if (name != "" &&
                (callerClassType ne initialDeclaringClassType) && // <= handles private calls
                classHierarchy.isInterface(initialDeclaringClassType).isNo) {
                // Let's select the direct superclass (if it is available; otherwise we use the
                // declared class as a fallback.)
                classHierarchy.superclassType(callerClassType).getOrElse(initialDeclaringClassType)
            } else {
                initialDeclaringClassType
            }

        // ...  default methods cannot override methods from java.lang.Object
        // ...  in case of super method calls (not initializers), we can use
        //      "instanceMethods" to find the method, because the method has to
        //      be an instance method, must not be abstract and must not be private.
        // ...  the receiver type of super initializer calls is always explicitly given
        classFile(declaringClassType) match {
            case Some(classFile) =>
                if (classFile.isInterfaceDeclaration != isInterface)
                    Failure
                else {
                    classFile.findMethod(name, descriptor) match {
                        case Some(method) =>
                            if (method.isAccessibleBy(callerClassType, nests))
                                Success(method)
                            else
                                Empty

                        case None if name == "" => Failure // initializer not found...

                        case _ =>
                            // We have to find the (maximally specific) super method, which is,
                            // unless we have an inconsistent code base, unique.
                            find(instanceMethods(declaringClassType)) { definedMethodContext =>
                                val definedMethod = definedMethodContext.method
                                definedMethod.compare(name, descriptor)
                            } match {
                                case Some(mdc) =>
                                    if (mdc.method.isAccessibleBy(callerClassType, nests))
                                        Success(mdc.method)
                                    else
                                        Empty
                                case None => Empty
                            }
                    }
                }
            case None => Empty
        }
    }

    /**
     * Returns the (instance) method that would be called when we have an instance of
     * the given receiver type. I.e., using this method is suitable only when the runtime
     * type, which is the receiver of the method call, is precisely known!
     *
     * == Examples ==
     * {{{
     * class A {def foo() = {} }
     * class B extends A {/*inherits, but does not override foo()*/}
     * class C extends B { def foo() = {} }
     * val b = new B();
     * b.foo() // <= in this case the method defined by A will be returned.
     * val c = new C();
     * c.foo() // <= in this case the method defined by C will be returned.
     * }}}
     *
     * This method supports default methods and signature polymorphic calls; i.e., the
     * descriptor of the retuned methods may not be equal to the given method descriptor.
     *
     * @param   callerClassType The object type which defines the method which performs the call.
     *          This information is required if the call target has (potentially) default
     *          visibility. (Note that this - in general - does not replace the need to perform an
     *          accessibility check.)
     * @param   receiverType A class type or an array type; never an interface type.
     */
    def instanceCall(
        callerClassType: ObjectType,
        receiverType:    ReferenceType,
        name:            String,
        descriptor:      MethodDescriptor
    ): Result[Method] = {
        if (receiverType.isArrayType) {
            return Result(ObjectClassFile flatMap { cf => cf.findMethod(name, descriptor) });
        }

        val receiverClassType = receiverType.asObjectType
        val mdcResult = lookupVirtualMethod(callerClassType, receiverClassType, name, descriptor)
        mdcResult flatMap { mdc =>
            if (!mdc.method.isPrivate || mdc.method.isAccessibleBy(callerClassType, nests))
                Success(mdc.method)
            else
                Empty
        }
    }

    def interfaceCall(callerType: ObjectType, i: INVOKEINTERFACE): Set[Method] = {
        interfaceCall(callerType, i.declaringClass, i.name, i.methodDescriptor)
    }

    private val useJava11CallSemantics: Boolean = {
        val key = ProjectLike.EnforceJava11CallSemanticsConfigKey
        val forceJ11semantics: Boolean =
            try {
                config.getBoolean(key)
            } catch {
                case t: Throwable =>
                    error("project configuration", s"couldn't read: $key", t)
                    false
            }
        val (useJ11semantics, reason) = if (forceJ11semantics) {
            (true, "(enforced by config)")
        } else {
            val requiredVersion = requiredJVMVersion
            (requiredVersion >= Java11MajorVersion, s"(required JVM version is $requiredVersion)")
        }
        info(
            "project configuration",
            s"${if (useJ11semantics) "" else "not "}using Java 11+ call semantics "+reason
        )
        useJ11semantics
    }

    /**
     * Returns the methods that may be called by an [[org.opalj.br.instructions.INVOKEINTERFACE]]
     * call if the precise runtime type is not known. (If the precise runtime type is known, use
     * `instanceCall` to get the target method.)
     *
     * @note    '''Caching the result (in particular when the call graph is computed)
     *          is recommended as the computation is expensive.''' In other words, this
     *          function is meant to be used as a foundation for call graph construction
     *          algorithms.
     *
     * @note    Keep in mind that the following is legal (byte)code:
     *          {{{
     *          class X { void m(){ System.out.println("X.m"); } }
     *          interface I { void m(); }
     *          class Z extends X implements I {}
     *          }}}
     *          Hence, we also have to consider inherited methods and just considering the
     *          methods defined by subclasses is not sufficient! In other words, the result
     *          can contain methods (here, `X.m`) defined by classes which are not subtypes
     *          of the given interface type!
     *
     * @return  The set of potentially called methods. The set will be empty if the target class
     *          is not defined as part of the analyzed code base.
     */
    def interfaceCall(
        callerType:     ObjectType,
        declaringClass: ObjectType, // an interface or class type to be precise
        name:           String,
        descriptor:     MethodDescriptor
    ): Set[Method] = {
        var methods = Set.empty[Method]

        // (1) consider the method defined by the super type or this type...
        // Depending on the set of open/closed packages it may be the case that the method
        // cannot be a receiver, because it is actually always overridden; however, we don't
        // do any checks related to this issue.
        val initialMethodsOption = instanceMethods.get(declaringClass)
        if (initialMethodsOption.isEmpty)
            return methods;
        find(initialMethodsOption.get) { mdc =>
            mdc.method.compare(name, descriptor)
        } foreach (mdc => methods += mdc.method)

        if (methods.nonEmpty) {
            val method = methods.head
            if (!method.isPublic) {
                if (method.isPrivate && useJava11CallSemantics) {
                    // The method is private, thus it is selected (JVM 11 Spec Section 5.4.6)
                    // However, access control may still fail
                    if (!method.isAccessibleBy(callerType, nests))
                        return Set.empty[Method];
                    return methods;
                } else {
                    methods = Set.empty[Method]
                }
            }
        }

        // (2) methods of strict subtypes (always necessary, because we have an interface)
        classHierarchy.foreachSubtypeCF(declaringClass, reflexive = false) { subtypeCF =>
            val subtype = subtypeCF.thisType
            val mdc = find(instanceMethods(subtype)) { mdc => mdc.method.compare(name, descriptor) }
            mdc match {
                case Some(mdc) =>
                    if (mdc.isPublic)
                        methods += mdc.method
                    // This is an overapproximation, if the inherited concrete method is
                    // always overridden by all concrete subtypes and subtypeCF
                    // is an abstract class in a closed package/module
                    if (!mdc.method.isPrivate)
                        methods ++=
                            overriddenBy(mdc.method).iterator.filter { m =>
                                m.classFile.thisType isSubtypeOf subtype
                            }

                    // for interfaces we have to continue, because we may have inherited a
                    // a concrete method from a class type which is not in the set of
                    // overriddenBy methods
                    subtypeCF.isInterfaceDeclaration
                case _ /*None*/ =>
                    true
            }
        }
        methods
    }

    /**
     * Convenience method; see `virtualCall(callerPackageName:String,declaringType:ReferenceType*`
     * for details.
     */
    def virtualCall(callerType: ObjectType, i: INVOKEVIRTUAL): SomeSet[Method] = {
        virtualCall(callerType, i.declaringClass, i.name, i.methodDescriptor)
    }

    /**
     * Returns the set of methods that may be called by an invokevirtual call, if
     * the receiver type is unknown or effectively encompasses all subtypes it
     * is recommended to use [[instanceCall]].
     *
     * @note    As in case of instance call, the returned method may have a different
     *          descriptor if we have a signature polymorphic call!
     */
    def virtualCall(
        callerType:    ObjectType,
        declaringType: ReferenceType, // an interface, class or array type to be precise
        name:          String,
        descriptor:    MethodDescriptor
    ): SomeSet[Method] = {
        if (declaringType.isArrayType) {
            return instanceCall(ObjectType.Object, ObjectType.Object, name, descriptor).toSet
        }

        // In the following we opted for implementing some support for the
        // different possibilities that exist w.r.t. where the defined method
        // is found. This is done to speed up the computation
        // of the set of methods (vs. using a very generic approach)!

        val declaringClassType = declaringType.asObjectType
        var methods = mutable.Set.empty[Method]

        val initialMethodsOption = instanceMethods.get(declaringClassType)
        if (initialMethodsOption.isEmpty)
            return methods;

        val callerPackageName = callerType.packageName

        // Let's find the (concrete) method defined by this type or a supertype if it exists.
        // We have to check the declaring package if the method has package visibility to ensure
        // that we find the correct method!
        find(initialMethodsOption.get) { mdc =>
            mdc.compareAccessibilityAware(callerPackageName, name, descriptor)
        } foreach (mdc => methods += mdc.method)

        if (methods.nonEmpty) {
            val method = methods.head
            if (method.isPrivate) {
                // The concrete method is private, thus it is selected (JVM 11 Spec Section 5.4.6)
                // However, access control may still fail
                if (!method.isAccessibleBy(callerType, nests))
                    return SomeSet.empty[Method];
                return methods;
            } else if (method.classFile.thisType eq declaringClassType) {
                // The (concrete) method belongs to this class... hence, we just need to
                // get all methods which override (reflexive) this method and are done.
                return overriddenBy(method);
            } else {
                // ... we cannot use the overriddenBy methods because this could return
                // methods belonging to classes which are not a subtype of the given
                // declaring class.
                classHierarchy.foreachSubtypeCF(declaringClassType, reflexive = false) { subtypeCF =>
                    val subtype = subtypeCF.thisType
                    val mdcOption = find(instanceMethods(subtype)) { mdc =>
                        mdc.compareAccessibilityAware(callerPackageName, name, descriptor)
                    }
                    if (mdcOption.nonEmpty && (mdcOption.get.method ne method)
                        && !mdcOption.get.method.isPrivate) {
                        methods ++= overriddenBy(mdcOption.get.method)
                        false // we don't have to look into furthersubtypes
                    } else {
                        true
                    }
                }
                return methods;
            }
        }

        // We just have to collect the methods in the subtypes... unless we have
        // a call to a signature polymorphic method!

        def findSignaturePolymorphicMethod(
            descriptor: MethodDescriptor
        ): Option[MethodDeclarationContext] = {
            find(instanceMethods(declaringClassType)) { mdc =>
                mdc.compareAccessibilityAware(callerPackageName, name, descriptor)
            }
        }

        if (MethodHandleClassFile.isDefined && MethodHandleSubtypes.contains(declaringClassType)) {
            val mdcOption = findSignaturePolymorphicMethod(SignaturePolymorphicMethodObject)
            if (mdcOption.isDefined) {
                val method = mdcOption.get.method
                if (method.isNativeAndVarargs && (method.classFile eq MethodHandleClassFile.get)) {
                    return Set(method);
                }
            }
        }

        if (VarHandleClassFile.isDefined && VarHandleSubtypes.contains(declaringClassType)) {
            val mdcOption =
                findSignaturePolymorphicMethod(SignaturePolymorphicMethodObject).orElse(
                    findSignaturePolymorphicMethod(SignaturePolymorphicMethodVoid).orElse(
                        findSignaturePolymorphicMethod(SignaturePolymorphicMethodBoolean)
                    )
                )
            if (mdcOption.isDefined) {
                val method = mdcOption.get.method
                if (method.isNativeAndVarargs && (method.classFile eq VarHandleClassFile.get)) {
                    return Set(method);
                }
            }
        }

        classHierarchy.foreachSubtypeCF(declaringClassType, reflexive = false) { subtypeCF =>
            val subtype = subtypeCF.thisType
            val mdcOption = find(instanceMethods(subtype)) { mdc =>
                mdc.compareAccessibilityAware(callerPackageName, name, descriptor)
            }
            mdcOption match {
                case Some(mdc) if !mdc.method.isPrivate =>
                    if (methods.isEmpty) {
                        methods = mutable.Set.from(overriddenBy(mdc.method))
                    } else {
                        methods ++= overriddenBy(mdc.method)
                    }
                    false // we don't have to look into furthersubtypes
                case _ /*None*/ =>
                    true
            }
        }
        methods

    }

}

object ProjectLike {

    /**
     * Computes the set of maximally specific superinterface methods with the
     * given name and descriptor.
     *
     * @note    This method requires that the class hierarchy is already computed.
     *          It does not require `instanceMethods`.
     * @note    '''This method does not consider methods defined by `java.lang.Object`'''!
     *          Those methods have precedence over respective methods defined by
     *          superinterfaces! A corresponding check needs to be done before calling
     *          this method.
     */
    def findMaximallySpecificSuperinterfaceMethods(
        superinterfaceType:          ObjectType,
        name:                        String,
        descriptor:                  MethodDescriptor,
        analyzedSuperinterfaceTypes: UIDSet[ObjectType] = UIDSet.empty
    )(
        implicit
        objectTypeToClassFile: ObjectType => Option[ClassFile],
        classHierarchy:        ClassHierarchy,
        logContext:            LogContext
    ): ( /*analyzed types*/ UIDSet[ObjectType], Set[Method]) = {

        val newAnalyzedSuperinterfaceTypes = analyzedSuperinterfaceTypes + superinterfaceType

        // the superinterfaceTypes in which it is potentially relevant to search for methods
        val superinterfaceTypes: UIDSet[ObjectType] =
            classHierarchy.superinterfaceTypes(superinterfaceType).getOrElse(UIDSet.empty) --
                analyzedSuperinterfaceTypes

        objectTypeToClassFile(superinterfaceType) match {
            case Some(classFile) =>
                if (!classFile.isInterfaceDeclaration) {
                    OPALLogger.warn(
                        "project configuration",
                        "finding the maximally specific superinterface methods failed: "+
                            s"${superinterfaceType.toJava} is not an interface and ignored"
                    )
                    (analyzedSuperinterfaceTypes ++ superinterfaceTypes + superinterfaceType, Set.empty)
                } else {
                    classFile.findMethod(name, descriptor) match {
                        case Some(method) if !method.isPrivate && !method.isStatic =>
                            val analyzedTypes = newAnalyzedSuperinterfaceTypes ++ superinterfaceTypes
                            (analyzedTypes, Set(method))

                        case _ /* None OR "the method was either private or static" */ =>
                            if (superinterfaceTypes.isEmpty) {
                                (newAnalyzedSuperinterfaceTypes, Set.empty)
                            } else if (superinterfaceTypes.isSingletonSet) {
                                findMaximallySpecificSuperinterfaceMethods(
                                    superinterfaceTypes.head,
                                    name, descriptor,
                                    newAnalyzedSuperinterfaceTypes
                                )
                            } else {
                                findMaximallySpecificSuperinterfaceMethods(
                                    superinterfaceTypes,
                                    name, descriptor,
                                    newAnalyzedSuperinterfaceTypes
                                )
                            }
                    }
                }

            case None =>
                (analyzedSuperinterfaceTypes ++ superinterfaceTypes + superinterfaceType, Set.empty)
        }
    }

    /**
     * Computes the maximally specific superinterface method with the given name
     * and descriptor
     *
     * @note    This method requires that the class hierarchy is already computed.
     *          It does not required `instanceMethods`.
     * @param   superinterfaceTypes A set of interfaces which potentially declare a method
     *          with the given name and descriptor.
     */
    def findMaximallySpecificSuperinterfaceMethods(
        superinterfaceTypes:         UIDSet[ObjectType],
        name:                        String,
        descriptor:                  MethodDescriptor,
        analyzedSuperinterfaceTypes: UIDSet[ObjectType]
    )(
        implicit
        objectTypeToClassFile: (ObjectType) => Option[ClassFile],
        classHierarchy:        ClassHierarchy,
        logContext:            LogContext
    ): ( /*analyzed types*/ UIDSet[ObjectType], Set[Method]) = {

        val anchor = ((analyzedSuperinterfaceTypes, Set.empty[Method]))

        superinterfaceTypes.foldLeft(anchor) { (currentResult, interfaceType) =>
            val (currentAnalyzedSuperinterfaceTypes, currentMethods) = currentResult
            val (analyzedSuperinterfaceTypes, methods) =
                findMaximallySpecificSuperinterfaceMethods(
                    interfaceType, name, descriptor,
                    currentAnalyzedSuperinterfaceTypes
                )

            val allMethods = currentMethods ++ methods
            if (allMethods.isEmpty || allMethods.size == 1) {
                (analyzedSuperinterfaceTypes, allMethods /*empty or singleton set*/ )
            } else {
                // When we reach this point, we may have a situation such as:
                //     intf A { default void foo(){} }
                //     intf B extends A { default void foo(){} }
                //     intf C extends A { }
                //     intf D extends B { }
                // and we started the analysis with the set {C,D} and
                // first selected C (hence, first found A.foo).
                //
                // We now have to determine the maximally specific method.

                // Both, the set of `currentMethods` and also the set of `methods`
                // each only contains maximally specific methods w.r.t. their
                // set.
                var currentMaximallySpecificMethods = currentMethods
                var additionalMaximallySpecificMethods = Set.empty[Method]
                methods foreach { method =>
                    if (!currentMethods.contains(method)) {
                        val newMethodDeclaringClassType = method.classFile.thisType
                        var addNewMethod = true
                        currentMaximallySpecificMethods =
                            currentMaximallySpecificMethods.filter { currentMaximallySpecificMethod =>
                                val specificMethodDeclaringClassType = currentMaximallySpecificMethod.classFile.thisType
                                if (specificMethodDeclaringClassType isSubtypeOf newMethodDeclaringClassType) {
                                    addNewMethod = false
                                    true
                                } else if (newMethodDeclaringClassType isSubtypeOf specificMethodDeclaringClassType) {
                                    false
                                } else {
                                    //... we have an incomplete class hierarchy;
                                    // let's keep both methods
                                    true
                                }
                            }
                        if (addNewMethod) additionalMaximallySpecificMethods += method
                    }
                }
                currentMaximallySpecificMethods ++= additionalMaximallySpecificMethods

                val concreteMaximallySpecificMethods = currentMaximallySpecificMethods.filter(!_.isAbstract)
                if (concreteMaximallySpecificMethods.isEmpty) {
                    // We have not yet found any method or we may have multiple abstract methods...
                    (analyzedSuperinterfaceTypes, currentMaximallySpecificMethods)
                } else {
                    (analyzedSuperinterfaceTypes, concreteMaximallySpecificMethods)
                }
            }
        }
    }

    private val EnforceJava11CallSemanticsConfigKey =
        "org.opalj.br.Project.enforceJava11CallSemantics"
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy