@@ -15,22 +15,24 @@ import scala.collection.mutable
15
15
16
16
import org .scalajs .core .ir
17
17
import ir .{ClassKind , Definitions , Infos }
18
- import Definitions .{ isConstructorName , isReflProxyName }
18
+ import Definitions ._
19
19
20
20
import org .scalajs .core .tools .sem ._
21
21
import org .scalajs .core .tools .javascript .{LongImpl , OutputMode }
22
22
23
23
import ScalaJSOptimizer ._
24
24
25
25
final class Analyzer (semantics : Semantics , outputMode : OutputMode ,
26
- reachOptimizerSymbols : Boolean , initialLink : Boolean ) extends Analysis {
26
+ reachOptimizerSymbols : Boolean ,
27
+ allowAddingSyntheticMethods : Boolean ) extends Analysis {
27
28
import Analyzer ._
28
29
import Analysis ._
29
30
30
- @ deprecated(" Use the overload with an explicit `initialLink ` flag" , " 0.6.6" )
31
+ @ deprecated(" Use the overload with an explicit `allowAddingSyntheticMethods ` flag" , " 0.6.6" )
31
32
def this (semantics : Semantics , outputMode : OutputMode ,
32
33
reachOptimizerSymbols : Boolean ) = {
33
- this (semantics, outputMode, reachOptimizerSymbols, initialLink = true )
34
+ this (semantics, outputMode, reachOptimizerSymbols,
35
+ allowAddingSyntheticMethods = true )
34
36
}
35
37
36
38
@ deprecated(" Use the overload with an explicit output mode" , " 0.6.3" )
@@ -297,7 +299,7 @@ final class Analyzer(semantics: Semantics, outputMode: OutputMode,
297
299
* during the initial link. In a refiner, this must not happen anymore.
298
300
*/
299
301
methodInfos.get(ctorName).getOrElse {
300
- if (! initialLink ) {
302
+ if (! allowAddingSyntheticMethods ) {
301
303
createNonExistentMethod(ctorName)
302
304
} else {
303
305
val inherited = lookupMethod(ctorName)
@@ -350,6 +352,139 @@ final class Analyzer(semantics: Semantics, outputMode: OutputMode,
350
352
loop(this )
351
353
}
352
354
355
+ def tryLookupReflProxyMethod (proxyName : String ): Option [MethodInfo ] = {
356
+ if (! allowAddingSyntheticMethods) {
357
+ tryLookupMethod(proxyName)
358
+ } else {
359
+ /* The lookup for a target method in this code implements the
360
+ * algorithm defining `java.lang.Class.getMethod`. This mimics how
361
+ * reflective calls are implemented on the JVM, at link time.
362
+ *
363
+ * Caveat: protected methods are not ignored. This can only make an
364
+ * otherwise invalid reflective call suddenly able to call a protected
365
+ * method. It never breaks valid reflective calls. This could be fixed
366
+ * if the IR retained the information that a method is protected.
367
+ */
368
+
369
+ @ tailrec
370
+ def loop (ancestorInfo : ClassInfo ): Option [MethodInfo ] = {
371
+ if (ancestorInfo ne null ) {
372
+ ancestorInfo.methodInfos.get(proxyName) match {
373
+ case Some (m) =>
374
+ assert(m.isReflProxy && ! m.isAbstract)
375
+ Some (m)
376
+
377
+ case _ =>
378
+ ancestorInfo.findProxyMatch(proxyName) match {
379
+ case Some (target) =>
380
+ val targetName = target.encodedName
381
+ Some (ancestorInfo.createReflProxy(proxyName, targetName))
382
+
383
+ case None =>
384
+ loop(ancestorInfo.superClass)
385
+ }
386
+ }
387
+ } else {
388
+ None
389
+ }
390
+ }
391
+
392
+ loop(this )
393
+ }
394
+ }
395
+
396
+ private def findProxyMatch (proxyName : String ): Option [MethodInfo ] = {
397
+ val candidates = methodInfos.valuesIterator.filter { m =>
398
+ // TODO In theory we should filter out protected methods
399
+ ! m.isReflProxy && ! m.isExported && ! m.isAbstract &&
400
+ reflProxyMatches(m.encodedName, proxyName)
401
+ }.toSeq
402
+
403
+ /* From the JavaDoc of java.lang.Class.getMethod:
404
+ *
405
+ * If more than one [candidate] method is found in C, and one of these
406
+ * methods has a return type that is more specific than any of the
407
+ * others, that method is reflected; otherwise one of the methods is
408
+ * chosen arbitrarily.
409
+ */
410
+
411
+ val targets = candidates.filterNot { c =>
412
+ val resultType = methodResultType(c.encodedName)
413
+ candidates.exists { other =>
414
+ (other ne c) &&
415
+ isMoreSpecific(methodResultType(other.encodedName), resultType)
416
+ }
417
+ }
418
+
419
+ /* This last step (chosen arbitrarily) causes some soundness issues of
420
+ * the implementation of reflective calls. This is bug-compatible with
421
+ * Scala/JVM.
422
+ */
423
+ targets.headOption
424
+ }
425
+
426
+ private def reflProxyMatches (methodName : String , proxyName : String ): Boolean = {
427
+ val sepPos = methodName.lastIndexOf(" __" )
428
+ sepPos >= 0 && methodName.substring(0 , sepPos + 2 ) == proxyName
429
+ }
430
+
431
+ private def methodResultType (methodName : String ): ir.Types .ReferenceType = {
432
+ val typeName = methodName.substring(methodName.lastIndexOf(" __" ) + 2 )
433
+ val arrayDepth = typeName.indexWhere(_ != 'A' )
434
+ if (arrayDepth == 0 )
435
+ ir.Types .ClassType (typeName)
436
+ else
437
+ ir.Types .ArrayType (typeName.substring(arrayDepth), arrayDepth)
438
+ }
439
+
440
+ private def isMoreSpecific (left : ir.Types .ReferenceType ,
441
+ right : ir.Types .ReferenceType ): Boolean = {
442
+ import ir .Types ._
443
+
444
+ def classIsMoreSpecific (leftCls : String , rightCls : String ): Boolean = {
445
+ leftCls != rightCls && {
446
+ val leftInfo = _classInfos.get(leftCls)
447
+ val rightInfo = _classInfos.get(rightCls)
448
+ leftInfo.zip(rightInfo).exists { case (l, r) =>
449
+ l.ancestors.contains(r)
450
+ }
451
+ }
452
+ }
453
+
454
+ (left, right) match {
455
+ case (ClassType (leftCls), ClassType (rightCls)) =>
456
+ classIsMoreSpecific(leftCls, rightCls)
457
+ case (ArrayType (leftBase, leftDepth), ArrayType (rightBase, rightDepth)) =>
458
+ leftDepth == rightDepth && classIsMoreSpecific(leftBase, rightBase)
459
+ case (ArrayType (_, _), ClassType (ObjectClass )) =>
460
+ true
461
+ case _ =>
462
+ false
463
+ }
464
+ }
465
+
466
+ private def createReflProxy (proxyName : String ,
467
+ targetName : String ): MethodInfo = {
468
+ assert(this .isScalaClass,
469
+ s " Cannot create reflective proxy in non-Scala class $this" )
470
+
471
+ val returnsChar = targetName.endsWith(" __C" )
472
+ val syntheticInfo = Infos .MethodInfo (
473
+ encodedName = proxyName,
474
+ methodsCalled = Map (
475
+ this .encodedName -> List (targetName)),
476
+ methodsCalledStatically = (
477
+ if (returnsChar) Map (BoxedCharacterClass -> List (" init___C" ))
478
+ else Map .empty),
479
+ instantiatedClasses = (
480
+ if (returnsChar) List (BoxedCharacterClass )
481
+ else Nil ))
482
+ val m = new MethodInfo (this , syntheticInfo)
483
+ m.syntheticKind = MethodSyntheticKind .ReflectiveProxy (targetName)
484
+ methodInfos += proxyName -> m
485
+ m
486
+ }
487
+
353
488
def lookupStaticMethod (methodName : String ): MethodInfo = {
354
489
tryLookupStaticMethod(methodName).getOrElse {
355
490
val syntheticData = createMissingMethodInfo(methodName, isStatic = true )
@@ -473,7 +608,7 @@ final class Analyzer(semantics: Semantics, outputMode: OutputMode,
473
608
474
609
private def delayedCallMethod (methodName : String )(implicit from : From ): Unit = {
475
610
if (isReflProxyName(methodName)) {
476
- tryLookupMethod (methodName).foreach(_.reach(this ))
611
+ tryLookupReflProxyMethod (methodName).foreach(_.reach(this ))
477
612
} else {
478
613
lookupMethod(methodName).reach(this )
479
614
}
0 commit comments