summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala')
-rw-r--r--src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala514
1 files changed, 514 insertions, 0 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
new file mode 100644
index 0000000000..90da570f01
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala
@@ -0,0 +1,514 @@
+package scala.tools.nsc
+package backend.jvm
+package analysis
+
+import java.lang.invoke.LambdaMetafactory
+
+import scala.annotation.switch
+import scala.collection.JavaConverters._
+import scala.collection.mutable
+import scala.tools.asm.Opcodes._
+import scala.tools.asm.tree._
+import scala.tools.asm.tree.analysis._
+import scala.tools.asm.{Handle, Type}
+import scala.tools.nsc.backend.jvm.BTypes._
+import scala.tools.nsc.backend.jvm.GenBCode._
+import scala.tools.nsc.backend.jvm.opt.BytecodeUtils._
+
+/**
+ * This component hosts tools and utilities used in the backend that require access to a `BTypes`
+ * instance.
+ *
+ * One example is the AsmAnalyzer class, which runs `computeMaxLocalsMaxStack` on the methodNode to
+ * be analyzed. This method in turn lives inside the BTypes assembly because it queries the per-run
+ * cache `maxLocalsMaxStackComputed` defined in there.
+ */
+class BackendUtils[BT <: BTypes](val btypes: BT) {
+ import btypes._
+ import btypes.coreBTypes._
+ import callGraph.ClosureInstantiation
+
+ /**
+ * A wrapper to make ASM's Analyzer a bit easier to use.
+ */
+ class AsmAnalyzer[V <: Value](methodNode: MethodNode, classInternalName: InternalName, val analyzer: Analyzer[V] = new Analyzer(new BasicInterpreter)) {
+ computeMaxLocalsMaxStack(methodNode)
+ try {
+ analyzer.analyze(classInternalName, methodNode)
+ } catch {
+ case ae: AnalyzerException =>
+ throw new AnalyzerException(null, "While processing " + classInternalName + "." + methodNode.name, ae)
+ }
+ def frameAt(instruction: AbstractInsnNode): Frame[V] = analyzer.frameAt(instruction, methodNode)
+ }
+
+ /**
+ * See the doc comment on package object `analysis` for a discussion on performance.
+ */
+ object AsmAnalyzer {
+ // jvm limit is 65535 for both number of instructions and number of locals
+
+ private def size(method: MethodNode) = method.instructions.size.toLong * method.maxLocals * method.maxLocals
+
+ // with the limits below, analysis should not take more than one second
+
+ private val nullnessSizeLimit = 5000l * 600l * 600l // 5000 insns, 600 locals
+ private val basicValueSizeLimit = 9000l * 1000l * 1000l
+ private val sourceValueSizeLimit = 8000l * 950l * 950l
+
+ def sizeOKForAliasing(method: MethodNode): Boolean = size(method) < nullnessSizeLimit
+ def sizeOKForNullness(method: MethodNode): Boolean = size(method) < nullnessSizeLimit
+ def sizeOKForBasicValue(method: MethodNode): Boolean = size(method) < basicValueSizeLimit
+ def sizeOKForSourceValue(method: MethodNode): Boolean = size(method) < sourceValueSizeLimit
+ }
+
+ class ProdConsAnalyzer(val methodNode: MethodNode, classInternalName: InternalName) extends AsmAnalyzer(methodNode, classInternalName, new Analyzer(new InitialProducerSourceInterpreter)) with ProdConsAnalyzerImpl
+
+ class NonLubbingTypeFlowAnalyzer(val methodNode: MethodNode, classInternalName: InternalName) extends AsmAnalyzer(methodNode, classInternalName, new Analyzer(new NonLubbingTypeFlowInterpreter))
+
+ /**
+ * Add:
+ * private static Object $deserializeLambda$(SerializedLambda l) {
+ * return indy[scala.runtime.LambdaDeserialize.bootstrap](l)
+ * }
+ *
+ * We use invokedynamic here to enable caching within the deserializer without needing to
+ * host a static field in the enclosing class. This allows us to add this method to interfaces
+ * that define lambdas in default methods.
+ */
+ def addLambdaDeserialize(classNode: ClassNode, implMethods: Iterable[Handle]): Unit = {
+ val cw = classNode
+
+ // Make sure to reference the ClassBTypes of all types that are used in the code generated
+ // here (e.g. java/util/Map) are initialized. Initializing a ClassBType adds it to the
+ // `classBTypeFromInternalName` map. When writing the classfile, the asm ClassWriter computes
+ // stack map frames and invokes the `getCommonSuperClass` method. This method expects all
+ // ClassBTypes mentioned in the source code to exist in the map.
+
+ val nilLookupDesc = MethodBType(Nil, jliMethodHandlesLookupRef).descriptor
+ val serlamObjDesc = MethodBType(jliSerializedLambdaRef :: Nil, ObjectRef).descriptor
+
+ {
+ val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", serlamObjDesc, null, null)
+ mv.visitCode()
+ mv.visitVarInsn(ALOAD, 0)
+ mv.visitInvokeDynamicInsn("lambdaDeserialize", serlamObjDesc, lambdaDeserializeBootstrapHandle, implMethods.toArray: _*)
+ mv.visitInsn(ARETURN)
+ mv.visitEnd()
+ }
+ }
+
+ /**
+ * Clone the instructions in `methodNode` into a new [[InsnList]], mapping labels according to
+ * the `labelMap`. Returns the new instruction list and a map from old to new instructions, and
+ * a list of lambda implementation methods references by invokedynamic[LambdaMetafactory] for a
+ * serializable SAM types.
+ */
+ def cloneInstructions(methodNode: MethodNode, labelMap: Map[LabelNode, LabelNode], keepLineNumbers: Boolean): (InsnList, Map[AbstractInsnNode, AbstractInsnNode], List[Handle]) = {
+ val javaLabelMap = labelMap.asJava
+ val result = new InsnList
+ var map = Map.empty[AbstractInsnNode, AbstractInsnNode]
+ var inlinedTargetHandles = mutable.ListBuffer[Handle]()
+ for (ins <- methodNode.instructions.iterator.asScala) {
+ ins match {
+ case callGraph.LambdaMetaFactoryCall(indy, _, _, _) => indy.bsmArgs match {
+ case Array(_, targetHandle: Handle, _, flags: Integer, xs@_*) if (flags.intValue & LambdaMetafactory.FLAG_SERIALIZABLE) != 0 =>
+ inlinedTargetHandles += targetHandle
+ case _ =>
+ }
+ case _ =>
+ }
+ if (keepLineNumbers || !ins.isInstanceOf[LineNumberNode]) {
+ val cloned = ins.clone(javaLabelMap)
+ result add cloned
+ map += ((ins, cloned))
+ }
+ }
+ (result, map, inlinedTargetHandles.toList)
+ }
+
+ def getBoxedUnit: FieldInsnNode = new FieldInsnNode(GETSTATIC, srBoxedUnitRef.internalName, "UNIT", srBoxedUnitRef.descriptor)
+
+ private val anonfunAdaptedName = """.*\$anonfun\$.*\$\d+\$adapted""".r
+ def hasAdaptedImplMethod(closureInit: ClosureInstantiation): Boolean = {
+ anonfunAdaptedName.pattern.matcher(closureInit.lambdaMetaFactoryCall.implMethod.getName).matches
+ }
+
+ private def primitiveAsmTypeToBType(primitiveType: Type): PrimitiveBType = (primitiveType.getSort: @switch) match {
+ case Type.BOOLEAN => BOOL
+ case Type.BYTE => BYTE
+ case Type.CHAR => CHAR
+ case Type.SHORT => SHORT
+ case Type.INT => INT
+ case Type.LONG => LONG
+ case Type.FLOAT => FLOAT
+ case Type.DOUBLE => DOUBLE
+ case _ => null
+ }
+
+ def isScalaBox(insn: MethodInsnNode): Boolean = {
+ insn.owner == srBoxesRunTimeRef.internalName && {
+ val args = Type.getArgumentTypes(insn.desc)
+ args.length == 1 && (srBoxesRuntimeBoxToMethods.get(primitiveAsmTypeToBType(args(0))) match {
+ case Some(MethodNameAndType(name, tp)) => name == insn.name && tp.descriptor == insn.desc
+ case _ => false
+ })
+ }
+ }
+
+ def getScalaBox(primitiveType: Type): MethodInsnNode = {
+ val bType = primitiveAsmTypeToBType(primitiveType)
+ val MethodNameAndType(name, methodBType) = srBoxesRuntimeBoxToMethods(bType)
+ new MethodInsnNode(INVOKESTATIC, srBoxesRunTimeRef.internalName, name, methodBType.descriptor, /*itf =*/ false)
+ }
+
+ def isScalaUnbox(insn: MethodInsnNode): Boolean = {
+ insn.owner == srBoxesRunTimeRef.internalName && (srBoxesRuntimeUnboxToMethods.get(primitiveAsmTypeToBType(Type.getReturnType(insn.desc))) match {
+ case Some(MethodNameAndType(name, tp)) => name == insn.name && tp.descriptor == insn.desc
+ case _ => false
+ })
+ }
+
+ def getScalaUnbox(primitiveType: Type): MethodInsnNode = {
+ val bType = primitiveAsmTypeToBType(primitiveType)
+ val MethodNameAndType(name, methodBType) = srBoxesRuntimeUnboxToMethods(bType)
+ new MethodInsnNode(INVOKESTATIC, srBoxesRunTimeRef.internalName, name, methodBType.descriptor, /*itf =*/ false)
+ }
+
+ private def calleeInMap(insn: MethodInsnNode, map: Map[InternalName, MethodNameAndType]): Boolean = map.get(insn.owner) match {
+ case Some(MethodNameAndType(name, tp)) => insn.name == name && insn.desc == tp.descriptor
+ case _ => false
+ }
+
+ def isJavaBox(insn: MethodInsnNode): Boolean = calleeInMap(insn, javaBoxMethods)
+ def isJavaUnbox(insn: MethodInsnNode): Boolean = calleeInMap(insn, javaUnboxMethods)
+
+ def isPredefAutoBox(insn: MethodInsnNode): Boolean = {
+ insn.owner == PredefRef.internalName && (predefAutoBoxMethods.get(insn.name) match {
+ case Some(tp) => insn.desc == tp.descriptor
+ case _ => false
+ })
+ }
+
+ def isPredefAutoUnbox(insn: MethodInsnNode): Boolean = {
+ insn.owner == PredefRef.internalName && (predefAutoUnboxMethods.get(insn.name) match {
+ case Some(tp) => insn.desc == tp.descriptor
+ case _ => false
+ })
+ }
+
+ def isRefCreate(insn: MethodInsnNode): Boolean = calleeInMap(insn, srRefCreateMethods)
+ def isRefZero(insn: MethodInsnNode): Boolean = calleeInMap(insn, srRefZeroMethods)
+
+ def runtimeRefClassBoxedType(refClass: InternalName): Type = Type.getArgumentTypes(srRefCreateMethods(refClass).methodType.descriptor)(0)
+
+ def isSideEffectFreeCall(insn: MethodInsnNode): Boolean = {
+ isScalaBox(insn) || isScalaUnbox(insn) ||
+ isJavaBox(insn) || // not java unbox, it may NPE
+ isSideEffectFreeConstructorCall(insn)
+ }
+
+ def isNonNullMethodInvocation(mi: MethodInsnNode): Boolean = {
+ isJavaBox(mi) || isScalaBox(mi) || isPredefAutoBox(mi) || isRefCreate(mi) || isRefZero(mi)
+ }
+
+ def isModuleLoad(insn: AbstractInsnNode, moduleName: InternalName): Boolean = insn match {
+ case fi: FieldInsnNode => fi.getOpcode == GETSTATIC && fi.owner == moduleName && fi.name == "MODULE$" && fi.desc == ("L" + moduleName + ";")
+ case _ => false
+ }
+
+ def isPredefLoad(insn: AbstractInsnNode) = isModuleLoad(insn, PredefRef.internalName)
+
+ def isPrimitiveBoxConstructor(insn: MethodInsnNode): Boolean = calleeInMap(insn, primitiveBoxConstructors)
+ def isRuntimeRefConstructor(insn: MethodInsnNode): Boolean = calleeInMap(insn, srRefConstructors)
+ def isTupleConstructor(insn: MethodInsnNode): Boolean = calleeInMap(insn, tupleClassConstructors)
+
+ // unused objects created by these constructors are eliminated by pushPop
+ private lazy val sideEffectFreeConstructors: Set[(String, String)] = {
+ val ownerDesc = (p: (InternalName, MethodNameAndType)) => (p._1, p._2.methodType.descriptor)
+ primitiveBoxConstructors.map(ownerDesc).toSet ++
+ srRefConstructors.map(ownerDesc) ++
+ tupleClassConstructors.map(ownerDesc) ++ Set(
+ (ObjectRef.internalName, MethodBType(Nil, UNIT).descriptor),
+ (StringRef.internalName, MethodBType(Nil, UNIT).descriptor),
+ (StringRef.internalName, MethodBType(List(StringRef), UNIT).descriptor),
+ (StringRef.internalName, MethodBType(List(ArrayBType(CHAR)), UNIT).descriptor))
+ }
+
+ def isSideEffectFreeConstructorCall(insn: MethodInsnNode): Boolean = {
+ insn.name == INSTANCE_CONSTRUCTOR_NAME && sideEffectFreeConstructors((insn.owner, insn.desc))
+ }
+
+ private lazy val classesOfSideEffectFreeConstructors = sideEffectFreeConstructors.map(_._1)
+
+ def isNewForSideEffectFreeConstructor(insn: AbstractInsnNode) = {
+ insn.getOpcode == NEW && {
+ val ti = insn.asInstanceOf[TypeInsnNode]
+ classesOfSideEffectFreeConstructors.contains(ti.desc)
+ }
+ }
+
+ def isBoxedUnit(insn: AbstractInsnNode) = {
+ insn.getOpcode == GETSTATIC && {
+ val fi = insn.asInstanceOf[FieldInsnNode]
+ fi.owner == srBoxedUnitRef.internalName && fi.name == "UNIT" && fi.desc == srBoxedUnitRef.descriptor
+ }
+ }
+
+ /**
+ * Visit the class node and collect all referenced nested classes.
+ */
+ def collectNestedClasses(classNode: ClassNode): List[ClassBType] = {
+ val innerClasses = mutable.Set.empty[ClassBType]
+
+ def visitInternalName(internalName: InternalName): Unit = if (internalName != null) {
+ val t = classBTypeFromParsedClassfile(internalName)
+ if (t.isNestedClass.get) innerClasses += t
+ }
+
+ // either an internal/Name or [[Linternal/Name; -- there are certain references in classfiles
+ // that are either an internal name (without the surrounding `L;`) or an array descriptor
+ // `[Linternal/Name;`.
+ def visitInternalNameOrArrayReference(ref: String): Unit = if (ref != null) {
+ val bracket = ref.lastIndexOf('[')
+ if (bracket == -1) visitInternalName(ref)
+ else if (ref.charAt(bracket + 1) == 'L') visitInternalName(ref.substring(bracket + 2, ref.length - 1))
+ }
+
+ // we are only interested in the class references in the descriptor, so we can skip over
+ // primitives and the brackets of array descriptors
+ def visitDescriptor(desc: String): Unit = (desc.charAt(0): @switch) match {
+ case '(' =>
+ val internalNames = mutable.ListBuffer.empty[String]
+ var i = 1
+ while (i < desc.length) {
+ if (desc.charAt(i) == 'L') {
+ val start = i + 1 // skip the L
+ while (desc.charAt(i) != ';') i += 1
+ internalNames append desc.substring(start, i)
+ }
+ // skips over '[', ')', primitives
+ i += 1
+ }
+ internalNames foreach visitInternalName
+
+ case 'L' =>
+ visitInternalName(desc.substring(1, desc.length - 1))
+
+ case '[' =>
+ visitInternalNameOrArrayReference(desc)
+
+ case _ => // skip over primitive types
+ }
+
+ def visitConstant(const: AnyRef): Unit = const match {
+ case t: Type => visitDescriptor(t.getDescriptor)
+ case _ =>
+ }
+
+ // in principle we could references to annotation types, as they only end up as strings in the
+ // constant pool, not as class references. however, the java compiler still includes nested
+ // annotation classes in the innerClass table, so we do the same. explained in detail in the
+ // large comment in class BTypes.
+ def visitAnnotation(annot: AnnotationNode): Unit = {
+ visitDescriptor(annot.desc)
+ if (annot.values != null) annot.values.asScala foreach visitConstant
+ }
+
+ def visitAnnotations(annots: java.util.List[_ <: AnnotationNode]) = if (annots != null) annots.asScala foreach visitAnnotation
+ def visitAnnotationss(annotss: Array[java.util.List[AnnotationNode]]) = if (annotss != null) annotss foreach visitAnnotations
+
+ def visitHandle(handle: Handle): Unit = {
+ visitInternalNameOrArrayReference(handle.getOwner)
+ visitDescriptor(handle.getDesc)
+ }
+
+ visitInternalName(classNode.name)
+ innerClasses ++= classBTypeFromParsedClassfile(classNode.name).info.get.nestedClasses
+
+ visitInternalName(classNode.superName)
+ classNode.interfaces.asScala foreach visitInternalName
+ visitInternalName(classNode.outerClass)
+
+ visitAnnotations(classNode.visibleAnnotations)
+ visitAnnotations(classNode.visibleTypeAnnotations)
+ visitAnnotations(classNode.invisibleAnnotations)
+ visitAnnotations(classNode.invisibleTypeAnnotations)
+
+ for (f <- classNode.fields.asScala) {
+ visitDescriptor(f.desc)
+ visitAnnotations(f.visibleAnnotations)
+ visitAnnotations(f.visibleTypeAnnotations)
+ visitAnnotations(f.invisibleAnnotations)
+ visitAnnotations(f.invisibleTypeAnnotations)
+ }
+
+ for (m <- classNode.methods.asScala) {
+ visitDescriptor(m.desc)
+
+ visitAnnotations(m.visibleAnnotations)
+ visitAnnotations(m.visibleTypeAnnotations)
+ visitAnnotations(m.invisibleAnnotations)
+ visitAnnotations(m.invisibleTypeAnnotations)
+ visitAnnotationss(m.visibleParameterAnnotations)
+ visitAnnotationss(m.invisibleParameterAnnotations)
+ visitAnnotations(m.visibleLocalVariableAnnotations)
+ visitAnnotations(m.invisibleLocalVariableAnnotations)
+
+ m.exceptions.asScala foreach visitInternalName
+ for (tcb <- m.tryCatchBlocks.asScala) visitInternalName(tcb.`type`)
+
+ val iter = m.instructions.iterator()
+ while (iter.hasNext) iter.next() match {
+ case ti: TypeInsnNode => visitInternalNameOrArrayReference(ti.desc)
+ case fi: FieldInsnNode => visitInternalNameOrArrayReference(fi.owner); visitDescriptor(fi.desc)
+ case mi: MethodInsnNode => visitInternalNameOrArrayReference(mi.owner); visitDescriptor(mi.desc)
+ case id: InvokeDynamicInsnNode => visitDescriptor(id.desc); visitHandle(id.bsm); id.bsmArgs foreach visitConstant
+ case ci: LdcInsnNode => visitConstant(ci.cst)
+ case ma: MultiANewArrayInsnNode => visitDescriptor(ma.desc)
+ case _ =>
+ }
+ }
+ innerClasses.toList
+ }
+
+ /**
+ * In order to run an Analyzer, the maxLocals / maxStack fields need to be available. The ASM
+ * framework only computes these values during bytecode generation.
+ *
+ * NOTE 1: as explained in the `analysis` package object, the maxStack value used by the Analyzer
+ * may be smaller than the correct maxStack value in the classfile (Analyzers only use a single
+ * slot for long / double values). The maxStack computed here are correct for running an analyzer,
+ * but not for writing in the classfile. We let the ClassWriter recompute max's.
+ *
+ * NOTE 2: the maxStack value computed here may be larger than the smallest correct value
+ * that would allow running an analyzer, see `InstructionStackEffect.forAsmAnalysis` and
+ * `InstructionStackEffect.maxStackGrowth`.
+ *
+ * NOTE 3: the implementation doesn't look at instructions that cannot be reached, it computes
+ * the max local / stack size in the reachable code. These max's work just fine for running an
+ * Analyzer: its implementation also skips over unreachable code in the same way.
+ */
+ def computeMaxLocalsMaxStack(method: MethodNode): Unit = {
+ if (isAbstractMethod(method) || isNativeMethod(method)) {
+ method.maxLocals = 0
+ method.maxStack = 0
+ } else if (!maxLocalsMaxStackComputed(method)) {
+ val size = method.instructions.size
+
+ var maxLocals = parametersSize(method)
+ var maxStack = 0
+
+ // queue of instruction indices where analysis should start
+ var queue = new Array[Int](8)
+ var top = -1
+ def enq(i: Int): Unit = {
+ if (top == queue.length - 1) {
+ val nq = new Array[Int](queue.length * 2)
+ Array.copy(queue, 0, nq, 0, queue.length)
+ queue = nq
+ }
+ top += 1
+ queue(top) = i
+ }
+ def deq(): Int = {
+ val r = queue(top)
+ top -= 1
+ r
+ }
+
+ val subroutineRetTargets = new mutable.Stack[AbstractInsnNode]
+
+ // for each instruction in the queue, contains the stack height at this instruction.
+ // once an instruction has been treated, contains -1 to prevent re-enqueuing
+ val stackHeights = new Array[Int](size)
+
+ def enqInsn(insn: AbstractInsnNode, height: Int): Unit = {
+ enqInsnIndex(method.instructions.indexOf(insn), height)
+ }
+
+ def enqInsnIndex(insnIndex: Int, height: Int): Unit = {
+ if (insnIndex < size && stackHeights(insnIndex) != -1) {
+ stackHeights(insnIndex) = height
+ enq(insnIndex)
+ }
+ }
+
+ val tcbIt = method.tryCatchBlocks.iterator()
+ while (tcbIt.hasNext) {
+ val tcb = tcbIt.next()
+ enqInsn(tcb.handler, 1)
+ if (maxStack == 0) maxStack = 1
+ }
+
+ enq(0)
+ while (top != -1) {
+ val insnIndex = deq()
+ val insn = method.instructions.get(insnIndex)
+ val initHeight = stackHeights(insnIndex)
+ stackHeights(insnIndex) = -1 // prevent i from being enqueued again
+
+ if (insn.getOpcode == -1) { // frames, labels, line numbers
+ enqInsnIndex(insnIndex + 1, initHeight)
+ } else {
+ val stackGrowth = InstructionStackEffect.maxStackGrowth(insn)
+ val heightAfter = initHeight + stackGrowth
+ if (heightAfter > maxStack) maxStack = heightAfter
+
+ // update maxLocals
+ insn match {
+ case v: VarInsnNode =>
+ val longSize = if (isSize2LoadOrStore(v.getOpcode)) 1 else 0
+ maxLocals = math.max(maxLocals, v.`var` + longSize + 1) // + 1 because local numbers are 0-based
+
+ case i: IincInsnNode =>
+ maxLocals = math.max(maxLocals, i.`var` + 1)
+
+ case _ =>
+ }
+
+ insn match {
+ case j: JumpInsnNode =>
+ if (j.getOpcode == JSR) {
+ val jsrTargetHeight = heightAfter + 1
+ if (jsrTargetHeight > maxStack) maxStack = jsrTargetHeight
+ subroutineRetTargets.push(j.getNext)
+ enqInsn(j.label, jsrTargetHeight)
+ } else {
+ enqInsn(j.label, heightAfter)
+ val opc = j.getOpcode
+ if (opc != GOTO) enqInsnIndex(insnIndex + 1, heightAfter) // jump is conditional, so the successor is also a possible control flow target
+ }
+
+ case l: LookupSwitchInsnNode =>
+ var j = 0
+ while (j < l.labels.size) {
+ enqInsn(l.labels.get(j), heightAfter); j += 1
+ }
+ enqInsn(l.dflt, heightAfter)
+
+ case t: TableSwitchInsnNode =>
+ var j = 0
+ while (j < t.labels.size) {
+ enqInsn(t.labels.get(j), heightAfter); j += 1
+ }
+ enqInsn(t.dflt, heightAfter)
+
+ case r: VarInsnNode if r.getOpcode == RET =>
+ enqInsn(subroutineRetTargets.pop(), heightAfter)
+
+ case _ =>
+ val opc = insn.getOpcode
+ if (opc != ATHROW && !isReturn(insn))
+ enqInsnIndex(insnIndex + 1, heightAfter)
+ }
+ }
+ }
+
+ method.maxLocals = maxLocals
+ method.maxStack = maxStack
+
+ maxLocalsMaxStackComputed += method
+ }
+ }
+}