Skip to content

Commit 2da5b03

Browse files
committed
SI-3452 Correct Java generic signatures for mixins, static forwarders
[Parts of this patch and some of the commentary are from @paulp] 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; The principled thing to do here would be to create a pair of methods in the host class: a mixin forwarder with the erased signature `(String)C[Int]`, and a bridge method with the same erased signature as the trait interface facet. But, this turns out to be pretty hard to retrofit onto the current setup of Mixin and Erasure, mostly due to the fact that mixin happens after erasure which has already taken care of bridging. For a future, release, we should try to move all bridging after mixin, and pursue this approach. But for now, what can we do about `LinkageError`s for Java clients? This commit simply checks if the pre-erasure method signature that we generate for the trait forward erases identically to that of the interface method. If so, we can be precise. If not, we emit the erased signature as the generic signature. 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;>; This takes the warning count for compiling collections under `-Ycheck:jvm` from 1521 to 26.
1 parent 3306967 commit 2da5b03

31 files changed

+717
-72
lines changed

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1064,7 +1064,18 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM {
10641064
)
10651065

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

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

Lines changed: 76 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -452,30 +452,18 @@ abstract class Erasure extends AddInterfaces
452452
val other = high
453453
val otpe = highErased
454454

455-
val bridgeNeeded = exitingErasure (
456-
!member.isMacro &&
457-
!(other.tpe =:= member.tpe) &&
458-
!(deconstMap(other.tpe) =:= deconstMap(member.tpe)) &&
459-
{ var e = bridgesScope.lookupEntry(member.name)
460-
while ((e ne null) && !((e.sym.tpe =:= otpe) && (bridgeTarget(e.sym) == member)))
461-
e = bridgesScope.lookupNextEntry(e)
462-
(e eq null)
463-
}
464-
)
455+
val bridgeNeeded = isBridgeNeeded(pair) && {
456+
var e = bridgesScope.lookupEntry(member.name)
457+
while ((e ne null) && !((e.sym.tpe =:= otpe) && (bridgeTarget(e.sym) == member)))
458+
e = bridgesScope.lookupNextEntry(e)
459+
(e eq null)
460+
}
461+
465462
if (!bridgeNeeded)
466463
return
467464

468-
val newFlags = (member.flags | BRIDGE | ARTIFACT) & ~(ACCESSOR | DEFERRED | LAZY | lateDEFERRED)
469-
val bridge = other.cloneSymbolImpl(root, newFlags) setPos root.pos
470-
471-
debuglog("generating bridge from %s (%s): %s to %s: %s".format(
472-
other, flagsToString(newFlags),
473-
otpe + other.locationString, member,
474-
specialErasure(root)(member.tpe) + member.locationString)
475-
)
465+
val bridge = makeBridgeSymbol(root, pair)
476466

477-
// the parameter symbols need to have the new owner
478-
bridge setInfo (otpe cloneInfo bridge)
479467
bridgeTarget(bridge) = member
480468

481469
if (!(member.tpe exists (_.typeSymbol.isDerivedValueClass)) ||
@@ -487,46 +475,79 @@ abstract class Erasure extends AddInterfaces
487475
}
488476

489477
bridgesScope enter bridge
490-
bridges ::= makeBridgeDefDef(bridge, member, other)
478+
bridges ::= makeBridgeDefDef(bridge, pair)
491479
}
492480
}
493481

494-
def makeBridgeDefDef(bridge: Symbol, member: Symbol, other: Symbol) = exitingErasure {
495-
// type checking ensures we can safely call `other`, but unless `member.tpe <:< other.tpe`,
496-
// calling `member` is not guaranteed to succeed in general, there's
497-
// nothing we can do about this, except for an unapply: when this subtype test fails,
498-
// return None without calling `member`
499-
//
500-
// TODO: should we do this for user-defined unapplies as well?
501-
// does the first argument list have exactly one argument -- for user-defined unapplies we can't be sure
502-
def maybeWrap(bridgingCall: Tree): Tree = {
503-
val guardExtractor = ( // can't statically know which member is going to be selected, so don't let this depend on member.isSynthetic
504-
(member.name == nme.unapply || member.name == nme.unapplySeq)
505-
&& !exitingErasure((member.tpe <:< other.tpe))) // no static guarantees (TODO: is the subtype test ever true?)
506-
507-
import CODE._
508-
val _false = FALSE
509-
val pt = member.tpe.resultType
510-
lazy val zero =
511-
if (_false.tpe <:< pt) _false
512-
else if (NoneModule.tpe <:< pt) REF(NoneModule)
513-
else EmptyTree
514-
515-
if (guardExtractor && (zero ne EmptyTree)) {
516-
val typeTest = gen.mkIsInstanceOf(REF(bridge.firstParam), member.tpe.params.head.tpe)
517-
IF (typeTest) THEN bridgingCall ELSE zero
518-
} else bridgingCall
519-
}
520-
val rhs = member.tpe match {
521-
case MethodType(Nil, ConstantType(c)) => Literal(c)
522-
case _ =>
523-
val sel: Tree = Select(This(root), member)
524-
val bridgingCall = (sel /: bridge.paramss)((fun, vparams) => Apply(fun, vparams map Ident))
482+
def makeBridgeDefDef(bridge: Symbol, pair: SymbolPair): Tree =
483+
Erasure.this.makeBridgeDefDef(root, bridge, pair)
484+
}
525485

526-
maybeWrap(bridgingCall)
527-
}
528-
DefDef(bridge, rhs)
486+
final def isBridgeNeeded(pair: SymbolPair): Boolean = {
487+
val member = pair.low
488+
val other = pair.high
489+
exitingErasure(
490+
!member.isMacro
491+
&& !(other.tpe =:= member.tpe)
492+
&& !(deconstMap(other.tpe) =:= deconstMap(member.tpe))
493+
)
494+
}
495+
496+
final def makeBridgeSymbol(root: Symbol, pair: SymbolPair): Symbol = {
497+
import pair._
498+
val member = low
499+
val other = high
500+
val otpe = highErased
501+
val newFlags = (member.flags | BRIDGE | ARTIFACT) & ~(ACCESSOR | DEFERRED | LAZY | lateDEFERRED)
502+
val bridge = pair.low.cloneSymbolImpl(root, newFlags) setPos root.pos
503+
504+
debuglog("generating bridge from %s (%s): %s to %s: %s".format(
505+
other, flagsToString(newFlags),
506+
otpe + other.locationString, member,
507+
specialErasure(root)(member.tpe) + member.locationString)
508+
)
509+
510+
// the parameter symbols need to have the new owner
511+
bridge setInfo (otpe cloneInfo bridge)
512+
}
513+
514+
final def makeBridgeDefDef(root: Symbol, bridge: Symbol, pair: SymbolPair): DefDef = exitingErasure {
515+
val member = pair.low
516+
val other = pair.high
517+
// type checking ensures we can safely call `other`, but unless `member.tpe <:< other.tpe`,
518+
// calling `member` is not guaranteed to succeed in general, there's
519+
// nothing we can do about this, except for an unapply: when this subtype test fails,
520+
// return None without calling `member`
521+
//
522+
// TODO: should we do this for user-defined unapplies as well?
523+
// does the first argument list have exactly one argument -- for user-defined unapplies we can't be sure
524+
def maybeWrap(bridgingCall: Tree): Tree = {
525+
val guardExtractor = ( // can't statically know which member is going to be selected, so don't let this depend on member.isSynthetic
526+
(member.name == nme.unapply || member.name == nme.unapplySeq)
527+
&& !exitingErasure((member.tpe <:< other.tpe))) // no static guarantees (TODO: is the subtype test ever true?)
528+
529+
import CODE._
530+
val _false = FALSE
531+
val pt = member.tpe.resultType
532+
lazy val zero =
533+
if (_false.tpe <:< pt) _false
534+
else if (NoneModule.tpe <:< pt) REF(NoneModule)
535+
else EmptyTree
536+
537+
if (guardExtractor && (zero ne EmptyTree)) {
538+
val typeTest = gen.mkIsInstanceOf(REF(bridge.firstParam), member.tpe.params.head.tpe)
539+
IF (typeTest) THEN bridgingCall ELSE zero
540+
} else bridgingCall
541+
}
542+
val rhs = member.tpe match {
543+
case MethodType(Nil, ConstantType(c)) => Literal(c)
544+
case _ =>
545+
val sel: Tree = Select(This(root), member)
546+
val bridgingCall = (sel /: bridge.paramss)((fun, vparams) => Apply(fun, vparams map Ident))
547+
548+
maybeWrap(bridgingCall)
529549
}
550+
DefDef(bridge, rhs)
530551
}
531552

532553
/** The modifier typer which retypes with erased types. */

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

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -172,18 +172,23 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
172172
// info) as they are seen from the class. We can't use the member that we get from the
173173
// implementation class, as it's a clone that was made after erasure, and thus it does not
174174
// know its info at the beginning of erasure anymore.
175-
// Optimize: no need if mixinClass has no typeparams.
176-
mixinMember cloneSymbol clazz modifyInfo (info =>
177-
if (mixinClass.typeParams.isEmpty) info
178-
else (clazz.thisType baseType mixinClass) memberInfo mixinMember
179-
)
175+
val sym = mixinMember cloneSymbol clazz
176+
177+
val erasureMap = erasure.erasure(mixinMember)
178+
val erasedInterfaceInfo: Type = erasureMap(mixinMember.info)
179+
val specificForwardInfo = (clazz.thisType baseType mixinClass) memberInfo mixinMember
180+
val forwarderInfo =
181+
if (erasureMap(specificForwardInfo) =:= erasedInterfaceInfo)
182+
specificForwardInfo
183+
else {
184+
erasedInterfaceInfo
185+
}
186+
// Optimize: no need if mixinClass has no typeparams.
187+
// !!! JZ Really? What about the effect of abstract types, prefix?
188+
if (mixinClass.typeParams.isEmpty) sym
189+
else sym modifyInfo (_ => forwarderInfo)
180190
}
181-
// clone before erasure got rid of type info we'll need to generate a javaSig
182-
// now we'll have the type info at (the beginning of) erasure in our history,
183-
// and now newSym has the info that's been transformed to fit this period
184-
// (no need for asSeenFrom as phase.erasedTypes)
185-
// TODO: verify we need the updateInfo and document why
186-
newSym updateInfo (mixinMember.info cloneInfo newSym)
191+
newSym
187192
}
188193

189194
/** Add getters and setters for all non-module fields of an implementation

test/files/neg/t4749.check

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ t4749.scala:26: warning: Fail6 has a main method with parameter type Array[Strin
2525

2626
object Fail6 {
2727
^
28+
t4749.scala:35: warning: Fail7 has a main method with parameter type Array[String], but bippy.Fail7 will not be a runnable program.
29+
Reason: main methods cannot refer to type parameters or abstract types.
30+
object Fail7 extends FailBippy[Unit] { }
31+
^
2832
error: No warnings can be incurred under -Xfatal-warnings.
29-
6 warnings found
33+
7 warnings found
3034
one error found

test/files/neg/t4749.scala

Lines changed: 4 additions & 4 deletions
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/pos/t3452f.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class Base[Coll] {
2+
trait Transformed[S] {
3+
lazy val underlying: Coll = ???
4+
}
5+
}
6+
7+
class Derived extends Base[String] {
8+
class C extends Transformed[Any]
9+
}
10+

test/files/run/mixin-signatures.check

Lines changed: 59 additions & 0 deletions
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.Object Test$bar5$.f(java.lang.String) <bridge> <synthetic>
43+
public java.lang.String Test$bar5$.f(java.lang.Object) <bridge> <synthetic>
44+
public java.lang.String Test$bar5$.g(java.lang.String)
45+
public java.lang.Object Test$bar5$.g(java.lang.Object) <bridge> <synthetic>
46+
public java.lang.Object Test$bar5$.g(java.lang.String) <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

0 commit comments

Comments
 (0)