All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.epfl.scala.debugadapter.internal.scalasig.Decompiler.scala Maven / Gradle / Ivy

package ch.epfl.scala.debugadapter.internal.scalasig

import ch.epfl.scala.debugadapter.internal.ClassFile

import java.lang.StringBuilder
import java.nio.charset.StandardCharsets.UTF_8

import scala.reflect.internal.pickling.ByteCodecs
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.Opcodes
import ch.epfl.scala.debugadapter.Logger

/**
 * Originally copied from https://github.com/JetBrains/intellij-scala
 * https://github.com/JetBrains/intellij-scala/blob/074e8f98d9789b3e7def3ade8d39e7ae770beccf/scala/decompiler/src/org/jetbrains/plugins/scala/decompiler/scalasig/Decompiler.scala
 */
private[internal] object Decompiler {
  private val BYTES_VALUE = "bytes"
  private val SCALA_SIG_ANNOTATION: String = "Lscala/reflect/ScalaSignature;"
  private val SCALA_LONG_SIG_ANNOTATION: String =
    "Lscala/reflect/ScalaLongSignature;"

  private val ScalaSigBytes = "ScalaSig".getBytes(UTF_8)

  def decompile(classFile: ClassFile, logger: Logger): Option[ScalaSig] = {
    val bytes = classFile.readBytes()
    decompile(bytes, classFile.fullyQualifiedName, logger)
  }

  private[internal] def decompile(
      classBytes: Array[Byte],
      fqcn: String,
      logger: Logger
  ): Option[ScalaSig] = {
    if (!containsMarker(classBytes)) return None

    val reader = new ClassReader(classBytes)

    val scalaAnnotations = scala.collection.mutable.Buffer[String]()

    val scalaVisitor = new AnnotationVisitor(Opcodes.ASM9) {
      override def visitArray(name: String): AnnotationVisitor = {
        if (name == "bytes") {
          new AnnotationVisitor(Opcodes.ASM9) {
            override def visit(name: String, value: Any): Unit = {
              scalaAnnotations += value.asInstanceOf[String]
            }
          }
        } else super.visitArray(name)
      }

      override def visit(name: String, value: Any): Unit = {
        if (name == "bytes") {
          scalaAnnotations += value.asInstanceOf[String]
        }
      }

    }

    val visitor = new ClassVisitor(Opcodes.ASM9) {
      override def visitAnnotation(
          descriptor: String,
          visible: Boolean
      ): AnnotationVisitor = {
        descriptor match {
          case SCALA_SIG_ANNOTATION | SCALA_LONG_SIG_ANNOTATION =>
            scalaVisitor
          case _ => super.visitAnnotation(descriptor, visible)
        }
      }
    }

    reader.accept(visitor, 256)

    if (scalaAnnotations.isEmpty) None
    else {
      val decoded = decode(scalaAnnotations.toList.map(_.getBytes()))
      Some(Parser.parseScalaSig(decoded, fqcn, logger))
    }
  }

  private def decode(strings: List[Array[Byte]]) = {
    val bytes = Array.concat(strings: _*)
    ByteCodecs.decode(bytes)
    bytes
  }

  def decompiledText(
      scalaSig: ScalaSig,
      className: String,
      isPackageObject: Boolean
  ) =
    try {
      val printer = new ScalaSigPrinter(new StringBuilder)

      def findPath(symbol: Symbol) = symbol.name match {
        case "" => None
        case _ =>
          val path = symbol.path
          if (isPackageObject) {
            path.lastIndexOf(".") match {
              case -1 | 0 => None
              case index => Some(path.substring(0, index))
            }
          } else Some(path)
      }

      val symbols = scalaSig.topLevelClasses ++ scalaSig.topLevelObjects

      // Print package with special treatment for package objects
      for {
        symbol <- symbols.headOption
        parent <- symbol.parent
        path <- findPath(parent)
        packageName = ScalaSigPrinter.processName(path)
      } {
        printer.print("package ")
        printer.print(packageName)
        printer.print("\n")
      }

      // Print classes
      for (symbol <- symbols) {
        printer.printSymbol(symbol)
      }

      Some(printer.result)
    } catch {
      case e: ScalaDecompilerException =>
        // Log.warn(s"Error decompiling class $className, ${e.getMessage}")
        None
      case cause: Exception =>
        // Log.error(s"Error decompiling class $className", cause)
        None
    }

  private def containsMarker(text: Array[Byte]): Boolean = {
    val arrayLength = ScalaSigBytes.length
    text.length - arrayLength match {
      case delta if delta >= 1 =>
        var wordStartIdx = 0
        var innerIdx = 0

        while (wordStartIdx <= delta) {
          while (
            innerIdx < arrayLength && text(
              wordStartIdx + innerIdx
            ) == ScalaSigBytes(innerIdx)
          ) {
            innerIdx += 1
          }
          if (innerIdx == arrayLength) return true
          else {
            wordStartIdx += 1
            innerIdx = 0
          }
        }

        false
      case _ => false
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy