Skip to content

Commit 1b0703e

Browse files
committed
[backport] 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$.
1 parent e511375 commit 1b0703e

File tree

2 files changed

+25
-1
lines changed

2 files changed

+25
-1
lines changed

src/compiler/scala/tools/nsc/backend/jvm/opt/BytecodeUtils.scala

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import scala.collection.mutable
1212
import scala.reflect.internal.util.Collections._
1313
import scala.tools.asm.commons.CodeSizeEvaluator
1414
import scala.tools.asm.tree.analysis._
15-
import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes}
15+
import scala.tools.asm.{MethodWriter, ClassWriter, Label, Opcodes, Type}
1616
import scala.tools.asm.tree._
1717
import GenBCode._
1818
import scala.collection.convert.decorateAsScala._
@@ -330,6 +330,26 @@ object BytecodeUtils {
330330
)).toList
331331
}
332332

333+
/**
334+
* This method is used by optimizer components to eliminate phantom values of instruction
335+
* that load a value of type `Nothing$` or `Null$`. Such values on the stack don't interact well
336+
* with stack map frames.
337+
*
338+
* For example, `opt.getOrElse(throw e)` is re-written to an invocation of the lambda body, a
339+
* method with return type `Nothing$`. Similarly for `opt.getOrElse(null)` and `Null$`.
340+
*
341+
* During bytecode generation this is handled by BCodeBodyBuilder.adapt. See the comment in that
342+
* method which explains the issue with such phantom values.
343+
*/
344+
def fixLoadedNothingOrNullValue(loadedType: Type, loadInstr: AbstractInsnNode, methodNode: MethodNode, bTypes: BTypes): Unit = {
345+
if (loadedType == bTypes.coreBTypes.RT_NOTHING.toASMType) {
346+
methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ATHROW))
347+
} else if (loadedType == bTypes.coreBTypes.RT_NULL.toASMType) {
348+
methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.ACONST_NULL))
349+
methodNode.instructions.insert(loadInstr, new InsnNode(Opcodes.POP))
350+
}
351+
}
352+
333353
/**
334354
* A wrapper to make ASM's Analyzer a bit easier to use.
335355
*/

src/compiler/scala/tools/nsc/backend/jvm/opt/ClosureOptimizer.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,10 @@ class ClosureOptimizer[BT <: BTypes](val btypes: BT) {
283283
val isInterface = bodyOpcode == INVOKEINTERFACE
284284
val bodyInvocation = new MethodInsnNode(bodyOpcode, lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc, isInterface)
285285
methodNode.instructions.insertBefore(invocation, bodyInvocation)
286+
287+
val returnType = Type.getReturnType(lambdaBodyHandle.getDesc)
288+
fixLoadedNothingOrNullValue(returnType, bodyInvocation, methodNode, btypes) // see comment of that method
289+
286290
methodNode.instructions.remove(invocation)
287291

288292
// update the call graph

0 commit comments

Comments
 (0)