@@ -10,7 +10,7 @@ package opt
1010import scala .tools .asm
1111import asm .tree ._
1212import scala .collection .JavaConverters ._
13- import scala .collection .concurrent
13+ import scala .collection .{ concurrent , mutable }
1414import scala .tools .asm .Attribute
1515import scala .tools .nsc .backend .jvm .BackendReporting ._
1616import scala .tools .nsc .io .AbstractFile
@@ -132,38 +132,135 @@ class ByteCodeRepository[BT <: BTypes](val classPath: ClassFileLookup[AbstractFi
132132 * The method node for a method matching `name` and `descriptor`, accessed in class `ownerInternalNameOrArrayDescriptor`.
133133 * The declaration of the method may be in one of the parents.
134134 *
135- * TODO: make sure we always return the right method, the one being invoked. write tests.
136- * - if there's an abstract and a concrete one. could possibly somehow the abstract be returned?
137- * - with traits and default methods, if there is more than one default method inherited and
138- * no override: what should be returned? We should not just inline one of the two.
135+ * Note that the JVM spec performs method lookup in two steps: resolution and selection.
136+ *
137+ * Method resolution, defined in jvms-5.4.3.3 and jvms-5.4.3.4, is the first step and is identical
138+ * for all invocation styles (virtual, interface, special, static). If C is the receiver class
139+ * in the invocation instruction:
140+ * 1 find a matching method (name and descriptor) in C
141+ * 2 then in C's superclasses
142+ * 3 then find the maximally-specific matching superinterface methods, succeed if there's a
143+ * single non-abstract one. static and private methods in superinterfaces are not considered.
144+ * 4 then pick a random non-static, non-private superinterface method.
145+ * 5 then fail.
146+ *
147+ * Note that for an `invokestatic` instruction, a method reference `B.m` may resolve to `A.m`, if
148+ * class `B` doesn't specify a matching method `m`, but the parent `A` does.
149+ *
150+ * Selection depends on the invocation style and is defined in jvms-6.5.
151+ * - invokestatic: invokes the resolved method
152+ * - invokevirtual / invokeinterface: searches for an override of the resolved method starting
153+ * at the dynamic receiver type. the search procedure is basically the same as in resolution,
154+ * but it fails at 4 instead of picking a superinterface method at random.
155+ * - invokespecial: if C is the receiver in the invocation instruction, searches for an override
156+ * of the resolved method starting at
157+ * - the superclass of the current class, if C is a superclass of the current class
158+ * - C otherwise
159+ * again, the search procedure is the same.
160+ *
161+ * In the method here we implement method *resolution*. Whether or not the returned method is
162+ * actually invoked at runtime depends on the invocation instruction and the class hierarchy, so
163+ * the users (e.g. the inliner) have to be aware of method selection.
164+ *
165+ * Note that the returned method may be abstract (ACC_ABSTRACT), native (ACC_NATIVE) or signature
166+ * polymorphic (methods `invoke` and `invokeExact` in class `MehtodHandles`).
139167 *
140168 * @return The [[MethodNode ]] of the requested method and the [[InternalName ]] of its declaring
141- * class, or an error message if the method could not be found.
169+ * class, or an error message if the method could not be found. An error message is also
170+ * returned if method resolution results in multiple default methods.
142171 */
143172 def methodNode (ownerInternalNameOrArrayDescriptor : String , name : String , descriptor : String ): Either [MethodNotFound , (MethodNode , InternalName )] = {
144- // on failure, returns a list of class names that could not be found on the classpath
145- def methodNodeImpl (ownerInternalName : InternalName ): Either [List [ClassNotFound ], (MethodNode , InternalName )] = {
146- classNode(ownerInternalName) match {
147- case Left (e) => Left (List (e))
148- case Right (c) =>
149- c.methods.asScala.find(m => m.name == name && m.desc == descriptor) match {
150- case Some (m) => Right ((m, ownerInternalName))
151- case None => findInParents(Option (c.superName) ++: c.interfaces.asScala.toList, Nil )
152- }
173+ def findMethod (c : ClassNode ): Option [MethodNode ] = c.methods.asScala.find(m => m.name == name && m.desc == descriptor)
174+
175+ // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9: "In Java SE 8, the only
176+ // signature polymorphic methods are the invoke and invokeExact methods of the class MethodHandle.
177+ def isSignaturePolymorphic (owner : InternalName ) = owner == coreBTypes.jliMethodHandleRef.internalName && (name == " invoke" || name == " invokeExact" )
178+
179+ // Note: if `owner` is an interface, in the first iteration we search for a matching member in the interface itself.
180+ // If that fails, the recursive invocation checks in the superclass (which is Object) with `publicInstanceOnly == true`.
181+ // This is specified in jvms-5.4.3.4: interface method resolution only returns public, non-static methods of Object.
182+ def findInSuperClasses (owner : ClassNode , publicInstanceOnly : Boolean = false ): Either [ClassNotFound , Option [(MethodNode , InternalName )]] = {
183+ findMethod(owner) match {
184+ case Some (m) if ! publicInstanceOnly || (isPublicMethod(m) && ! isStaticMethod(m)) => Right (Some ((m, owner.name)))
185+ case None =>
186+ if (isSignaturePolymorphic(owner.name)) Right (Some ((owner.methods.asScala.find(_.name == name).get, owner.name)))
187+ else if (owner.superName == null ) Right (None )
188+ else classNode(owner.superName).flatMap(findInSuperClasses(_, isInterface(owner)))
153189 }
154190 }
155191
156- // find the MethodNode in one of the parent classes
157- def findInParents (parents : List [InternalName ], failedClasses : List [ClassNotFound ]): Either [List [ClassNotFound ], (MethodNode , InternalName )] = parents match {
158- case x :: xs => methodNodeImpl(x).left.flatMap(failed => findInParents(xs, failed ::: failedClasses))
159- case Nil => Left (failedClasses)
192+ def findInInterfaces (initialOwner : ClassNode ): Either [ClassNotFound , Option [(MethodNode , InternalName )]] = {
193+ val visited = mutable.Set .empty[InternalName ]
194+ val found = mutable.ListBuffer .empty[(MethodNode , ClassNode )]
195+
196+ def findIn (owner : ClassNode ): Option [ClassNotFound ] = {
197+ for (i <- owner.interfaces.asScala if ! visited(i)) classNode(i) match {
198+ case Left (e) => return Some (e)
199+ case Right (c) =>
200+ visited += i
201+ // abstract and static methods are excluded, see jvms-5.4.3.3
202+ for (m <- findMethod(c) if ! isPrivateMethod(m) && ! isStaticMethod(m)) found += ((m, c))
203+ val recusionResult = findIn(c)
204+ if (recusionResult.isDefined) return recusionResult
205+ }
206+ None
207+ }
208+
209+ findIn(initialOwner)
210+
211+ val result =
212+ if (found.size <= 1 ) found.headOption
213+ else {
214+ val maxSpecific = found.filterNot({
215+ case (method, owner) =>
216+ isAbstractMethod(method) || {
217+ val ownerTp = classBTypeFromClassNode(owner)
218+ found exists {
219+ case (other, otherOwner) =>
220+ (other ne method) && {
221+ val otherTp = classBTypeFromClassNode(otherOwner)
222+ otherTp.isSubtypeOf(ownerTp).get
223+ }
224+ }
225+ }
226+ })
227+ // (*) note that if there's no single, non-abstract, maximally-specific method, the jvm
228+ // method resolution (jvms-5.4.3.3) returns any of the non-private, non-static parent
229+ // methods at random (abstract or concrete).
230+ // we chose not to do this here, to prevent the inliner from potentially inlining the
231+ // wrong method. in other words, we guarantee that a concrete method is only returned if
232+ // it resolves deterministically.
233+ // however, there may be multiple abstract methods inherited. in this case we *do* want
234+ // to return a result to allow performing accessibility checks in the inliner. note that
235+ // for accessibility it does not matter which of these methods is return, as they are all
236+ // non-private (i.e., public, protected is not possible, jvms-4.1).
237+ // the remaining case (when there's no max-specific method, but some non-abstract one)
238+ // does not occur in bytecode generated by scalac or javac. we return no result in this
239+ // case. this may at worst prevent some optimizations from happening.
240+ if (maxSpecific.size == 1 ) maxSpecific.headOption
241+ else if (found.forall(p => isAbstractMethod(p._1))) found.headOption // (*)
242+ else None
243+ }
244+ Right (result.map(p => (p._1, p._2.name)))
160245 }
161246
162247 // In a MethodInsnNode, the `owner` field may be an array descriptor, for example when invoking `clone`. We don't have a method node to return in this case.
163- if (ownerInternalNameOrArrayDescriptor.charAt(0 ) == '[' )
164- Left (MethodNotFound (name, descriptor, ownerInternalNameOrArrayDescriptor, Nil ))
165- else
166- methodNodeImpl(ownerInternalNameOrArrayDescriptor).left.map(MethodNotFound (name, descriptor, ownerInternalNameOrArrayDescriptor, _))
248+ if (ownerInternalNameOrArrayDescriptor.charAt(0 ) == '[' ) {
249+ Left (MethodNotFound (name, descriptor, ownerInternalNameOrArrayDescriptor, None ))
250+ } else {
251+ def notFound (cnf : Option [ClassNotFound ]) = Left (MethodNotFound (name, descriptor, ownerInternalNameOrArrayDescriptor, cnf))
252+ val res : Either [ClassNotFound , Option [(MethodNode , InternalName )]] = classNode(ownerInternalNameOrArrayDescriptor).flatMap(c =>
253+ findInSuperClasses(c) flatMap {
254+ case None => findInInterfaces(c)
255+ case res => Right (res)
256+ }
257+ )
258+ res match {
259+ case Left (e) => notFound(Some (e))
260+ case Right (None ) => notFound(None )
261+ case Right (Some (res)) => Right (res)
262+ }
263+ }
167264 }
168265
169266 private def parseClass (internalName : InternalName ): Either [ClassNotFound , ClassNode ] = {
0 commit comments