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

org.opalj.br.Method.scala Maven / Gradle / Ivy

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

import scala.collection.{Map => SomeMap}
import scala.math.Ordered
import org.opalj.bi.ACC_ABSTRACT
import org.opalj.bi.ACC_STRICT
import org.opalj.bi.ACC_NATIVE
import org.opalj.bi.ACC_BRIDGE
import org.opalj.bi.ACC_VARARGS
import org.opalj.bi.ACC_SYNCHRONIZED
import org.opalj.bi.ACC_PUBLIC
import org.opalj.bi.ACC_PRIVATE
import org.opalj.bi.ACC_PROTECTED
import org.opalj.bi.AccessFlagsContexts
import org.opalj.bi.AccessFlags
import org.opalj.bi.VisibilityModifier
import org.opalj.br.instructions.ALOAD_0
import org.opalj.br.instructions.INVOKESPECIAL
import org.opalj.br.instructions.RETURN
import org.opalj.br.instructions.Instruction

import scala.collection.immutable.ArraySeq

/**
 * Represents a single method.
 *
 * Method objects are constructed using the companion object's factory methods.
 *
 * @note   Methods, which are directly created, have no link to "their defining" [[ClassFile]].
 *         This link is implicitly established when a method is added to a [[ClassFile]]. This
 *         operation also updates the method object. Hence, an empty method/constructor which
 *         is identical across multiple classes can be reused.
 *
 * @note   Equality of methods is – by purpose – reference based.
 *
 * @author Michael Eichberg
 * @author Marco Torsello
 */
sealed abstract class JVMMethod
    extends ClassMember
    with Ordered[JVMMethod]
    with InstructionsContainer {

    //
    //
    // THE STATE
    //
    //

    /**
     * The ''access flags'' of this method. Though it is possible to
     * directly work with the `accessFlags` field, it may be more convenient to use
     * the respective methods (`isNative`, `isAbstract`, ...) to query the access flags.
     */
    def accessFlags: Int

    /**
     * The name of the method. The name is interned (see `String.intern()`
     * for details) to enable reference comparisons.
     */
    def name: String

    /** This method's descriptor. */
    def descriptor: MethodDescriptor

    /** The body of the method if any. */
    def body: Option[Code]

    /**
     * This method's defined attributes. (Which attributes are available
     * generally depends on the configuration of the class file reader. However,
     * the `Code_Attribute` is – if it was loaded – always directly accessible by
     * means of the `body` attribute.)
     */
    def attributes: Attributes

    // This method is only to be called by ..br.ClassFile to associate this method
    // with the respective class file.
    private[br] def prepareClassFileAttachement(): Method = {
        new Method(
            null /*will be set by class file*/ ,
            accessFlags, name, descriptor, body, attributes
        )
    }

    /**
     * Creates a copy of this method object which is not associated with any class file.
     */
    def copy(
        accessFlags: Int              = this.accessFlags,
        name:        String           = this.name,
        descriptor:  MethodDescriptor = this.descriptor,
        body:        Option[Code]     = this.body,
        attributes:  Attributes       = this.attributes
    ): MethodTemplate = {
        // ensure invariant that the code attribute is explicitly extracted...
        assert(attributes.forall { a => a.kindId != Code.KindId })

        val n = if (this.name eq name) name else name.intern()

        new MethodTemplate(accessFlags, n, descriptor, body, attributes)
    }

    //
    //
    // THE METHODS
    //
    //

    /**
     * Compares this method with the given one for structural equality. The declaring class
     * file is ignored.
     *
     * Two methods are structurally equal if they have the same names, flags and descriptor.
     * The bodies and attributes are recursively checked for structural equality. In case of the
     * attributes, the order doesn't matter!
     */
    def similar(other: JVMMethod, config: SimilarityTestConfiguration): Boolean = {
        // IMPROVE Define a method "findDissimilarity" as in case of ClassFile to report the difference
        if (this.accessFlags != other.accessFlags ||
            this.name != other.name ||
            this.descriptor != other.descriptor) {
            return false;
        }

        val (thisBody, otherBody) = config.compareCode(this, this.body, other.body)
        if (!(
            (thisBody.isEmpty && otherBody.isEmpty) ||
            (
                thisBody.nonEmpty && otherBody.nonEmpty &&
                thisBody.get.similar(otherBody.get, config)
            )
        )) {
            return false;
        }

        compareAttributes(other.attributes, config).isEmpty
    }

    final override def instructionsOption: Option[Array[Instruction]] = body.map(_.instructions)

    /**
     * The number of registers required to store this method's parameters (
     * including the self reference if necessary).
     *
     * Basically, `MethodDescriptor.requiredRegisters` adapted by the required parameter for
     * `this` in case of an instance method.
     */
    def requiredRegisters: Int = {
        descriptor.requiredRegisters + (if (isStatic) 0 else 1)
    }

    /**
     * Returns `true` if this method has the given name and descriptor.
     *
     * @param  ignoreReturnType If `false`, then the return type is taken
     *         into consideration; this models the behavior of the JVM w.r.t. method
     *         dispatch.
     */
    def hasSignature(
        name:             String,
        descriptor:       MethodDescriptor,
        ignoreReturnType: Boolean
    ): Boolean = {
        this.name == name && {
            if (ignoreReturnType)
                this.descriptor.equalParameters(descriptor)
            else
                this.descriptor == descriptor
        }
    }

    /**
     * Returns `true` if this method and the given method have the same signature.
     *
     * @param ignoreReturnType If `false` (default), then the return type is taken
     *      into consideration. This models the behavior of the JVM w.r.t. method
     *      dispatch.
     *      However, if you want to determine whether this method potentially overrides
     *      the given one, you may want to specify that you want to ignore the return type.
     *      (The Java compiler generates the appropriate methods.)
     */
    def hasSignature(other: Method, ignoreReturnType: Boolean = false): Boolean = {
        this.hasSignature(other.name, other.descriptor, ignoreReturnType)
    }

    /**
     * Returns `true` if this method has the given name and descriptor.
     *
     * @note When matching the descriptor the return type is also taken into consideration.
     */
    def hasSignature(name: String, descriptor: MethodDescriptor): Boolean = {
        this.hasSignature(name, descriptor, false)
    }

    def signature: MethodSignature = new MethodSignature(name, descriptor)

    def runtimeVisibleParameterAnnotations: ParameterAnnotations = {
        attributes.collectFirst { case RuntimeVisibleParameterAnnotationTable(as) => as } match {
            case Some(annotations) => annotations
            case None              => NoParameterAnnotations
        }
    }

    def runtimeInvisibleParameterAnnotations: ParameterAnnotations = {
        attributes.collectFirst { case RuntimeInvisibleParameterAnnotationTable(as) => as } match {
            case Some(annotations) => annotations
            case None              => NoParameterAnnotations
        }
    }

    def parameterAnnotations: Iterator[Annotations] = {
        runtimeVisibleParameterAnnotations.iterator ++ runtimeInvisibleParameterAnnotations.iterator
    }

    /**
     * If this method represents a method of an annotation that defines a default
     * value then this value is returned.
     */
    def annotationDefault: Option[ElementValue] = {
        attributes collectFirst { case ev: ElementValue => ev }
    }

    /**
     * If this method has extended method parameter information, the `MethodParameterTable` is
     * returned.
     */
    def methodParameters: Option[MethodParameterTable] = {
        attributes collectFirst { case mp: MethodParameterTable => mp }
    }

    /**
     * Returns `Yes` if the parameter with the given index is synthetic; `No` if not and `Unknown`
     * if the information is not available. The indexes correspond to those used by the
     * [[MethodDescriptor]].
     */
    def isSyntheticParameter(parameterIndex: Int): Answer = {
        val mpsOpt = methodParameters
        if (mpsOpt.isEmpty)
            return Unknown;

        val mps = mpsOpt.get
        Answer(mps(parameterIndex).isSynthetic)
    }

    /**
     * Returns `Yes` if the parameter with the given index is mandated; `No` if not and `Unknown`
     * if the information is not available. The indexes correspond to those used by the
     * [[MethodDescriptor]].
     */
    def isMandatedParameter(parameterIndex: Int): Answer = {
        val mpsOpt = methodParameters
        if (mpsOpt.isEmpty)
            return Unknown;

        val mps = mpsOpt.get
        Answer(mps(parameterIndex).isMandated)
    }

    // This is directly supported due to its need for the resolution of signature
    // polymorphic methods.
    final def isNativeAndVarargs: Boolean = Method.isNativeAndVarargs(accessFlags)

    final def isVarargs: Boolean = (ACC_VARARGS.mask & accessFlags) != 0

    final def isSynchronized: Boolean = (ACC_SYNCHRONIZED.mask & accessFlags) != 0

    final def isBridge: Boolean = (ACC_BRIDGE.mask & accessFlags) != 0

    final def isNative: Boolean = (ACC_NATIVE.mask & accessFlags) != 0

    def isStrict: Boolean = (ACC_STRICT.mask & accessFlags) != 0

    final def isAbstract: Boolean = (ACC_ABSTRACT.mask & accessFlags) != 0

    final def isNotAbstract: Boolean = (ACC_ABSTRACT.mask & accessFlags) == 0

    final def isConstructor: Boolean = name == ""

    final def isStaticInitializer: Boolean = name == ""

    final def isInitializer: Boolean = isConstructor || isStaticInitializer

    /**
     * Returns true if this method is a potential target of a virtual call
     * by means of an invokevirtual or invokeinterface instruction; i.e.,
     * if the method is not an initializer, is not abstract, is not private
     * and is not static.
     */
    final def isVirtualCallTarget: Boolean = {
        isNotAbstract && !isPrivate && !isStatic && !isInitializer &&
            !isStaticInitializer // before Java 8  was not required to be static
    }

    /**
     * Returns true if this method declares a virtual method. This method
     * may be abstract!
     */
    final def isVirtualMethodDeclaration: Boolean = {
        !isPrivate && !isStatic && !isInitializer &&
            !isStaticInitializer // before Java 8  was not required to be static
    }

    def returnType: Type = descriptor.returnType

    def parameterTypes: FieldTypes = descriptor.parameterTypes

    /**
     * The number of explicit and implicit parameters of this method – that is,
     * including `this` in case of a non-static method.
     */
    def actualArgumentsCount: Int = (if (isStatic) 0 else 1) + descriptor.parametersCount

    /**
     * Each method optionally defines a method type signature.
     */
    def methodTypeSignature: Option[MethodTypeSignature] = {
        attributes collectFirst { case s: MethodTypeSignature => s }
    }

    def exceptionTable: Option[ExceptionTable] = {
        attributes collectFirst { case et: ExceptionTable => et }
    }

    /**
     * Defines an absolute order on `Method` instances based on their method signatures.
     *
     * The order is defined by lexicographically comparing the names of the methods
     * and – in case that the names of both methods are identical – by comparing
     * their method descriptors.
     */
    def compare(other: JVMMethod): Int = {
        if (this.name == other.name)
            this.descriptor.compare(other.descriptor)
        else
            this.name.compareTo(other.name)
    }

    def compare(otherName: String, otherDescriptor: MethodDescriptor): Int = {
        if (this.name == otherName)
            this.descriptor.compare(otherDescriptor)
        else
            this.name.compareTo(otherName)
    }

    def signatureToJava(withVisibility: Boolean = true): String = {
        val visibility =
            if (withVisibility)
                VisibilityModifier.get(accessFlags).map(_.javaName.get+" ").getOrElse("")
            else
                ""
        val static = if (isStatic) "static " else ""
        visibility + static + descriptor.toJava(name)
    }

    //
    //
    // DEBUGGING PURPOSES
    //
    //

    override def toString: String = {
        import AccessFlagsContexts.METHOD
        val jAccessFlags = AccessFlags.toStrings(accessFlags, METHOD).mkString(" ")
        val method =
            if (jAccessFlags.nonEmpty)
                jAccessFlags+" "+descriptor.toJava(name)
            else
                descriptor.toJava(name)

        if (attributes.nonEmpty)
            method + attributes.map(_.getClass.getSimpleName).mkString("«", ", ", "»")
        else
            method

    }

}

/**
 * A method which is not (yet) associated with a class file.
 */
final class MethodTemplate private[br] (
        val accessFlags: Int,
        val name:        String,
        val descriptor:  MethodDescriptor,
        val body:        Option[Code],
        val attributes:  Attributes
) extends JVMMethod {

    /** This template is not (yet) a [[Method]] which is a [[SourceElement]].  */
    override def isMethod: Boolean = false

}

/**
 * A method belonging to a class file. [[Method]] objects are created by creating a class file
 * using [[MethodTemplate]]s.
 *
 * @param declaringClassFile The declaring class file.
 */
final class Method private[br] (
        private[br] var declaringClassFile: ClassFile, // the back-link can be updated to enable efficient load-time transformations
        val accessFlags:                    Int,
        val name:                           String,
        val descriptor:                     MethodDescriptor,
        val body:                           Option[Code],
        val attributes:                     Attributes
) extends JVMMethod {

    // see ClassFile._UNSAFE_replaceMethod for THE usage!
    private[br] def detach(): this.type = { declaringClassFile = null; this }

    /**
     * This method's class file.
     */
    def classFile: ClassFile = declaringClassFile

    /**
     * @return This method as a [[VirtualMethod]].
     */
    def asVirtualMethod: VirtualMethod = asVirtualMethod(declaringClassFile.thisType)

    /**
     * This method as a virtual method belonging to the given declaring class type.
     */
    def asVirtualMethod(declaringClassType: ObjectType): VirtualMethod = {
        VirtualMethod(declaringClassType, name, descriptor)
    }

    def toJava: String = s"${classFile.thisType.toJava}{ ${signatureToJava(true)} }"

    override def toString: String = toJava

    /**
     * Creates a method object based on this method where the body is replaced by the code
     * returned by `Code.invalidBytecode`. This method is NOT replaced in its declaring class file.
     *
     * @param message A short descriptive method that states why the body was replaced.
     */
    def invalidBytecode(message: Option[String]): Method = {
        new Method(
            declaringClassFile,
            accessFlags,
            name,
            descriptor,
            Some(Code.invalidBytecode(descriptor, !isStatic, message)),
            attributes
        )
    }

    /**
     * A Java-like representation of the signature of this method; "the body" will contain
     * the given `methodInfo` data.
     */
    def toJava(methodInfo: String): String = {
        s"${classFile.thisType.toJava}{ ${signatureToJava(true)}{ $methodInfo } }"
    }

    /**
     * The fully qualified signature of this method.
     */
    def fullyQualifiedSignature: String = descriptor.toJava(s"${classFile.thisType.toJava}.$name")

    override def isMethod: Boolean = true

    override def asMethod: this.type = this

    /**
     *
     * @return wether this class is defined as strict. Starting from Java 17, this is true by default.
     *         Strict evaluation of float expressions was also required in Java 1.0 and 1.1.
     */
    override def isStrict: Boolean =
        if (this.classFile.version.major >= bi.Java17MajorVersion || this.classFile.version.major < bi.Java1_2MajorVersion)
            true
        else
            (ACC_STRICT.mask & accessFlags) != 0

    def isAccessibleBy(
        objectType: ObjectType,
        nests:      SomeMap[ObjectType, ObjectType]
    )(
        implicit
        classHierarchy: ClassHierarchy
    ): Boolean = {
        visibilityModifier match {
            // TODO Respect Java 9 modules
            case Some(ACC_PUBLIC) => true
            case Some(ACC_PROTECTED) =>
                declaringClassFile.thisType.packageName == objectType.packageName ||
                    objectType.isASubtypeOf(declaringClassFile.thisType).isNotNo
            case Some(ACC_PRIVATE) =>
                val thisType = declaringClassFile.thisType
                thisType == objectType ||
                    nests.getOrElse(thisType, thisType) == nests.getOrElse(objectType, objectType)
            case None => declaringClassFile.thisType.packageName == objectType.packageName
        }
    }
}

/**
 * Defines factory and extractor methods for `Method` objects.
 *
 * @author Michael Eichberg
 */
object Method {

    @inline def isNativeAndVarargs(accessFlags: Int): Boolean = {
        import AccessFlags.ACC_NATIVE_VARARGS
        (accessFlags & ACC_NATIVE_VARARGS) == ACC_NATIVE_VARARGS
    }

    /**
     * Returns `true` if the method is object serialization related.
     * That is, if the declaring class is `Externalizable` then the methods `readObject` and
     * `writeObject` are unused.
     * If the declaring class is '''only''' `Seralizable`, then the write and read
     * external methods are not serialization related unless a subclass exists that inherits
     * these two methods and implements the interface `Externalizable`.
     *
     * @note Calling this method only makes sense if the given class or a subclass thereof
     *       is at least `Serializable`.
     *
     * @param method A method defined by a class that inherits from Serializable or which has
     *          at least one sublcass that is Serializable and that inherits the given method.
     * @param isInheritedBySerializableOnlyClass This parameter should be `Yes` iff this method is
     *      defined in a `Serializable` class or is inherited by at least one class that is
     *      (just) `Serializable`, but which is not `Externalizable`.
     * @param isInheritedByExternalizableClass This parameter should be `Yes` iff the method's
     *      defining class is `Externalizable` or if this method is inherited by at least one class
     *      that is `Externalizable`.
     */
    def isObjectSerializationRelated(
        method:                             Method,
        isInheritedBySerializableOnlyClass: => Answer,
        isInheritedByExternalizableClass:   => Answer
    ): Boolean = {
        import MethodDescriptor.JustReturnsObject
        import MethodDescriptor.NoArgsAndReturnVoid
        import MethodDescriptor.ReadObjectDescriptor
        import MethodDescriptor.WriteObjectDescriptor
        import MethodDescriptor.ReadObjectInputDescriptor
        import MethodDescriptor.WriteObjectOutputDescriptor

        val name = method.name
        val descriptor = method.descriptor
        /*The default constructor is used by the deserialization process*/
        (name == "" && descriptor == NoArgsAndReturnVoid) ||
            (name == "readObjectNoData" && descriptor == NoArgsAndReturnVoid) ||
            (name == "readResolve" && descriptor == JustReturnsObject) ||
            (name == "writeReplace" && descriptor == JustReturnsObject) ||
            ((
                (name == "readObject" && descriptor == ReadObjectDescriptor) ||
                (name == "writeObject" && descriptor == WriteObjectDescriptor)
            ) && isInheritedBySerializableOnlyClass.isYesOrUnknown) ||
                (
                    method.isPublic /*we are implementing an interface...*/ &&
                    (
                        (name == "readExternal" && descriptor == ReadObjectInputDescriptor) ||
                        (name == "writeExternal" && descriptor == WriteObjectOutputDescriptor)
                    ) &&
                        isInheritedByExternalizableClass.isYesOrUnknown
                )
    }

    /**
     * Returns `true` if a method declared by a subclass in the package
     * `declaringPackageOfSubclassMethod` can directly override a method which has the
     *  given visibility and package.
     */
    def canDirectlyOverride(
        declaringPackageOfSubclassMethod:   String,
        superclassMethodVisibility:         Option[VisibilityModifier],
        declaringPackageOfSuperclassMethod: String
    ): Boolean = {
        superclassMethodVisibility match {
            case Some(ACC_PUBLIC) | Some(ACC_PROTECTED) => true
            case Some(ACC_PRIVATE)                      => false

            case None =>
                declaringPackageOfSubclassMethod == declaringPackageOfSuperclassMethod
        }
    }

    /**
     * @param   name The name of the method. In case of a constructor the method
     *          name has to be "". In case of a static initializer the name has to
     *          be "".
     */
    def apply(
        accessFlags: Int,
        name:        String,
        descriptor:  MethodDescriptor,
        attributes:  Attributes
    ): MethodTemplate = {

        val (bodies, remainingAttributes) = partitionByType(attributes, classOf[Code])
        val body = bodies.headOption

        new MethodTemplate(
            accessFlags,
            name.intern(),
            descriptor,
            body,
            remainingAttributes
        )
    }

    // Only to be called by the class file reader!
    protected[br] def unattached(
        accessFlags: Int,
        name:        String,
        descriptor:  MethodDescriptor,
        attributes:  Attributes
    ): Method = {

        val (bodies, remainingAttributes) = partitionByType(attributes, classOf[Code])
        val body = bodies.headOption

        new Method(
            null,
            accessFlags,
            name.intern(),
            descriptor,
            body,
            remainingAttributes
        )
    }

    /**
     * Factory for MethodTemplate objects.
     *
     * @example A new method that is public abstract that takes no parameters and
     *          returns void and has the name "myMethod" can be created as shown next:
     *          {{{
     *          val myMethod = Method(name="myMethod");
     *          }}}
     */
    def apply(
        accessFlags:    Int        = ACC_ABSTRACT.mask | ACC_PUBLIC.mask,
        name:           String,
        parameterTypes: FieldTypes = NoFieldTypes,
        returnType:     Type       = VoidType,
        attributes:     Attributes = ArraySeq.empty
    ): MethodTemplate = {
        Method(accessFlags, name, MethodDescriptor(parameterTypes, returnType), attributes)
    }

    def unapply(method: JVMMethod): Option[(Int, String, MethodDescriptor)] = {
        Some((method.accessFlags, method.name, method.descriptor))
    }

    def defaultConstructor(superclassType: ObjectType = ObjectType.Object): MethodTemplate = {
        import MethodDescriptor.NoArgsAndReturnVoid
        val body = Some(Code(
            maxStack = 1,
            maxLocals = 1,
            instructions = Array(
                ALOAD_0,
                INVOKESPECIAL(superclassType, false, "", NoArgsAndReturnVoid),
                null,
                null,
                RETURN
            )
        ))
        val accessFlags = ACC_PUBLIC.mask
        new MethodTemplate(accessFlags, "", NoArgsAndReturnVoid, body, ArraySeq.empty)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy