Skip to content

Commit 0f35054

Browse files
committed
Prevent infinite recursion in ProdConsAnalyzer
When an instruction is its own producer or consumer, the `initialProducer` / `ultimateConsumer` methods would loop. While loops or @tailrec annotated methods can generate such bytecode.
1 parent 055a373 commit 0f35054

File tree

3 files changed

+63
-9
lines changed

3 files changed

+63
-9
lines changed

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import java.io.{StringWriter, PrintWriter}
1010
import scala.tools.asm.util.{CheckClassAdapter, TraceClassVisitor, TraceMethodVisitor, Textifier}
1111
import scala.tools.asm.{ClassWriter, Attribute, ClassReader}
1212
import scala.collection.convert.decorateAsScala._
13+
import scala.tools.nsc.backend.jvm.analysis.InitialProducer
1314
import scala.tools.nsc.backend.jvm.opt.InlineInfoAttributePrototype
1415

1516
object AsmUtils {
@@ -81,13 +82,16 @@ object AsmUtils {
8182
/**
8283
* Returns a human-readable representation of the given instruction.
8384
*/
84-
def textify(insn: AbstractInsnNode): String = {
85-
val trace = new TraceMethodVisitor(new Textifier)
86-
insn.accept(trace)
87-
val sw = new StringWriter
88-
val pw = new PrintWriter(sw)
89-
trace.p.print(pw)
90-
sw.toString.trim
85+
def textify(insn: AbstractInsnNode): String = insn match {
86+
case _: InitialProducer =>
87+
insn.toString
88+
case _ =>
89+
val trace = new TraceMethodVisitor(new Textifier)
90+
insn.accept(trace)
91+
val sw = new StringWriter
92+
val pw = new PrintWriter(sw)
93+
trace.p.print(pw)
94+
sw.toString.trim
9195
}
9296

9397
/**

src/compiler/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzer.scala

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,11 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
103103
def initialProducersForValueAt(insn: AbstractInsnNode, slot: Int): Set[AbstractInsnNode] = {
104104
def initialProducers(insn: AbstractInsnNode, producedSlot: Int): Set[AbstractInsnNode] = {
105105
if (isCopyOperation(insn)) {
106-
_initialProducersCache.getOrElseUpdate((insn, producedSlot), {
106+
val key = (insn, producedSlot)
107+
_initialProducersCache.getOrElseUpdate(key, {
108+
// prevent infinite recursion if an instruction is its own producer or consumer
109+
// see cyclicProdCons in ProdConsAnalyzerTest
110+
_initialProducersCache(key) = Set.empty
107111
val (sourceValue, sourceValueSlot) = copyOperationSourceValue(insn, producedSlot)
108112
sourceValue.insns.iterator.asScala.flatMap(initialProducers(_, sourceValueSlot)).toSet
109113
})
@@ -121,7 +125,11 @@ class ProdConsAnalyzer(methodNode: MethodNode, classInternalName: InternalName)
121125
def ultimateConsumersOfValueAt(insn: AbstractInsnNode, slot: Int): Set[AbstractInsnNode] = {
122126
def ultimateConsumers(insn: AbstractInsnNode, consumedSlot: Int): Set[AbstractInsnNode] = {
123127
if (isCopyOperation(insn)) {
124-
_ultimateConsumersCache.getOrElseUpdate((insn, consumedSlot), {
128+
val key = (insn, consumedSlot)
129+
_ultimateConsumersCache.getOrElseUpdate(key, {
130+
// prevent infinite recursion if an instruction is its own producer or consumer
131+
// see cyclicProdCons in ProdConsAnalyzerTest
132+
_ultimateConsumersCache(key) = Set.empty
125133
for {
126134
producedSlot <- copyOperationProducedValueSlots(insn, consumedSlot)
127135
consumer <- consumersOfValueAt(insn.getNext, producedSlot)

test/junit/scala/tools/nsc/backend/jvm/analysis/ProdConsAnalyzerTest.scala

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,46 @@ class ProdConsAnalyzerTest extends ClearAfterClass {
246246
testSingleInsn(a.consumersOfOutputsFrom(l2i), "IRETURN")
247247
testSingleInsn(a.producersForInputsOf(ret), "L2I")
248248
}
249+
250+
@Test
251+
def cyclicProdCons(): Unit = {
252+
import Opcodes._
253+
val m = genMethod(descriptor = "(I)I")(
254+
Label(1),
255+
VarOp(ILOAD, 1),
256+
IntOp(BIPUSH, 10),
257+
Op(IADD), // consumer of the above ILOAD
258+
259+
Op(ICONST_0),
260+
Jump(IF_ICMPNE, Label(2)),
261+
262+
VarOp(ILOAD, 1),
263+
VarOp(ISTORE, 1),
264+
Jump(GOTO, Label(1)),
265+
266+
Label(2),
267+
IntOp(BIPUSH, 9),
268+
Op(IRETURN)
269+
)
270+
m.maxLocals = 2
271+
m.maxStack = 2
272+
val a = new ProdConsAnalyzer(m, "C")
273+
274+
val List(iadd) = findInstr(m, "IADD")
275+
val firstLoad = iadd.getPrevious.getPrevious
276+
assert(firstLoad.getOpcode == ILOAD)
277+
val secondLoad = findInstr(m, "ISTORE").head.getPrevious
278+
assert(secondLoad.getOpcode == ILOAD)
279+
280+
testSingleInsn(a.producersForValueAt(iadd, 2), "ILOAD")
281+
testSingleInsn(a.initialProducersForValueAt(iadd, 2), "ParameterProducer(1)")
282+
testMultiInsns(a.producersForInputsOf(firstLoad), List("ParameterProducer", "ISTORE"))
283+
testMultiInsns(a.producersForInputsOf(secondLoad), List("ParameterProducer", "ISTORE"))
284+
285+
testSingleInsn(a.ultimateConsumersOfOutputsFrom(firstLoad), "IADD")
286+
testSingleInsn(a.ultimateConsumersOfOutputsFrom(secondLoad), "IADD")
287+
288+
testSingleInsn(a.consumersOfOutputsFrom(firstLoad), "IADD")
289+
testSingleInsn(a.consumersOfOutputsFrom(secondLoad), "ISTORE")
290+
}
249291
}

0 commit comments

Comments
 (0)