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

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

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

import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
import java.util.function.{Function => JFunction}
import org.opalj.br.ObjectType.MethodHandle
import org.opalj.br.ObjectType.VarHandle
import org.opalj.br.MethodDescriptor.SignaturePolymorphicMethodBoolean
import org.opalj.br.MethodDescriptor.SignaturePolymorphicMethodObject
import org.opalj.br.MethodDescriptor.SignaturePolymorphicMethodVoid

import scala.collection.immutable.ArraySeq
import scala.jdk.CollectionConverters._

/**
 * The ''key'' object to get information about all declared methods.
 *
 * @note See [[org.opalj.br.DeclaredMethod]] for further details.
 * @example To get the index use the [[org.opalj.br.analyses.Project]]'s `get` method and pass in
 *          `this` object.
 *
 * @author Dominik Helm
 * @author Florian Kuebler
 */
object DeclaredMethodsKey extends ProjectInformationKey[DeclaredMethods, Nothing] {

    // The following lists were created using the Java 10 specification
    private val methodHandleSignaturePolymorphicMethods = List(
        "invoke",
        "invokeExact"
    )

    private val varHandleSignaturePolymorphicMethods = List(
        "get",
        "set",
        "getVolatile",
        "setVolatile",
        "getOpaque",
        "setOpaque",
        "getAcquire",
        "setRelease",
        "compareAndSet",
        "compareAndExchange",
        "compareAndExchangeAcquire",
        "compareAndExchangeRelease",
        "weakCompareAndSetPlain",
        "weakCompareAndSet",
        "weakCompareAndSetAcquire",
        "weakCompareAndSetRelease",
        "getAndSet",
        "getAndSetAcquire",
        "getAndSetRelease",
        "getAndAdd",
        "getAndAddAcquire",
        "getAndAddRelease",
        "getAndBitwiseOr",
        "getAndBitwiseOrAcquire",
        "getAndBitwiseOrRelease",
        "getAndBitwiseAnd",
        "getAndBitwiseAndAcquire",
        "getAndBitwiseAndRelease",
        "getAndBitwiseXor",
        "getAndBitwiseXorAcquire",
        "getAndBitwiseXorRelease"
    )

    /**
     * The analysis has no special prerequisites.
     *
     * @return `Nil`.
     */
    override def requirements(project: SomeProject): Seq[ProjectInformationKey[Nothing, Nothing]] = Nil

    // TODO [Java9+] Needs to be updated for Java9+ projects which use Modules.
    /**
     * Collects all declared methods.
     */
    override def compute(p: SomeProject): DeclaredMethods = {

        val result: ConcurrentHashMap[ReferenceType, ConcurrentHashMap[MethodContext, DeclaredMethod]] =
            new ConcurrentHashMap

        val mapFactory: JFunction[ReferenceType, ConcurrentHashMap[MethodContext, DeclaredMethod]] =
            (_: ReferenceType) => { new ConcurrentHashMap() }

        val idCounter = new AtomicInteger()

        def insertDeclaredMethod(
            dms:                   ConcurrentHashMap[MethodContext, DeclaredMethod],
            context:               MethodContext,
            computeDeclaredMethod: Int => DeclaredMethod
        ): Unit = {
            var computedDM: DeclaredMethod = null
            val oldDm = dms.computeIfAbsent(context, _ => {
                computedDM = computeDeclaredMethod(idCounter.getAndIncrement())
                computedDM
            })

            assert(
                (computedDM ne null) || {
                    computedDM = computeDeclaredMethod(0)
                    oldDm match {
                        case dm: DefinedMethod =>
                            computedDM.hasSingleDefinedMethod &&
                                (dm.definedMethod eq computedDM.definedMethod)
                        case mdm: MultipleDefinedMethods =>
                            mdm.hasMultipleDefinedMethods &&
                                mdm.definedMethods.size == computedDM.definedMethods.size &&
                                mdm.definedMethods.forall(computedDM.definedMethods.contains)
                        case _: VirtualDeclaredMethod => true
                    }
                },
                "creation of declared methods failed:\n\t"+
                    s"$oldDm\n\t\tvs.(new)\n\t$computedDM}"
            )
        }

        p.parForeachClassFile() { cf =>
            val classType = cf.thisType

            // The set to add the methods for this class to
            val dms = result.computeIfAbsent(classType, mapFactory)

            for {
                // all methods present in the current class file, excluding methods derived
                // from any supertype that are not overridden by this type.
                m <- cf.methods
                if m.isStatic || m.isAbstract || m.isInitializer
            } {
                if (m.isAbstract) {
                    // Abstract methods can be inherited, but will not appear as instance methods
                    // for subtypes, so we have to add them manually for all subtypes that don't
                    // override/implement them here
                    p.classHierarchy.processSubtypes(classType)(null) {
                        (_: Null, subtype: ObjectType) =>
                            val subClassFile = p.classFile(subtype).get
                            val subtypeDms = result.computeIfAbsent(subtype, mapFactory)
                            if (subClassFile.findMethod(m.name, m.descriptor).isEmpty) {
                                val interfaceMethods =
                                    p.resolveAllMethodReferences(subtype, m.name, m.descriptor)
                                interfaceMethods.size match {
                                    case 0 =>
                                    case 1 =>
                                        val interfaceMethod = interfaceMethods.head
                                        insertDeclaredMethod(
                                            subtypeDms,
                                            MethodContext(p, subtype, interfaceMethod),
                                            id => new DefinedMethod(subtype, interfaceMethod, id)
                                        )
                                    case _ =>
                                        val methods = ArraySeq.from(interfaceMethods)
                                        insertDeclaredMethod(
                                            subtypeDms,
                                            new MethodContext(m.name, m.descriptor),
                                            id => new MultipleDefinedMethods(
                                                subtype,
                                                methods,
                                                id
                                            )
                                        )
                                }

                                (null, false, false) // Continue traversal on non-overridden method
                            } else {
                                (null, true, false) // Stop traversal on overridden method
                            }
                    }
                } else if (m.isStatic && !m.isPrivate &&
                    !m.isStaticInitializer && !cf.isInterfaceDeclaration) {
                    // Static methods are inherited as well - they can be invoked on subtypes
                    // this is not true for static initializers and static methods on interfaces
                    p.classHierarchy.processSubtypes(classType)(initial = null) {
                        (_: Null, subtype: ObjectType) =>
                            val subClassFile = p.classFile(subtype).get
                            val subtypeDms = result.computeIfAbsent(subtype, mapFactory)
                            if (subClassFile.findMethod(m.name, m.descriptor).isEmpty) {
                                val staticMethodResult = p.staticCall(
                                    subtype,
                                    subtype,
                                    isInterface = false,
                                    m.name,
                                    m.descriptor
                                )
                                if (staticMethodResult.hasValue) {
                                    val staticMethod = staticMethodResult.value
                                    insertDeclaredMethod(
                                        subtypeDms,
                                        MethodContext(p, subtype, staticMethod),
                                        id => new DefinedMethod(subtype, staticMethod, id)
                                    )
                                    // Continue traversal on non-overridden method
                                    (null, false, false)
                                } else {
                                    // This can only happen with a broken class hierarchy such that
                                    // `subtype` implements `classType` but `classType` isn't an
                                    // interface

                                    // Stop traversal
                                    (null, true, false)
                                }
                            } else {
                                (null, true, false) // Stop traversal on overridden method
                            }
                    }
                }
                val context = MethodContext(p, classType, m)
                insertDeclaredMethod(dms, context, id => new DefinedMethod(classType, m, id))
            }

            for {
                // all non-abstract instance methods present in the current class file,
                // including methods derived from any supertype that are not overridden by this type
                mc <- p.instanceMethods(classType)
            } {
                val context = MethodContext(p, classType, mc.method)
                insertDeclaredMethod(dms, context, id => new DefinedMethod(classType, mc.method, id))
            }
        }

        // Special handling for signature-polymorphic methods
        if (p.MethodHandleClassFile.isEmpty) {
            val dms = result.computeIfAbsent(MethodHandle, _ => new ConcurrentHashMap)
            for (name <- methodHandleSignaturePolymorphicMethods) {
                val context = new MethodContext(name, SignaturePolymorphicMethodObject)
                insertDeclaredMethod(dms, context, id => new VirtualDeclaredMethod(
                    MethodHandle, name, SignaturePolymorphicMethodObject, id
                ))
            }
        }
        if (p.VarHandleClassFile.isEmpty) {
            val dms = result.computeIfAbsent(VarHandle, _ => new ConcurrentHashMap)
            for (name <- varHandleSignaturePolymorphicMethods) {
                val descriptor = if (name == "compareAndSet" || name.startsWith("weak"))
                    SignaturePolymorphicMethodBoolean
                else if (name.startsWith("set"))
                    SignaturePolymorphicMethodVoid
                else if (name.startsWith("get") || name.startsWith("compare"))
                    SignaturePolymorphicMethodObject
                else
                    throw new IllegalArgumentException(
                        s"Unexpected signature polymorphic method $name"
                    )
                val context = new MethodContext(name, descriptor)
                insertDeclaredMethod(dms, context, id => new VirtualDeclaredMethod(
                    VarHandle, name, descriptor, id
                ))
            }
        }

        val id2method = new Array[DeclaredMethod](idCounter.get() + 1000)

        for {
            context2Method <- result.elements().asScala
            dm <- context2Method.elements().asScala
        } {
            id2method(dm.id) = dm
        }

        new DeclaredMethods(p, result, id2method, idCounter.get)
    }

    /**
     * Represents a non-package-private method by its signature. The hashCode method is the same as
     * the one in [[PackagePrivateMethodContext]], so this is found when a HashMap is queried with
     * a `PackagePrivateMethodContext`, in which case the equals method guarantees that it matches
     * any `PackagePrivateMethodContext` with the same signature.
     */
    private[analyses] sealed class MethodContext(
            val methodName: String,
            val descriptor: MethodDescriptor
    ) {

        override def equals(other: Any): Boolean = other match {
            case that: MethodContext =>
                methodName == that.methodName && descriptor == that.descriptor
            case _ => false
        }

        override def hashCode(): Int = {
            methodName.hashCode * 31 + descriptor.hashCode()
        }
    }

    private[analyses] object MethodContext {
        /**
         * Factory method for [[MethodContext]]/[[PackagePrivateMethodContext]] depending on
         * whether the given method is package-private or not.
         */
        def apply(project: SomeProject, objectType: ObjectType, method: Method): MethodContext = {
            MethodContext(
                project,
                objectType,
                method.classFile.thisType.packageName,
                method.name,
                method.descriptor,
                method.isPackagePrivate
            )
        }

        /**
         * Factory method for [[MethodContext]]/[[PackagePrivateMethodContext]] depending on
         * whether the given method is package-private or not.
         */
        def apply(
            project:          SomeProject,
            objectType:       ObjectType,
            declaringPackage: String,
            methodName:       String,
            descriptor:       MethodDescriptor,
            isPackagePrivate: Boolean
        ): MethodContext = {
            if (isPackagePrivate)
                new PackagePrivateMethodContext(declaringPackage, methodName, descriptor)
            else if (project.classFile(objectType).isDefined &&
                project.hasInstanceMethod(objectType, methodName, descriptor, true))
                new ShadowsPackagePrivateMethodContext(methodName, descriptor)
            else
                new MethodContext(methodName, descriptor)
        }
    }

    /**
     * Represents a package-private method by its declaring package and signature.
     * The hashCode method is that from [[MethodContext]] (i.e. it does not include the package
     * name), so it can be used to query a HashMap for a `MethodContext`, in which case the
     * equals method guarantees that it matches a `MethodContext` with the same signature regardless
     * of the package name. It only matches another [[PackagePrivateMethodContext]] if the package
     * names are the same, though.
     */
    private[this] class PackagePrivateMethodContext(
            val packageName: String,
            methodName:      String,
            descriptor:      MethodDescriptor
    ) extends MethodContext(methodName, descriptor) {

        override def equals(other: Any): Boolean = other match {
            case that: MethodContextQuery => that.equals(this)
            case that: PackagePrivateMethodContext =>
                packageName == that.packageName &&
                    methodName == that.methodName &&
                    descriptor == that.descriptor
            case _: ShadowsPackagePrivateMethodContext => false
            case that: MethodContext =>
                methodName == that.methodName && descriptor == that.descriptor
            case _ => false
        }
    }

    /**
     * Represents a protected or public method that shadows one or more package-private methods
     * from supertypes in other packages.
     * The hashCode method is that from [[MethodContext]], so it can be used to query a HashMap for
     * a `MethodContext`, in which case the equals method guarantees that it matches a
     * `MethodContext` with the same signature regardless of the package name.
     */
    private[this] class ShadowsPackagePrivateMethodContext(
            methodName: String,
            descriptor: MethodDescriptor
    ) extends MethodContext(methodName, descriptor) {

        override def equals(other: Any): Boolean = other match {
            case that: MethodContextQuery       => that.equals(this)
            case _: PackagePrivateMethodContext => false
            case that: MethodContext =>
                methodName == that.methodName && descriptor == that.descriptor
            case _ => false
        }
    }

    /**
     * Represents a virtual call site by the declared receiver type, the package of the caller and
     * the method signature.
     * Used to query a HashMap for different kinds of [[MethodContext]]s, which all share the same
     * hashCode, but differ in their equality with this type.
     */
    private[analyses] class MethodContextQuery(
            project:          SomeProject,
            val receiverType: ObjectType,
            val packageName:  String,
            methodName:       String,
            descriptor:       MethodDescriptor
    ) extends MethodContext(methodName, descriptor) {

        override def equals(other: Any): Boolean = other match {
            case that: PackagePrivateMethodContext =>
                packageName == that.packageName &&
                    methodName == that.methodName &&
                    descriptor == that.descriptor &&
                    isPackagePrivateMethod
            case that: ShadowsPackagePrivateMethodContext =>
                methodName == that.methodName &&
                    descriptor == that.descriptor &&
                    !isPackagePrivateMethod
            case that: MethodContext =>
                methodName == that.methodName && descriptor == that.descriptor
            case _ => false
        }

        private def isPackagePrivateMethod: Boolean = {
            if (project.classHierarchy.isInterface(receiverType).isYes) {
                false
            } else {
                val method = project.resolveClassMethodReference(
                    receiverType,
                    methodName,
                    descriptor
                )
                method.hasValue && method.value.isPackagePrivate &&
                    method.value.declaringClassFile.thisType.packageName == packageName
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy