private def computeCustomizedLocationsFor()

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()
  }