/* 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) } }