in scala/debugger/src/org/jetbrains/plugins/scala/debugger/LocationLineManager.scala [87:291]
private def computeCustomizedLocationsFor(refType: ReferenceType): Unit = {
seenRefTypes += refType
val generatingElem = findElementByReferenceType(refType).orNull
if (generatingElem == null) return
ProgressManager.checkCanceled()
val containingFile = generatingElem.getContainingFile
if (containingFile == null) return
val document = PsiDocumentManager.getInstance(debugProcess.getProject).getDocument(containingFile)
if (document == null) return
def elementStartLine(e: PsiElement): Int = document.getLineNumber(e.getTextOffset)
def locationsOfLine(m: Method, line: Int): Seq[Location] =
Try(m.locationsOfLine(line + 1).asScala.toSeq).getOrElse(Seq.empty)
//scalac sometimes generates very strange line numbers for <init> method
def customizeLineForConstructors(): Unit = {
//2.12 generates line number for return of constructor, it has no use in debugger
def isReturnInstr(location: Location): Boolean = {
try {
val bytecodes = location.method().bytecodes()
val index = location.codeIndex()
bytecodes(index.toInt) == voidReturn
} catch {
case _: Throwable => false
}
}
def shouldPointAtStartLine(location: Location): Boolean = {
ProgressManager.checkCanceled()
if (location.codeIndex() != 0) return false
val lineNumber = ScalaPositionManager.checkedLineNumber(location)
if (lineNumber < 0) return true
val linePosition = SourcePosition.createFromLine(containingFile, lineNumber)
val elem = nonWhitespaceElement(linePosition)
val parent = PsiTreeUtil.getParentOfType(elem, classOf[ScBlockStatement], classOf[ScEarlyDefinitions])
parent == null || !PsiTreeUtil.isAncestor(generatingElem, parent, false)
}
val methods = refType.methodsByName("<init>").asScala.filter(_.declaringType() == refType)
for {
location <- methods.flatMap(_.allLineLocations().asScala)
} {
if (shouldPointAtStartLine(location)) {
val significantElem = DebuggerUtil.getSignificantElement(generatingElem)
val lineNumber = elementStartLine(significantElem)
if (lineNumber != ScalaPositionManager.checkedLineNumber(location))
cacheCustomLine(location, lineNumber)
}
else if (isReturnInstr(location)) {
cacheCustomLine(location, -1)
}
}
}
def customizeCaseClauses(): Unit = {
def skipTypeCheckOptimization(method: Method, caseLineLocations: Seq[Location]): Unit = {
val bytecodes =
try method.bytecodes()
catch {case _: Throwable => return }
def cacheCorrespondingIloadLocations(iconst_0Loc: Location): Unit = {
val codeIndex = iconst_0Loc.codeIndex().toInt
val bytes = readIstore(codeIndex + 1, bytecodes) match {
case Seq() => Nil
case istoreCode => iloadCode(istoreCode)
}
if (bytes.isEmpty) return
method.allLineLocations().asScala.foreach {
case loc if readIload(loc.codeIndex().toInt, bytecodes) == bytes =>
cacheCustomLine(loc, -1)
case _ =>
}
}
val iconst_0Locations = caseLineLocations.filter(l => isIconst_0(l.codeIndex().toInt, bytecodes))
iconst_0Locations.foreach { l =>
cacheCustomLine(l, -1)
cacheCorrespondingIloadLocations(l)
}
}
def skipReturnValueAssignment(method: Method, caseLinesLocations: Seq[Seq[Location]]): Unit = {
val bytecodes =
try method.bytecodes()
catch {case _: Throwable => return }
def storeCode(location: Location): Option[Seq[Byte]] = {
val codeIndex = location.codeIndex().toInt
val code = readStoreCode(codeIndex, bytecodes)
if (code.nonEmpty) Some(code) else None
}
ProgressManager.checkCanceled()
val notCustomizedYet = caseLinesLocations.map(_.filter(!customizedLocationsCache.contains(_)))
val repeating = notCustomizedYet.filter(_.size > 1)
val lastLocations = repeating.map(_.last)
val withStoreCode = for (loc <- lastLocations; code <- storeCode(loc)) yield (loc, code)
val (locationsToSkip, codes) = withStoreCode.unzip
if (codes.distinct.size != 1) return
locationsToSkip.foreach(cacheCustomLine(_, -1))
val bytes = loadCode(codes.head)
if (bytes.isEmpty) return
val loadLocations = method.allLineLocations().asScala.filter { l =>
ProgressManager.checkCanceled()
readLoadCode(l.codeIndex().toInt, bytecodes) == bytes
}
loadLocations.foreach(cacheCustomLine(_, -1))
}
def skipBaseLineExtraLocations(method: Method, locations: Seq[Location]): Unit = {
val filtered = locations.filter(!customizedLocationsCache.contains(_))
if (filtered.size <= 1) return
val bytecodes =
try method.bytecodes()
catch {
case _: Throwable => return
}
val tail: Seq[Location] = filtered.tail
val loadExpressionValueLocations = tail.filter { l =>
ProgressManager.checkCanceled()
readLoadCode(l.codeIndex().toInt, bytecodes).nonEmpty
}
val returnLocations = tail.filter { l =>
returnCodes.contains(bytecodes(l.codeIndex().toInt))
}
(loadExpressionValueLocations ++ returnLocations).foreach(cacheCustomLine(_, -1))
}
def skipGotoLocations(method: Method, possibleLocations: Seq[Location]): Unit = {
val bytecodes =
try method.bytecodes()
catch {case _: Throwable => return }
val gotos = possibleLocations.filter(loc => isGoto(loc.codeIndex().toInt, bytecodes))
gotos.foreach(cacheCustomLine(_, -1))
}
def customizeFor(caseClauses: ScCaseClauses): Unit = {
def tooSmall(m: Method) = {
try m.allLineLocations().size() <= 3
catch {
case _: AbsentInformationException => true
}
}
val baseLine = caseClauses.getParent match {
case ms: ScMatch => ms.expression.map(elementStartLine)
case (_: ScBlock) childOf (_: ScTry) => return //todo: handle try statements
case b: ScBlock => Some(elementStartLine(b))
case _ => None
}
val caseLines = caseClauses.caseClauses.map(elementStartLine)
val methods = refType.methods().asScala.filterNot(tooSmall)
for {
m <- methods
caseLinesLocations = caseLines.map(locationsOfLine(m, _))
if caseLinesLocations.exists(_.nonEmpty)
} {
ProgressManager.checkCanceled()
val flattenCaseLines = caseLinesLocations.flatten
skipTypeCheckOptimization(m, flattenCaseLines)
skipGotoLocations(m, flattenCaseLines)
skipReturnValueAssignment(m, caseLinesLocations)
}
for {
m <- methods
line <- baseLine
locations = locationsOfLine(m, line)
if locations.size > 1
} {
ProgressManager.checkCanceled()
skipBaseLineExtraLocations(m, locations)
skipGotoLocations(m, locations)
}
}
val allCaseClauses = generatingElem.breadthFirst().collect {
case cc: ScCaseClauses => cc
}
allCaseClauses.foreach(customizeFor)
}
ProgressManager.checkCanceled()
customizeLineForConstructors()
customizeCaseClauses()
}