Skip to content

Commit 7c24661

Browse files
authored
Merge pull request #541 from dwijnand/forward-bincompat
Catch forward-incompatible method overriding in traits
2 parents f6ef24b + f25269a commit 7c24661

File tree

12 files changed

+71
-11
lines changed

12 files changed

+71
-11
lines changed

core/src/main/scala/com/typesafe/tools/mima/core/ClassInfo.scala

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,22 +175,36 @@ private[mima] sealed abstract class ClassInfo(val owner: PackageInfo) extends In
175175
case _: SyntheticClassInfo => false
176176
case impl: ConcreteClassInfo =>
177177
assert(impl.isImplClass, impl)
178-
impl.methods.get(m.bytecodeName).exists(im => hasImplSig(im.descriptor, m.descriptor))
178+
impl.methods.get(m.bytecodeName).exists { im =>
179+
val isig = im.descriptor
180+
val tsig = m.descriptor
181+
assert(isig(0) == '(' && isig(1) == 'L' && tsig(0) == '(', s"isig=[$isig] tsig=[$tsig]")
182+
hasMatchingSig(isig, tsig)
183+
}
179184
}
180185
}
181186

182-
// Does `isig` correspond to `tsig` if seen as the signature of the static
183-
// implementation method of a trait method with signature `tsig`?
184-
private def hasImplSig(isig: String, tsig: String): Boolean = {
185-
assert(isig(0) == '(' && isig(1) == 'L' && tsig(0) == '(')
186-
val ilen = isig.length
187+
/** Does the given method have a static mixin forwarder? */
188+
final def hasMixinForwarder(m: MethodInfo): Boolean = {
189+
methods.get(m.bytecodeName + "$").exists { fm =>
190+
val fsig = fm.descriptor
191+
val tsig = m.descriptor
192+
assert(fsig(0) == '(' && tsig(0) == '(', s"fsig=[$fsig] tsig=[$tsig]")
193+
hasMatchingSig(fsig, tsig)
194+
}
195+
}
196+
197+
// Does `sig` correspond to `tsig` if seen as the signature of the static
198+
// implementation method or the mixin forwarder method of a trait method with signature `tsig`?
199+
private def hasMatchingSig(sig: String, tsig: String): Boolean = {
200+
val ilen = sig.length
187201
val tlen = tsig.length
188202
var i = 2
189-
while (isig(i) != ';')
203+
while (sig(i) != ';')
190204
i += 1
191205
i += 1
192206
var j = 1
193-
while (i < ilen && j < tlen && isig(i) == tsig(j)) {
207+
while (i < ilen && j < tlen && sig(i) == tsig(j)) {
194208
i += 1
195209
j += 1
196210
}

core/src/main/scala/com/typesafe/tools/mima/core/Problems.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ sealed abstract class Problem extends ProblemRef {
4545
case DirectAbstractMethodProblem(ref) => s"${ref.methodString} does not have a correspondent in $affectedVersion version"
4646
case ReversedAbstractMethodProblem(ref) => s"in $affectedVersion version there is ${ref.methodString}, which does not have a correspondent"
4747
case UpdateForwarderBodyProblem(ref) => s"in $affectedVersion version, classes mixing ${ref.owner.fullName} needs to update body of ${ref.shortMethodString}"
48+
case NewMixinForwarderProblem(ref) => s"in $affectedVersion version, classes mixing ${ref.owner.fullName} need be recompiled to wire to the new static mixin forwarder method all super calls to ${ref.shortMethodString}"
4849
case InheritedNewAbstractMethodProblem(absmeth, ref) => s"${absmeth.methodString} is inherited by class ${ref.owner.bytecodeName} in $affectedVersion version."
4950
}
5051
}
@@ -84,4 +85,5 @@ sealed abstract class AbstractMethodProblem(memb: MemberInfo)
8485
final case class DirectAbstractMethodProblem(newmeth: MethodInfo) extends AbstractMethodProblem(newmeth)
8586
final case class ReversedAbstractMethodProblem(newmeth: MethodInfo) extends MemberProblem(newmeth)
8687
final case class UpdateForwarderBodyProblem(oldmeth: MethodInfo) extends MemberProblem(oldmeth)
88+
final case class NewMixinForwarderProblem(oldmeth: MethodInfo) extends MemberProblem(oldmeth)
8789
final case class InheritedNewAbstractMethodProblem(absmeth: MethodInfo, newmeth: MethodInfo) extends AbstractMethodProblem(newmeth)

core/src/main/scala/com/typesafe/tools/mima/lib/analyze/method/MethodChecker.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ private[analyze] object MethodChecker {
2929
} else {
3030
if (oldmeth.owner.hasStaticImpl(oldmeth))
3131
checkStaticImplMethod(oldmeth, newclazz)
32+
else if (oldmeth.owner.hasMixinForwarder(oldmeth))
33+
checkStaticMixinForwarderMethod(oldmeth, newclazz)
3234
else
3335
checkExisting1Impl(oldmeth, newclazz, _.lookupMethods(oldmeth))
3436
}
@@ -79,6 +81,19 @@ private[analyze] object MethodChecker {
7981
}
8082
}
8183

84+
private def checkStaticMixinForwarderMethod(oldmeth: MethodInfo, newclazz: ClassInfo) = {
85+
if (newclazz.hasMixinForwarder(oldmeth)) {
86+
None // then it's ok, the method it is still there
87+
} else {
88+
if (newclazz.allTraits.exists(_.hasMixinForwarder(oldmeth))) {
89+
Some(NewMixinForwarderProblem(oldmeth))
90+
} else {
91+
val methsLookup = (_: ClassInfo).lookupConcreteTraitMethods(oldmeth)
92+
Some(missingOrIncompatible(oldmeth, methsLookup(newclazz).toList, methsLookup))
93+
}
94+
}
95+
}
96+
8297
private def missingOrIncompatible(oldmeth: MethodInfo, newmeths: List[MethodInfo], methsLookup: ClassInfo => Iterator[MethodInfo]) = {
8398
val newmethAndBridges = newmeths.filter(oldmeth.matchesType(_)) // _the_ corresponding new method + its bridges
8499
newmethAndBridges.find(_.tpe.resultType == oldmeth.tpe.resultType) match {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
in new version, classes mixing scala.collection.MapView need be recompiled to wire to the new static mixin forwarder method all super calls to method className()java.lang.String

functional-tests/src/main/scala/com/typesafe/tools/mima/lib/CollectProblemsTest.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ object CollectProblemsTest {
1313
() <- testCase.compileBoth
1414
problems = collectProblems(direction.lhs(testCase).jfile, direction.rhs(testCase).jfile)
1515
expected = readOracleFile(testCase.versionedFile(direction.oracleFile).jfile)
16-
() <- diffProblems(problems, expected)
16+
() <- direction match {
17+
case Backwards => diffProblems(problems, expected)
18+
case Forwards => diffProblems(problems, expected, "other")
19+
}
1720
} yield ()
1821

1922
val mimaLib: MiMaLib = new MiMaLib(cp = Nil)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object App {
2+
def main(args: Array[String]): Unit = {
3+
println(new B {}.n)
4+
}
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
in other version, classes mixing B need be recompiled to wire to the new static mixin forwarder method all super calls to method n()java.lang.String
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
trait A {
2+
def m = "a"
3+
def n = "a"
4+
}
5+
6+
trait B extends A {
7+
override def m = "b"
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
trait A {
2+
def m = "a"
3+
def n = "a"
4+
}
5+
6+
trait B extends A {
7+
override def m = "b"
8+
override def n = "b"
9+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
synthetic static method $init$(B)Unit in interface B does not have a correspondent in new version
2+
in new version, classes mixing B need be recompiled to wire to the new static mixin forwarder method all super calls to method foo()Int

0 commit comments

Comments
 (0)