Skip to content

Commit c007b7d

Browse files
committed
Almost-fix for SI-3452.
At this commit ant test-opt has two test failures: test/files/pos/javaReadsSigs [FAILED] test/files/run/t4238 [FAILED] Fix for wrong bytecode in forwarders. This took me so long to figure out I can't even tell you. Partly because there were two different bugs, one which only arose for trait forwarders and one for mirror class forwarders, and every time I'd make one set of tests work another set would start failing. The runtime failures associated with these bugs were fairly well hidden because you usually have to go through java to encounter them: scala doesn't pay that much attention to generic signatures, so they can be wrong and scala might still generate correct code. But java is not so lucky. Bug #1) During mixin composition, classes which extend traits receive forwarders to the implementations. An attempt was made to give these the correct info (in method "cloneBeforeErasure") but it was prone to giving the wrong answer, because: the key attribute which the forwarder must capture is what the underlying method will erase to *where the implementation is*, not how it appears to the class which contains it. That means the signature of the forwarder must be no more precise than the signature of the inherited implementation unless additional measures will be taken. This subtle difference will put on an unsubtle show for you in test run/t3452.scala. trait C[T] trait Search[M] { def search(input: M): C[Int] = null } object StringSearch extends Search[String] { } StringSearch.search("test"); // java // java.lang.NoSuchMethodError: StringSearch.search(Ljava/lang/String;)LC; Before/after this commit: < signature search (Ljava/lang/String;)LC<Ljava/lang/Object;>; --- > signature search (Ljava/lang/Object;)LC<Ljava/lang/Object;>; Bug #2) The same principle is at work, at a different location. During genjvm, objects without declared companion classes are given static forwarders in the corresponding class, e.g. object Foo { def bar = 5 } which creates these classes (taking minor liberties): class Foo$ { static val MODULE$ = new Foo$ ; def bar = 5 } class Foo { static def bar = Foo$.MODULE$.bar } In generating these, genjvm circumvented the usual process whereby one creates a symbol and gives it an info, preferring to target the bytecode directly. However generic signatures are calculated from symbol info (in this case reusing the info from the module class.) Lacking even the attempt which was being made in mixin to "clone before erasure", we would have runtime failures of this kind: abstract class Foo { type T def f(x: T): List[T] = List() } object Bar extends Foo { type T = String } Bar.f(""); // java // java.lang.NoSuchMethodError: Bar.f(Ljava/lang/String;)Lscala/collection/immutable/List; Before/after this commit: < signature f (Ljava/lang/String;)Lscala/collection/immutable/List<Ljava/lang/String;>; --- > signature f (Ljava/lang/Object;)Lscala/collection/immutable/List<Ljava/lang/Object;>; Closes SI-3452.
1 parent 1c6a0cf commit c007b7d

19 files changed

+553
-12
lines changed

src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala

+12-1
Original file line numberDiff line numberDiff line change
@@ -1060,7 +1060,18 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM {
10601060
)
10611061

10621062
// TODO needed? for(ann <- m.annotations) { ann.symbol.initialize }
1063-
val jgensig = if (m.isDeferred) null else getGenericSignature(m, module); // only add generic signature if method concrete; bug #1745
1063+
val jgensig = (
1064+
// only add generic signature if method concrete; bug #1745
1065+
if (m.isDeferred) null else {
1066+
val clazz = module.linkedClassOfClass
1067+
val m1 = (
1068+
if ((clazz.info member m.name) eq NoSymbol)
1069+
enteringErasure(m.cloneSymbol(clazz, Flags.METHOD | Flags.STATIC))
1070+
else m
1071+
)
1072+
getGenericSignature(m1, clazz)
1073+
}
1074+
)
10641075
addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m)
10651076
val (throws, others) = m.annotations partition (_.symbol == ThrowsClass)
10661077
val thrownExceptions: List[String] = getExceptions(throws)

src/compiler/scala/tools/nsc/transform/Mixin.scala

+21-7
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
161161
debuglog("new member of " + clazz + ":" + member.defString)
162162
clazz.info.decls enter member setFlag MIXEDIN
163163
}
164+
/** Warning: subtle issues at play!
165+
* The info of the cloned symbol used to be calculated as:
166+
* (clazz.thisType baseType mixinClass) memberInfo mixinMember
167+
* This is not correct! See test run/t3452b for an example
168+
* of runtime failure.
169+
*/
164170
def cloneAndAddMember(mixinClass: Symbol, mixinMember: Symbol, clazz: Symbol): Symbol =
165171
addMember(clazz, cloneBeforeErasure(mixinClass, mixinMember, clazz))
166172

@@ -172,17 +178,19 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
172178
// implementation class, as it's a clone that was made after erasure, and thus it does not
173179
// know its info at the beginning of erasure anymore.
174180
// Optimize: no need if mixinClass has no typeparams.
175-
mixinMember cloneSymbol clazz modifyInfo (info =>
176-
if (mixinClass.typeParams.isEmpty) info
177-
else (clazz.thisType baseType mixinClass) memberInfo mixinMember
178-
)
181+
val sym = mixinMember cloneSymbol clazz
182+
// Optimize: no need if mixinClass has no typeparams.
183+
if (mixinClass.typeParams.isEmpty) sym
184+
else sym modifyInfo (_.asSeenFrom(clazz.thisType, clazz))
179185
}
180186
// clone before erasure got rid of type info we'll need to generate a javaSig
181187
// now we'll have the type info at (the beginning of) erasure in our history,
182188
// and now newSym has the info that's been transformed to fit this period
183189
// (no need for asSeenFrom as phase.erasedTypes)
184190
// TODO: verify we need the updateInfo and document why
185-
newSym updateInfo (mixinMember.info cloneInfo newSym)
191+
// addMember(clazz, newSym)
192+
newSym
193+
// updateInfo (mixinMember.info cloneInfo newSym)
186194
}
187195

188196
/** Add getters and setters for all non-module fields of an implementation
@@ -268,8 +276,14 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
268276
val imember = member overriddenSymbol mixinInterface
269277
imember overridingSymbol clazz match {
270278
case NoSymbol =>
271-
if (clazz.info.findMember(member.name, 0, lateDEFERRED, stableOnly = false).alternatives contains imember)
272-
cloneAndAddMixinMember(mixinInterface, imember).asInstanceOf[TermSymbol] setAlias member
279+
if (clazz.info.findMember(member.name, 0, lateDEFERRED, stableOnly = false).alternatives contains imember) {
280+
val forwarder = cloneAndAddMixinMember(mixinInterface, imember).asInstanceOf[TermSymbol] setAlias member
281+
log("Adding forwarder from %s to %s during mixin:\n before erasure: %s => %s\n after erasure: %s => %s".format(
282+
clazz, member.owner,
283+
enteringErasure(forwarder.defString), enteringErasure(member.defString),
284+
forwarder.defString, member.defString)
285+
)
286+
}
273287
case _ =>
274288
}
275289
}

test/files/neg/t4749.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,16 @@ package bippy {
2929
class Fail6 {
3030
def main = "bippy"
3131
}
32+
trait FailBippy[T] {
33+
def main(args: Array[String]): T = null.asInstanceOf[T]
34+
}
35+
object Fail7 extends FailBippy[Unit] { }
3236

3337
object Win1 {
3438
def main(args: Array[String]): Unit = ()
3539
}
3640
object Win2 extends Bippy[Unit] {
3741
override def main(args: Array[String]): Unit = ()
3842
}
39-
trait WinBippy[T] {
40-
def main(args: Array[String]): T = null.asInstanceOf[T]
41-
}
42-
object Win3 extends WinBippy[Unit] { }
4343
}
4444

test/files/run/mixin-signatures.check

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
class Test$bar1$ {
2+
public java.lang.String Test$bar1$.f(java.lang.Object)
3+
public java.lang.Object Test$bar1$.f(java.lang.Object) <bridge> <synthetic>
4+
public java.lang.String Test$bar1$.g(java.lang.String)
5+
public java.lang.Object Test$bar1$.g(java.lang.Object) <bridge> <synthetic>
6+
public java.lang.String Test$bar1$.g(java.lang.Object) <bridge> <synthetic>
7+
public java.lang.Object Test$bar1$.h(java.lang.Object)
8+
}
9+
10+
class Test$bar2$ {
11+
public java.lang.Object Test$bar2$.f(java.lang.String)
12+
public java.lang.Object Test$bar2$.f(java.lang.Object) <bridge> <synthetic>
13+
public java.lang.String Test$bar2$.g(java.lang.String)
14+
public java.lang.Object Test$bar2$.g(java.lang.Object) <bridge> <synthetic>
15+
public java.lang.Object Test$bar2$.g(java.lang.String) <bridge> <synthetic>
16+
public java.lang.Object Test$bar2$.h(java.lang.Object)
17+
}
18+
19+
class Test$bar3$ {
20+
public java.lang.String Foo3.f(java.lang.Object)
21+
generic: public java.lang.String Foo3.f(T)
22+
public java.lang.Object Foo3.f(java.lang.Object) <bridge> <synthetic>
23+
public java.lang.String Test$bar3$.g(java.lang.String)
24+
public java.lang.Object Test$bar3$.g(java.lang.Object) <bridge> <synthetic>
25+
public java.lang.String Test$bar3$.g(java.lang.Object) <bridge> <synthetic>
26+
public java.lang.Object Foo3.h(java.lang.Object)
27+
}
28+
29+
class Test$bar4$ {
30+
public java.lang.Object Foo4.f(java.lang.String)
31+
generic: public R Foo4.f(java.lang.String)
32+
public java.lang.Object Foo4.f(java.lang.Object) <bridge> <synthetic>
33+
public java.lang.String Test$bar4$.g(java.lang.String)
34+
public java.lang.Object Test$bar4$.g(java.lang.Object) <bridge> <synthetic>
35+
public java.lang.Object Test$bar4$.g(java.lang.String) <bridge> <synthetic>
36+
public java.lang.Object Foo4.h(java.lang.Object)
37+
}
38+
39+
class Test$bar5$ {
40+
public java.lang.String Test$bar5$.f(java.lang.String)
41+
public java.lang.Object Test$bar5$.f(java.lang.Object) <bridge> <synthetic>
42+
public java.lang.String Test$bar5$.f(java.lang.Object) <bridge> <synthetic>
43+
public java.lang.Object Test$bar5$.f(java.lang.String) <bridge> <synthetic>
44+
public java.lang.String Test$bar5$.g(java.lang.String)
45+
public java.lang.Object Test$bar5$.g(java.lang.String) <bridge> <synthetic>
46+
public java.lang.Object Test$bar5$.g(java.lang.Object) <bridge> <synthetic>
47+
public java.lang.String Test$bar5$.g(java.lang.Object) <bridge> <synthetic>
48+
public java.lang.Object Test$bar5$.h(java.lang.Object)
49+
}
50+
51+
class Foo1$class {
52+
public static java.lang.String Foo1$class.f(Foo1,java.lang.Object)
53+
}
54+
55+
class Foo2$class {
56+
public static java.lang.Object Foo2$class.f(Foo2,java.lang.String)
57+
}
58+
59+
000000000000000000000000000000000000

test/files/run/mixin-signatures.scala

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
trait Base[T, R] {
2+
def f(x: T): R
3+
def g(x: T): R
4+
def h(x: T): R = null.asInstanceOf[R]
5+
}
6+
7+
trait Foo1[T] extends Base[T, String] {
8+
def f(x: T): String = null
9+
def g(x: T): String
10+
}
11+
trait Foo2[R] extends Base[String, R] {
12+
def f(x: String): R = { print(x.length) ; null.asInstanceOf[R] }
13+
def g(x: String): R
14+
}
15+
abstract class Foo3[T] extends Base[T, String] {
16+
def f(x: T): String = ""
17+
def g(x: T): String
18+
}
19+
abstract class Foo4[R] extends Base[String, R] {
20+
def f(x: String): R = { print(x.length) ; null.asInstanceOf[R] }
21+
def g(x: String): R
22+
}
23+
24+
object Test {
25+
object bar1 extends Foo1[String] { def g(x: String): String = { print(x.length) ; "" } }
26+
object bar2 extends Foo2[String] { def g(x: String): String = { print(x.length) ; "" } }
27+
object bar3 extends Foo3[String] { def g(x: String): String = { print(x.length) ; "" } }
28+
object bar4 extends Foo4[String] { def g(x: String): String = { print(x.length) ; "" } }
29+
30+
// Notice that in bar5, f and g require THREE bridges, because the final
31+
// implementation is (String)String, but:
32+
//
33+
// inherited abstract signatures: T(R), (T)String, and (String)R
34+
// which erase to: (Object)Object, (Object)String, and (String)Object
35+
//
36+
// each of which must be bridged to the actual (String)String implementation.
37+
//
38+
// public java.lang.String Test$bar5$.g(java.lang.String)
39+
// public java.lang.Object Test$bar5$.g(java.lang.String) <bridge> <synthetic>
40+
// public java.lang.Object Test$bar5$.g(java.lang.Object) <bridge> <synthetic>
41+
// public java.lang.String Test$bar5$.g(java.lang.Object) <bridge> <synthetic>
42+
object bar5 extends Foo1[String] with Foo2[String] {
43+
override def f(x: String): String = { print(x.length) ; x }
44+
def g(x: String): String = { print(x.length) ; x }
45+
}
46+
47+
final def m1[T, R](x: Base[T, R], y: T) = { x.f(y) ; x.g(y) ; x.h(y) }
48+
final def m2[T](x: Base[T, String], y: T) = { x.f(y) ; x.g(y) ; x.h(y) }
49+
final def m3[R](x: Base[String, R]) = { x.f("") ; x.g("") ; x.h("") }
50+
final def m4(x: Base[String, String]) = { x.f("") ; x.g("") ; x.h("") }
51+
52+
final def m11[T](x: Foo1[T], y: T) = { x.f(y) ; x.g(y) ; x.h(y) }
53+
final def m12(x: Foo1[String]) = { x.f("") ; x.g("") ; x.h("") }
54+
final def m21[T](x: Foo2[T], y: T) = { x.f("") ; x.g("") ; x.h("") }
55+
final def m22(x: Foo2[String]) = { x.f("") ; x.g("") ; x.h("") }
56+
final def m31[T](x: Foo3[T], y: T) = { x.f(y) ; x.g(y) ; x.h(y) }
57+
final def m32(x: Foo3[String]) = { x.f("") ; x.g("") ; x.h("") }
58+
final def m41[T](x: Foo4[T], y: T) = { x.f("") ; x.g("") ; x.h("") }
59+
final def m42(x: Foo4[String]) = { x.f("") ; x.g("") ; x.h("") }
60+
61+
def go = {
62+
m1(bar1, "") ; m2(bar1, "") ; m3(bar1) ; m4(bar1)
63+
m1(bar2, "") ; m2(bar2, "") ; m3(bar2) ; m4(bar2)
64+
m1(bar3, "") ; m2(bar3, "") ; m3(bar3) ; m4(bar3)
65+
m1(bar4, "") ; m2(bar4, "") ; m3(bar4) ; m4(bar4)
66+
67+
m11(bar1, "") ; m12(bar1)
68+
m21(bar2, "") ; m22(bar2)
69+
m31(bar3, "") ; m32(bar3)
70+
m41(bar4, "") ; m42(bar4)
71+
""
72+
}
73+
74+
def flagsString(m: java.lang.reflect.Method) = {
75+
val str = List(
76+
if (m.isBridge) "<bridge>" else "",
77+
if (m.isSynthetic) "<synthetic>" else ""
78+
) filterNot (_ == "") mkString " "
79+
80+
if (str == "") "" else " " + str
81+
//
82+
// val flags = scala.reflect.internal.ClassfileConstants.toScalaMethodFlags(m.getModifiers())
83+
// scala.tools.nsc.symtab.Flags.flagsToString(flags)
84+
}
85+
86+
def show(clazz: Class[_]) {
87+
print(clazz + " {")
88+
clazz.getMethods.sortBy(x => (x.getName, x.isBridge)) filter (_.getName.length == 1) foreach { m =>
89+
print("\n " + m + flagsString(m))
90+
if ("" + m != "" + m.toGenericString) {
91+
print("\n generic: " + m.toGenericString)
92+
}
93+
}
94+
println("\n}")
95+
println("")
96+
}
97+
def show(x: AnyRef) { show(x.getClass) }
98+
def show(x: String) { show(Class.forName(x)) }
99+
100+
def main(args: Array[String]): Unit = {
101+
List(bar1, bar2, bar3, bar4, bar5) foreach show
102+
List("Foo1$class", "Foo2$class") foreach show
103+
println(go)
104+
}
105+
}

test/files/run/t3452.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
4

test/files/run/t3452.scala

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
trait IStringPair[T] {
2+
def a : String
3+
def b : String
4+
def build(a : String, b : String) : T
5+
def cat(that : IStringPair[T]) = build(this.a + that.a, this.b + that.b)
6+
override def toString = a + b
7+
}
8+
9+
class StringPair(val a : String, val b : String) extends IStringPair[StringPair] {
10+
def build(a : String, b : String) = new StringPair(a, b)
11+
def len = a.length + b.length
12+
}
13+
14+
object Test {
15+
def main(args: Array[String]): Unit = {
16+
val a = new StringPair("A", "B")
17+
val b = new StringPair("1", "2")
18+
val c = a cat b
19+
println(c.len)
20+
}
21+
}

test/files/run/t3452a.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
BulkSearch.searchFor called.

test/files/run/t3452a/J_2.java

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public class J_2 {
2+
public static void main(String[] args) {
3+
BulkSearchInstance.searchFor(new UpRelation());
4+
}
5+
}

test/files/run/t3452a/S_1.scala

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
abstract class BulkSearch {
2+
type R <: Row
3+
type Rel <: Relation [R]
4+
type Corr <: Correspondence[R]
5+
6+
def searchFor(input: Rel): Mapping[Corr] = { println("BulkSearch.searchFor called.") ; null }
7+
}
8+
9+
object BulkSearchInstance extends BulkSearch {
10+
type R = UpRow
11+
type Rel = UpRelation
12+
type Corr = UpCorrespondence
13+
}
14+
15+
class Row
16+
class UpRow extends Row
17+
18+
class Relation [R <: Row]
19+
class UpRelation extends Relation [UpRow]
20+
21+
class Correspondence [R <: Row]
22+
class UpCorrespondence extends Correspondence [UpRow]
23+
24+
class Mapping[MC <: Correspondence[_]]

test/files/run/t3452a/S_3.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object Test {
2+
def main(args: Array[String]): Unit = {
3+
J_2.main(args)
4+
}
5+
}

test/files/run/t3452b.check

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Search received: test

test/files/run/t3452b/J_2.java

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public class J_2 {
2+
public static void j() {
3+
StringSearch.search("test");
4+
}
5+
}

test/files/run/t3452b/S_1.scala

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
trait Search[M] {
2+
def search(input: M): C[Int] = {
3+
println("Search received: " + input)
4+
null
5+
}
6+
}
7+
8+
object StringSearch extends Search[String]
9+
10+
trait C[T]

test/files/run/t3452b/S_3.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object Test {
2+
def main(args: Array[String]): Unit = {
3+
J_2.j()
4+
}
5+
}

test/files/run/t3452c.check

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
3
2+
3
3+
3
4+
3
5+
3
6+
3
7+
3
8+
3

0 commit comments

Comments
 (0)