Skip to content

Commit 554de3b

Browse files
paulpretronym
authored andcommitted
SI-3452 Correct Java generic signatures for mixins, static 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; This commit creates a bridge method to match the erased interface signature exactly. This forwards to a method with a more refined 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;>; Closes SI-3452. fail
1 parent 9514221 commit 554de3b

29 files changed

+729
-75
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
@@ -1061,7 +1061,18 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM {
10611061
)
10621062

10631063
// TODO needed? for(ann <- m.annotations) { ann.symbol.initialize }
1064-
val jgensig = if (m.isDeferred) null else getGenericSignature(m, module); // only add generic signature if method concrete; bug #1745
1064+
val jgensig = (
1065+
// only add generic signature if method concrete; bug #1745
1066+
if (m.isDeferred) null else {
1067+
val clazz = module.linkedClassOfClass
1068+
val m1 = (
1069+
if ((clazz.info member m.name) eq NoSymbol)
1070+
enteringErasure(m.cloneSymbol(clazz, Flags.METHOD | Flags.STATIC))
1071+
else m
1072+
)
1073+
getGenericSignature(m1, clazz)
1074+
}
1075+
)
10651076
addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m)
10661077
val (throws, others) = m.annotations partition (_.symbol == ThrowsClass)
10671078
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
@@ -444,30 +444,18 @@ abstract class Erasure extends AddInterfaces
444444
val other = high
445445
val otpe = highErased
446446

447-
val bridgeNeeded = exitingErasure (
448-
!member.isMacro &&
449-
!(other.tpe =:= member.tpe) &&
450-
!(deconstMap(other.tpe) =:= deconstMap(member.tpe)) &&
451-
{ var e = bridgesScope.lookupEntry(member.name)
452-
while ((e ne null) && !((e.sym.tpe =:= otpe) && (bridgeTarget(e.sym) == member)))
453-
e = bridgesScope.lookupNextEntry(e)
454-
(e eq null)
455-
}
456-
)
447+
val bridgeNeeded = isBridgeNeeded(pair) && {
448+
var e = bridgesScope.lookupEntry(member.name)
449+
while ((e ne null) && !((e.sym.tpe =:= otpe) && (bridgeTarget(e.sym) == member)))
450+
e = bridgesScope.lookupNextEntry(e)
451+
(e eq null)
452+
}
453+
457454
if (!bridgeNeeded)
458455
return
459456

460-
val newFlags = (member.flags | BRIDGE | ARTIFACT) & ~(ACCESSOR | DEFERRED | LAZY | lateDEFERRED)
461-
val bridge = other.cloneSymbolImpl(root, newFlags) setPos root.pos
457+
val bridge = makeBridgeSymbol(root, pair)
462458

463-
debuglog("generating bridge from %s (%s): %s to %s: %s".format(
464-
other, flagsToString(newFlags),
465-
otpe + other.locationString, member,
466-
specialErasure(root)(member.tpe) + member.locationString)
467-
)
468-
469-
// the parameter symbols need to have the new owner
470-
bridge setInfo (otpe cloneInfo bridge)
471459
bridgeTarget(bridge) = member
472460

473461
if (!(member.tpe exists (_.typeSymbol.isDerivedValueClass)) ||
@@ -479,46 +467,79 @@ abstract class Erasure extends AddInterfaces
479467
}
480468

481469
bridgesScope enter bridge
482-
bridges ::= makeBridgeDefDef(bridge, member, other)
470+
bridges ::= makeBridgeDefDef(bridge, pair)
483471
}
484472
}
485473

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

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

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

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

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -162,28 +162,43 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
162162
debuglog("new member of " + clazz + ":" + member.defString)
163163
clazz.info.decls enter member setFlag MIXEDIN
164164
}
165+
165166
def cloneAndAddMember(mixinClass: Symbol, mixinMember: Symbol, clazz: Symbol): Symbol =
166167
addMember(clazz, cloneBeforeErasure(mixinClass, mixinMember, clazz))
167168

169+
private val bridgeTo = perRunCaches.newMap[Symbol, SymbolPair]()
170+
168171
def cloneBeforeErasure(mixinClass: Symbol, mixinMember: Symbol, clazz: Symbol): Symbol = {
169172
val newSym = enteringErasure {
170173
// since we used `mixinMember` from the interface that represents the trait that's
171174
// being mixed in, have to instantiate the interface type params (that may occur in mixinMember's
172175
// info) as they are seen from the class. We can't use the member that we get from the
173176
// implementation class, as it's a clone that was made after erasure, and thus it does not
174177
// 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-
)
178+
val sym = mixinMember cloneSymbol clazz
179+
180+
def forwarderInfo(forwardee: Type): Type =
181+
(clazz.thisType baseType mixinClass) memberInfo mixinMember
182+
// Optimize: no need if mixinClass has no typeparams.
183+
val result =
184+
if (mixinClass.typeParams.isEmpty) sym
185+
else sym modifyInfo (info => forwarderInfo(info))
186+
187+
// Add a bridge, if needed.
188+
val root = sym.owner
189+
val pair = new SymbolPair(root, sym, mixinMember)
190+
if (erasure.isBridgeNeeded(pair)) {
191+
log("bridge needed for: " + pair)
192+
val bridgeSym = erasure.makeBridgeSymbol(root, pair)
193+
enteringMixin {
194+
addMember(clazz, bridgeSym)
195+
}
196+
bridgeTo(bridgeSym) = pair
197+
}
198+
199+
result
180200
}
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)
201+
newSym
187202
}
188203

189204
/** Add getters and setters for all non-module fields of an implementation
@@ -269,8 +284,14 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
269284
val imember = member overriddenSymbol mixinInterface
270285
imember overridingSymbol clazz match {
271286
case NoSymbol =>
272-
if (clazz.info.findMember(member.name, 0, lateDEFERRED, stableOnly = false).alternatives contains imember)
273-
cloneAndAddMixinMember(mixinInterface, imember).asInstanceOf[TermSymbol] setAlias member
287+
if (clazz.info.findMember(member.name, 0, lateDEFERRED, stableOnly = false).alternatives contains imember) {
288+
val forwarder = cloneAndAddMixinMember(mixinInterface, imember).asInstanceOf[TermSymbol] setAlias member
289+
log("Adding forwarder from %s to %s during mixin:\n before erasure: %s => %s\n after erasure: %s => %s".format(
290+
clazz, member.owner,
291+
enteringErasure(forwarder.defString), enteringErasure(member.defString),
292+
forwarder.defString, member.defString)
293+
)
294+
}
274295
case _ =>
275296
}
276297
}
@@ -1076,11 +1097,20 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL {
10761097
// add superaccessors
10771098
addDefDef(sym)
10781099
}
1100+
else if (sym.isBridge) {
1101+
val bridge = sym
1102+
val root = sym.owner
1103+
val pair = bridgeTo(bridge)
1104+
val bridgeDefDef = erasure.makeBridgeDefDef(root, bridge, pair)
1105+
addDefDef(bridge, localTyper.typed(bridgeDefDef.rhs, bridge.tpe.finalResultType)) // TODO run erasure tree transformer over this?
1106+
}
10791107
else {
10801108
// add forwarders
10811109
assert(sym.alias != NoSymbol, sym)
10821110
// debuglog("New forwarder: " + sym.defString + " => " + sym.alias.defString)
1083-
if (!sym.isMacro) addDefDef(sym, Apply(staticRef(sym.alias), gen.mkAttributedThis(clazz) :: sym.paramss.head.map(Ident)))
1111+
if (!sym.isMacro) {
1112+
addDefDef(sym, Apply(staticRef(sym.alias), gen.mkAttributedThis(clazz) :: sym.paramss.head.map(Ident)))
1113+
}
10841114
}
10851115
}
10861116
}

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

0 commit comments

Comments
 (0)