Skip to content

Commit

Permalink
Almost-fix for SI-3452.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
paulp committed Feb 20, 2013
1 parent 70956e5 commit edfd93e
Show file tree
Hide file tree
Showing 19 changed files with 553 additions and 12 deletions.
13 changes: 12 additions & 1 deletion src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1063,7 +1063,18 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM {
)

// TODO needed? for(ann <- m.annotations) { ann.symbol.initialize }
val jgensig = if (m.isDeferred) null else getGenericSignature(m, module); // only add generic signature if method concrete; bug #1745
val jgensig = (
// only add generic signature if method concrete; bug #1745
if (m.isDeferred) null else {
val clazz = module.linkedClassOfClass
val m1 = (
if ((clazz.info member m.name) eq NoSymbol)
enteringErasure(m.cloneSymbol(clazz, Flags.METHOD | Flags.STATIC))
else m
)
getGenericSignature(m1, clazz)
}
)
addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m)
val (throws, others) = m.annotations partition (_.symbol == ThrowsClass)
val thrownExceptions: List[String] = getExceptions(throws)
Expand Down
28 changes: 21 additions & 7 deletions src/compiler/scala/tools/nsc/transform/Mixin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
debuglog("new member of " + clazz + ":" + member.defString)
clazz.info.decls enter member setFlag MIXEDIN
}
/** Warning: subtle issues at play!
* The info of the cloned symbol used to be calculated as:
* (clazz.thisType baseType mixinClass) memberInfo mixinMember
* This is not correct! See test run/t3452b for an example
* of runtime failure.
*/
def cloneAndAddMember(mixinClass: Symbol, mixinMember: Symbol, clazz: Symbol): Symbol =
addMember(clazz, cloneBeforeErasure(mixinClass, mixinMember, clazz))

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

def needsExpandedSetterName(field: Symbol) = !field.isLazy && (
Expand Down Expand Up @@ -273,8 +281,14 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
val imember = member overriddenSymbol mixinInterface
imember overridingSymbol clazz match {
case NoSymbol =>
if (clazz.info.findMember(member.name, 0, lateDEFERRED, false).alternatives contains imember)
cloneAndAddMixinMember(mixinInterface, imember).asInstanceOf[TermSymbol] setAlias member
if (clazz.info.findMember(member.name, 0, lateDEFERRED, false).alternatives contains imember) {
val forwarder = cloneAndAddMixinMember(mixinInterface, imember).asInstanceOf[TermSymbol] setAlias member
log("Adding forwarder from %s to %s during mixin:\n before erasure: %s => %s\n after erasure: %s => %s".format(
clazz, member.owner,
enteringErasure(forwarder.defString), enteringErasure(member.defString),
forwarder.defString, member.defString)
)
}
case _ =>
}
}
Expand Down
8 changes: 4 additions & 4 deletions test/files/neg/t4749.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ package bippy {
class Fail6 {
def main = "bippy"
}
trait FailBippy[T] {
def main(args: Array[String]): T = null.asInstanceOf[T]
}
object Fail7 extends FailBippy[Unit] { }

object Win1 {
def main(args: Array[String]): Unit = ()
}
object Win2 extends Bippy[Unit] {
override def main(args: Array[String]): Unit = ()
}
trait WinBippy[T] {
def main(args: Array[String]): T = null.asInstanceOf[T]
}
object Win3 extends WinBippy[Unit] { }
}

59 changes: 59 additions & 0 deletions test/files/run/mixin-signatures.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
class Test$bar1$ {
public java.lang.String Test$bar1$.f(java.lang.Object)
public java.lang.Object Test$bar1$.f(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar1$.g(java.lang.String)
public java.lang.Object Test$bar1$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar1$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.Object Test$bar1$.h(java.lang.Object)
}

class Test$bar2$ {
public java.lang.Object Test$bar2$.f(java.lang.String)
public java.lang.Object Test$bar2$.f(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar2$.g(java.lang.String)
public java.lang.Object Test$bar2$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.Object Test$bar2$.g(java.lang.String) <bridge> <synthetic>
public java.lang.Object Test$bar2$.h(java.lang.Object)
}

class Test$bar3$ {
public java.lang.String Foo3.f(java.lang.Object)
generic: public java.lang.String Foo3.f(T)
public java.lang.Object Foo3.f(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar3$.g(java.lang.String)
public java.lang.Object Test$bar3$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar3$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.Object Foo3.h(java.lang.Object)
}

class Test$bar4$ {
public java.lang.Object Foo4.f(java.lang.String)
generic: public R Foo4.f(java.lang.String)
public java.lang.Object Foo4.f(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar4$.g(java.lang.String)
public java.lang.Object Test$bar4$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.Object Test$bar4$.g(java.lang.String) <bridge> <synthetic>
public java.lang.Object Foo4.h(java.lang.Object)
}

class Test$bar5$ {
public java.lang.String Test$bar5$.f(java.lang.String)
public java.lang.Object Test$bar5$.f(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar5$.f(java.lang.Object) <bridge> <synthetic>
public java.lang.Object Test$bar5$.f(java.lang.String) <bridge> <synthetic>
public java.lang.String Test$bar5$.g(java.lang.String)
public java.lang.Object Test$bar5$.g(java.lang.String) <bridge> <synthetic>
public java.lang.Object Test$bar5$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar5$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.Object Test$bar5$.h(java.lang.Object)
}

class Foo1$class {
public static java.lang.String Foo1$class.f(Foo1,java.lang.Object)
}

class Foo2$class {
public static java.lang.Object Foo2$class.f(Foo2,java.lang.String)
}

000000000000000000000000000000000000
105 changes: 105 additions & 0 deletions test/files/run/mixin-signatures.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
trait Base[T, R] {
def f(x: T): R
def g(x: T): R
def h(x: T): R = null.asInstanceOf[R]
}

trait Foo1[T] extends Base[T, String] {
def f(x: T): String = null
def g(x: T): String
}
trait Foo2[R] extends Base[String, R] {
def f(x: String): R = { print(x.length) ; null.asInstanceOf[R] }
def g(x: String): R
}
abstract class Foo3[T] extends Base[T, String] {
def f(x: T): String = ""
def g(x: T): String
}
abstract class Foo4[R] extends Base[String, R] {
def f(x: String): R = { print(x.length) ; null.asInstanceOf[R] }
def g(x: String): R
}

object Test {
object bar1 extends Foo1[String] { def g(x: String): String = { print(x.length) ; "" } }
object bar2 extends Foo2[String] { def g(x: String): String = { print(x.length) ; "" } }
object bar3 extends Foo3[String] { def g(x: String): String = { print(x.length) ; "" } }
object bar4 extends Foo4[String] { def g(x: String): String = { print(x.length) ; "" } }

// Notice that in bar5, f and g require THREE bridges, because the final
// implementation is (String)String, but:
//
// inherited abstract signatures: T(R), (T)String, and (String)R
// which erase to: (Object)Object, (Object)String, and (String)Object
//
// each of which must be bridged to the actual (String)String implementation.
//
// public java.lang.String Test$bar5$.g(java.lang.String)
// public java.lang.Object Test$bar5$.g(java.lang.String) <bridge> <synthetic>
// public java.lang.Object Test$bar5$.g(java.lang.Object) <bridge> <synthetic>
// public java.lang.String Test$bar5$.g(java.lang.Object) <bridge> <synthetic>
object bar5 extends Foo1[String] with Foo2[String] {
override def f(x: String): String = { print(x.length) ; x }
def g(x: String): String = { print(x.length) ; x }
}

final def m1[T, R](x: Base[T, R], y: T) = { x.f(y) ; x.g(y) ; x.h(y) }
final def m2[T](x: Base[T, String], y: T) = { x.f(y) ; x.g(y) ; x.h(y) }
final def m3[R](x: Base[String, R]) = { x.f("") ; x.g("") ; x.h("") }
final def m4(x: Base[String, String]) = { x.f("") ; x.g("") ; x.h("") }

final def m11[T](x: Foo1[T], y: T) = { x.f(y) ; x.g(y) ; x.h(y) }
final def m12(x: Foo1[String]) = { x.f("") ; x.g("") ; x.h("") }
final def m21[T](x: Foo2[T], y: T) = { x.f("") ; x.g("") ; x.h("") }
final def m22(x: Foo2[String]) = { x.f("") ; x.g("") ; x.h("") }
final def m31[T](x: Foo3[T], y: T) = { x.f(y) ; x.g(y) ; x.h(y) }
final def m32(x: Foo3[String]) = { x.f("") ; x.g("") ; x.h("") }
final def m41[T](x: Foo4[T], y: T) = { x.f("") ; x.g("") ; x.h("") }
final def m42(x: Foo4[String]) = { x.f("") ; x.g("") ; x.h("") }

def go = {
m1(bar1, "") ; m2(bar1, "") ; m3(bar1) ; m4(bar1)
m1(bar2, "") ; m2(bar2, "") ; m3(bar2) ; m4(bar2)
m1(bar3, "") ; m2(bar3, "") ; m3(bar3) ; m4(bar3)
m1(bar4, "") ; m2(bar4, "") ; m3(bar4) ; m4(bar4)

m11(bar1, "") ; m12(bar1)
m21(bar2, "") ; m22(bar2)
m31(bar3, "") ; m32(bar3)
m41(bar4, "") ; m42(bar4)
""
}

def flagsString(m: java.lang.reflect.Method) = {
val str = List(
if (m.isBridge) "<bridge>" else "",
if (m.isSynthetic) "<synthetic>" else ""
) filterNot (_ == "") mkString " "

if (str == "") "" else " " + str
//
// val flags = scala.reflect.internal.ClassfileConstants.toScalaMethodFlags(m.getModifiers())
// scala.tools.nsc.symtab.Flags.flagsToString(flags)
}

def show(clazz: Class[_]) {
print(clazz + " {")
clazz.getMethods.sortBy(x => (x.getName, x.isBridge)) filter (_.getName.length == 1) foreach { m =>
print("\n " + m + flagsString(m))
if ("" + m != "" + m.toGenericString) {
print("\n generic: " + m.toGenericString)
}
}
println("\n}")
println("")
}
def show(x: AnyRef) { show(x.getClass) }
def show(x: String) { show(Class.forName(x)) }

def main(args: Array[String]): Unit = {
List(bar1, bar2, bar3, bar4, bar5) foreach show
List("Foo1$class", "Foo2$class") foreach show
println(go)
}
}
1 change: 1 addition & 0 deletions test/files/run/t3452.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4
21 changes: 21 additions & 0 deletions test/files/run/t3452.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
trait IStringPair[T] {
def a : String
def b : String
def build(a : String, b : String) : T
def cat(that : IStringPair[T]) = build(this.a + that.a, this.b + that.b)
override def toString = a + b
}

class StringPair(val a : String, val b : String) extends IStringPair[StringPair] {
def build(a : String, b : String) = new StringPair(a, b)
def len = a.length + b.length
}

object Test {
def main(args: Array[String]): Unit = {
val a = new StringPair("A", "B")
val b = new StringPair("1", "2")
val c = a cat b
println(c.len)
}
}
1 change: 1 addition & 0 deletions test/files/run/t3452a.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BulkSearch.searchFor called.
5 changes: 5 additions & 0 deletions test/files/run/t3452a/J_2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public class J_2 {
public static void main(String[] args) {
BulkSearchInstance.searchFor(new UpRelation());
}
}
24 changes: 24 additions & 0 deletions test/files/run/t3452a/S_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
abstract class BulkSearch {
type R <: Row
type Rel <: Relation [R]
type Corr <: Correspondence[R]

def searchFor(input: Rel): Mapping[Corr] = { println("BulkSearch.searchFor called.") ; null }
}

object BulkSearchInstance extends BulkSearch {
type R = UpRow
type Rel = UpRelation
type Corr = UpCorrespondence
}

class Row
class UpRow extends Row

class Relation [R <: Row]
class UpRelation extends Relation [UpRow]

class Correspondence [R <: Row]
class UpCorrespondence extends Correspondence [UpRow]

class Mapping[MC <: Correspondence[_]]
5 changes: 5 additions & 0 deletions test/files/run/t3452a/S_3.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object Test {
def main(args: Array[String]): Unit = {
J_2.main(args)
}
}
1 change: 1 addition & 0 deletions test/files/run/t3452b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Search received: test
5 changes: 5 additions & 0 deletions test/files/run/t3452b/J_2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public class J_2 {
public static void j() {
StringSearch.search("test");
}
}
10 changes: 10 additions & 0 deletions test/files/run/t3452b/S_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
trait Search[M] {
def search(input: M): C[Int] = {
println("Search received: " + input)
null
}
}

object StringSearch extends Search[String]

trait C[T]
5 changes: 5 additions & 0 deletions test/files/run/t3452b/S_3.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object Test {
def main(args: Array[String]): Unit = {
J_2.j()
}
}
8 changes: 8 additions & 0 deletions test/files/run/t3452c.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
3
3
3
3
3
3
3
3
Loading

0 comments on commit edfd93e

Please sign in to comment.