diff options
author | Lukas Rytz <lukas.rytz@gmail.com> | 2015-07-02 20:17:57 +0200 |
---|---|---|
committer | Lukas Rytz <lukas.rytz@gmail.com> | 2015-07-03 10:42:52 +0200 |
commit | 055a373802a34ee09fc0ed20b2b25c3fa20507d4 (patch) | |
tree | a4ae2c969fcea60726796f552e5bbdffe36d191c /src | |
parent | 6ae2dd8dc4556e8085710122097c849fdeac6d95 (diff) | |
download | scala-055a373802a34ee09fc0ed20b2b25c3fa20507d4.tar.gz scala-055a373802a34ee09fc0ed20b2b25c3fa20507d4.tar.bz2 scala-055a373802a34ee09fc0ed20b2b25c3fa20507d4.zip |
SI-9376 don't crash when inlining a closure body that throws.
If the closure body method has return type Nothing$, add an `ATHROW`
instruction after the callsite. This is required for computing stack
map frames, as explained in a comment in BCodeBodyBuilder.adapt.
Similar for closure bodies with return type Null$.
Diffstat (limited to 'src')
3 files changed, 25 insertions, 2 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index c3f71969f6..063fb81d46 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -843,7 +843,6 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { * * New (http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1) * - Requires consistent stack map frames. GenBCode always generates stack frames. - * or higher. * - In practice: the ASM library computes stack map frames for us (ClassWriter). Emitting * correct frames after an ATHROW is probably complex, so ASM uses the following strategy: * - Every time when generating an ATHROW, a new basic block is started. diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala index 0ec550981a..cd36fd8bba 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala @@ -12,7 +12,7 @@ import scala.collection.mutable import scala.reflect.internal.util.Collections._ import scala.tools.asm.commons.CodeSizeEvaluator import scala.tools.asm.tree.analysis._ -import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes} +import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes, Type} import scala.tools.asm.tree._ import GenBCode._ import scala.collection.convert.decorateAsScala._ @@ -331,6 +331,26 @@ object BytecodeUtils { } /** + * This method is used by optimizer components to eliminate phantom values of instruction + * that load a value of type `Nothing$` or `Null$`. Such values on the stack don't interact well + * with stack map frames. + * + * For example, `opt.getOrElse(throw e)` is re-written to an invocation of the lambda body, a + * method with return type `Nothing$`. Similarly for `opt.getOrElse(null)` and `Null$`. + * + * During bytecode generation this is handled by BCodeBodyBuilder.adapt. See the comment in that + * method which explains the issue with such phantom values. + */ + def fixLoadedNothingOrNullValue(loadedType: Type, loadInstr: AbstractInsnNode, methodNode: MethodNode, bTypes: BTypes): Unit = { + if (loadedType == bTypes.coreBTypes.RT_NOTHING.toASMType) { + methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ATHROW)) + } else if (loadedType == bTypes.coreBTypes.RT_NULL.toASMType) { + methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ACONST_NULL)) + methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.POP)) + } + } + + /** * A wrapper to make ASM's Analyzer a bit easier to use. */ class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, interpreter: Interpreter[V] = new BasicInterpreter) { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala index 1648a53ed8..743a454678 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala @@ -283,6 +283,10 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) { val isInterface = bodyOpcode == INVOKEINTERFACE val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, isInterface) methodNode.instructions.insertBefore(invocation, bodyInvocation) + + val returnType = Type.getReturnType(lambdaBodyHandle.getDesc) + fixLoadedNothingOrNullValue(returnType, bodyInvocation, methodNode, btypes) // see comment of that method + methodNode.instructions.remove(invocation) // update the call graph |