Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Force super classes to generate default bridges on lookup
- Fixes #2520 (make default bridge generation smarter)
- Avoids traversal order dependent behavior.

Since we potentially need to do a default target check on every method
lookup, we memoize calculated default targets.

Note that this might generate more default bridges than actually
necessary. However, since they will not be marked as reachable, they
will not even be synthesized.
  • Loading branch information
gzm0 committed Aug 6, 2023
commit 72282bc23c47eb18f265ca6e1bf30eec2a47b1c4
Original file line number Diff line number Diff line change
Expand Up @@ -667,50 +667,40 @@ final class Analyzer(config: CommonPhaseConfig, initial: Boolean,
assert(isScalaClass || isInterface,
s"Cannot call lookupMethod($methodName) on non Scala class $this")

@tailrec
def tryLookupInherited(ancestorInfo: ClassInfo): Option[MethodInfo] = {
ancestorInfo.publicMethodInfos.get(methodName) match {
case Some(m) if !m.isAbstract && (!m.nonExistent || ancestorInfo == this) =>
Some(m)
case _ =>
ancestorInfo.superClass match {
case Some(superClass) => tryLookupInherited(superClass)
case None => None
publicMethodInfos.get(methodName) match {
case Some(m) if !m.isAbstract => Some(m)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This keeps the previous behavior w.r.t. abstract methods, but it seems this is a problem:
If we try to call a method on a class that is abstract, won't we replace the abstract method info with the missing method info? (similar thing with default methods, but its unclear to me ATM whether that can happen).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems to be the case, yes. But it also doesn't seem to be an issue? Unless perhaps it adds more errors: trying to later call that method will report a missing declaration (because the abstract method is now missing). Is that what you're concerned about?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might add more errors, yes. My main concern here is about determinism: It would not surprise me if the exact errors here depend on the call graph traversal order. But in any case, this is not something related to this PR.


case _ =>
val candidate = superClass
.flatMap(_.tryLookupMethod(methodName))
.filterNot(_.nonExistent)

if (allowAddingSyntheticMethods) {
def maybeDefaultTarget = getDefaultTarget(methodName)

def needsDefaultOverride(method: MethodInfo): Boolean = {
/* The .get is OK, since we only get here if:
* - This class doesn't implement the method directly.
* - The superClass has found a default target.
* In this case, we always find at least one target.
*/
method.isDefaultBridge && method.defaultBridgeTarget != maybeDefaultTarget.get.owner.className
}
}
}
val existing =
if (isScalaClass) tryLookupInherited(this)
else publicMethodInfos.get(methodName).filter(!_.isAbstract)

if (!allowAddingSyntheticMethods) {
existing
} else if (existing.exists(m => !m.isDefaultBridge || m.owner == this)) {
/* If we found a non-bridge, it must be the right target.
* If we found a bridge directly in this class/interface, it must also
* be the right target.
*/
existing
} else {
// Try and find the target of a possible default bridge
findDefaultTarget(methodName).fold {
assert(existing.isEmpty)
existing
} { defaultTarget =>
if (existing.exists(_.defaultBridgeTarget == defaultTarget.owner.className)) {
/* If we found an existing bridge targeting the right method, we
* can reuse it.
* We also get here with None when there is no target whatsoever.
*/
existing
candidate
.filterNot(needsDefaultOverride(_))
.orElse(maybeDefaultTarget.map(createDefaultBridge(_)))
} else {
// Otherwise, create a new default bridge
Some(createDefaultBridge(defaultTarget))
candidate
}
}
}
}

private val defaultTargets = mutable.Map.empty[MethodName, Option[MethodInfo]]

private def getDefaultTarget(methodName: MethodName): Option[MethodInfo] =
defaultTargets.getOrElseUpdate(methodName, findDefaultTarget(methodName))

/** Resolves an inherited default method.
*
* This lookup is specified by the JVM resolution rules for default
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Scala.js (https://www.scala-js.org/)
*
* Copyright EPFL.
*
* Licensed under Apache License 2.0
* (https://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/

package org.scalajs.linker

import org.junit.Test
import org.junit.Assert._

import org.scalajs.ir.ClassKind
import org.scalajs.ir.Names._
import org.scalajs.ir.Trees._
import org.scalajs.ir.Types._

import org.scalajs.junit.async._

import org.scalajs.linker.interface.StandardConfig
import org.scalajs.linker.standard._

import org.scalajs.linker.testutils.TestIRBuilder._
import org.scalajs.linker.testutils.LinkingUtils._

class BaseLinkerTest {
import scala.concurrent.ExecutionContext.Implicits.global

@Test
def noUnnecessaryDefaultBridges(): AsyncResult = await {
val fooName = m("foo", Nil, IntRef)
val classDefs = Seq(
classDef(
"Intf",
kind = ClassKind.Interface,
methods = List(
MethodDef(EMF, fooName, NON, Nil, IntType, Some(int(1)))(EOH, UNV))
),
classDef(
"Base",
kind = ClassKind.Class,
superClass = Some(ObjectClass),
interfaces = List("Intf"),
methods = List(trivialCtor("Base"))
),
classDef(
"Sub",
kind = ClassKind.Class,
superClass = Some("Base"),
methods = List(trivialCtor("Sub"))
),
mainTestClassDef(
consoleLog(Apply(EAF, New("Sub", NoArgConstructorName, Nil), fooName, Nil)(IntType))
)
)

val config = StandardConfig().withOptimizer(false)

for (moduleSet <- linkToModuleSet(classDefs, MainTestModuleInitializers, config = config)) yield {
val clazz = findClass(moduleSet, "Sub").get
assertFalse(clazz.methods.exists(_.name.name == fooName))
}
}

private def findClass(moduleSet: ModuleSet, name: ClassName): Option[LinkedClass] =
moduleSet.modules.flatMap(_.classDefs).find(_.className == name)
}