Skip to content

Commit 985d43a

Browse files
committed
Add bridges for overridden methods in lambda indy call
If a SAM trait's abstract method overrides a method in a supertrait while changing the return type, the generated invokedynamic instruction needs to pass the types of the overridden methods to `LambdaMetaFactory` so that bridge methods can be added to the generated lambda. Java does this differently: it generates a default method in the subinterface overriding the superinterface method. Theoretically we could also generate the default bridges, but that is a binary-incompatible change, so I think it's safer to just add the bridges in the invokedynamic. Honestly I'm surprised that this hasn't come up already. Fixes scala/bug#10512.
1 parent 38628d1 commit 985d43a

File tree

4 files changed

+108
-8
lines changed

4 files changed

+108
-8
lines changed

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

+20-7
Original file line numberDiff line numberDiff line change
@@ -1349,23 +1349,36 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
13491349
val constrainedType = MethodBType(lambdaParams.map(p => typeToBType(p.tpe)), typeToBType(lambdaTarget.tpe.resultType)).toASMType
13501350
val samMethodType = methodBTypeFromSymbol(sam).toASMType
13511351
val markers = if (addScalaSerializableMarker) classBTypeFromSymbol(definitions.SerializableClass).toASMType :: Nil else Nil
1352-
visitInvokeDynamicInsnLMF(bc.jmethod, sam.name.toString, invokedType, samMethodType, implMethodHandle, constrainedType, isSerializable, markers)
1352+
val overriddenMethods = enteringErasure(sam.allOverriddenSymbols).map(o => methodBTypeFromSymbol(o).toASMType)
1353+
visitInvokeDynamicInsnLMF(bc.jmethod, sam.name.toString, invokedType, samMethodType, implMethodHandle, constrainedType, overriddenMethods, isSerializable, markers)
13531354
if (isSerializable)
13541355
addIndyLambdaImplMethod(cnode.name, implMethodHandle)
13551356
}
13561357
}
13571358

13581359
private def visitInvokeDynamicInsnLMF(jmethod: MethodNode, samName: String, invokedType: String, samMethodType: asm.Type,
1359-
implMethodHandle: asm.Handle, instantiatedMethodType: asm.Type,
1360-
serializable: Boolean, markerInterfaces: Seq[asm.Type]) = {
1360+
implMethodHandle: asm.Handle, instantiatedMethodType: asm.Type, overriddenMethodTypes: Seq[asm.Type],
1361+
serializable: Boolean, markerInterfaces: Seq[asm.Type]): Unit = {
13611362
import java.lang.invoke.LambdaMetafactory.{FLAG_BRIDGES, FLAG_MARKERS, FLAG_SERIALIZABLE}
13621363
// scala/bug#10334: make sure that a lambda object for `T => U` has a method `apply(T)U`, not only the `(Object)Object`
13631364
// version. Using the lambda a structural type `{def apply(t: T): U}` causes a reflective lookup for this method.
1364-
val needsBridge = samMethodType != instantiatedMethodType
1365-
val bridges = if (needsBridge) Seq(Int.box(1), instantiatedMethodType) else Nil
1365+
val needsGenericBridge = samMethodType != instantiatedMethodType
1366+
// scala/bug#10512: any methods which `samMethod` overrides need bridges made for them
1367+
// this is done automatically during erasure for classes we generate, but LMF needs to have them explicitly mentioned
1368+
// so we have to compute them at this relatively late point.
1369+
val bridges = (
1370+
if (needsGenericBridge)
1371+
instantiatedMethodType +: overriddenMethodTypes
1372+
else overriddenMethodTypes
1373+
).distinct.filterNot(_ == samMethodType)
1374+
1375+
/* We're saving on precious BSM arg slots by not passing 0 as the bridge count */
1376+
val bridgeArgs = if (bridges.nonEmpty) Int.box(bridges.length) +: bridges else Nil
1377+
13661378
def flagIf(b: Boolean, flag: Int): Int = if (b) flag else 0
1367-
val flags = FLAG_MARKERS | flagIf(serializable, FLAG_SERIALIZABLE) | flagIf(needsBridge, FLAG_BRIDGES)
1368-
val bsmArgs = Seq(samMethodType, implMethodHandle, instantiatedMethodType, Int.box(flags), Int.box(markerInterfaces.length)) ++ markerInterfaces ++ bridges
1379+
val flags = FLAG_MARKERS | flagIf(serializable, FLAG_SERIALIZABLE) | flagIf(bridges.nonEmpty, FLAG_BRIDGES)
1380+
val bsmArgs = Seq(samMethodType, implMethodHandle, instantiatedMethodType, Int.box(flags), Int.box(markerInterfaces.length)) ++ markerInterfaces ++ bridgeArgs
1381+
13691382
jmethod.visitInvokeDynamicInsn(samName, invokedType, lambdaMetaFactoryAltMetafactoryHandle, bsmArgs: _*)
13701383
}
13711384

src/reflect/scala/reflect/internal/Symbols.scala

+7-1
Original file line numberDiff line numberDiff line change
@@ -2385,7 +2385,13 @@ trait Symbols extends api.Symbols { self: SymbolTable =>
23852385
} else Nil
23862386
}
23872387

2388-
/** Equivalent to allOverriddenSymbols.nonEmpty, but more efficient. */
2388+
/** Equivalent to allOverriddenSymbols.nonEmpty, but more efficient.
2389+
*
2390+
* Drive-by warning: the result of `allOverriddenSymbols` can go from
2391+
* non-empty to empty following `erasure`; it seems that this is always
2392+
* initialized pre-erasure, so post-erasure this method is no longer
2393+
* equivalent to `allOverriddenSymbols.nonEmpty` like it claims.
2394+
*/
23892395
lazy val isOverridingSymbol = (
23902396
canMatchInheritedSymbols
23912397
&& owner.ancestors.exists(base => overriddenSymbol(base) != NoSymbol)

test/files/jvm/t10512a.scala

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
trait JsonValue
2+
class JsonObject extends JsonValue
3+
class JsonString extends JsonValue
4+
5+
trait JsonEncoder[A] {
6+
def encode(value: A): JsonValue
7+
}
8+
9+
trait JsonObjectEncoder[A] extends JsonEncoder[A] {
10+
def encode(value: A): JsonObject
11+
}
12+
13+
object JsonEncoderInstances {
14+
15+
implicit val stringEncoder: JsonEncoder[String] =
16+
s => new JsonString
17+
18+
implicit def listEncoder[A](implicit encoder: JsonEncoder[A]): JsonObjectEncoder[List[A]] =
19+
l => new JsonObject
20+
21+
}
22+
23+
object Test extends App {
24+
import JsonEncoderInstances._
25+
26+
implicitly[JsonEncoder[List[String]]].encode("" :: Nil)
27+
}

test/files/jvm/t10512b.scala

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
trait A
2+
trait B extends A
3+
trait C extends B
4+
object it extends C
5+
6+
/* try as many weird diamondy things as I can think of */
7+
trait SAM_A { def apply(): A }
8+
trait SAM_A1 extends SAM_A { def apply(): A }
9+
trait SAM_B extends SAM_A1 { def apply(): B }
10+
trait SAM_B1 extends SAM_A1 { def apply(): B }
11+
trait SAM_B2 extends SAM_B with SAM_B1
12+
trait SAM_C extends SAM_B2 { def apply(): C }
13+
14+
trait SAM_F extends (() => A) with SAM_C
15+
trait SAM_F1 extends (() => C) with SAM_F
16+
17+
18+
object Test extends App {
19+
20+
val s1: SAM_A = () => it
21+
val s2: SAM_A1 = () => it
22+
val s3: SAM_B = () => it
23+
val s4: SAM_B1 = () => it
24+
val s5: SAM_B2 = () => it
25+
val s6: SAM_C = () => it
26+
val s7: SAM_F = () => it
27+
val s8: SAM_F1 = () => it
28+
29+
(s1(): A)
30+
31+
(s2(): A)
32+
33+
(s3(): B)
34+
(s3(): A)
35+
36+
(s4(): B)
37+
(s4(): A)
38+
39+
(s5(): B)
40+
(s5(): A)
41+
42+
(s6(): C)
43+
(s6(): B)
44+
(s6(): A)
45+
46+
(s7(): C)
47+
(s7(): B)
48+
(s7(): A)
49+
50+
(s8(): C)
51+
(s8(): B)
52+
(s8(): A)
53+
54+
}

0 commit comments

Comments
 (0)