Skip to content

Decompile .tasty files with VS Code Extension #5513

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 3, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ object PickledQuotes {
val pickled = pickler.assembleParts()

if (quotePickling ne noPrinter)
new TastyPrinter(pickled).printContents()
println(new TastyPrinter(pickled).printContents())

pickled
}
Expand All @@ -98,7 +98,7 @@ object PickledQuotes {
private def unpickle(bytes: Array[Byte], splices: Seq[Any], isType: Boolean)(implicit ctx: Context): Tree = {
if (quotePickling ne noPrinter) {
println(i"**** unpickling quote from TASTY")
new TastyPrinter(bytes).printContents()
println(new TastyPrinter(bytes).printContents())
}

val mode = if (isType) UnpickleMode.TypeTree else UnpickleMode.Term
Expand Down
16 changes: 16 additions & 0 deletions compiler/src/dotty/tools/dotc/core/tasty/TastyHTMLPrinter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package dotty.tools.dotc
package core
package tasty

import Contexts._, Decorators._
import Names.Name
import TastyUnpickler._
import TastyBuffer.NameRef
import util.Positions.offsetToInt
import printing.Highlighting._

class TastyHTMLPrinter(bytes: Array[Byte])(implicit ctx: Context) extends TastyPrinter(bytes) {
override protected def nameColor(str: String): String = s"<span class='name'>$str</span>"
override protected def treeColor(str: String): String = s"<span class='tree'>$str</span>"
override protected def lengthColor(str: String): String = s"<span class='length'>$str</span>"
}
92 changes: 59 additions & 33 deletions compiler/src/dotty/tools/dotc/core/tasty/TastyPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import printing.Highlighting._

class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) {

private[this] val sb: StringBuilder = new StringBuilder

val unpickler: TastyUnpickler = new TastyUnpickler(bytes)
import unpickler.{nameAtRef, unpickle}

Expand All @@ -21,41 +23,56 @@ class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) {
def printNames(): Unit =
for ((name, idx) <- nameAtRef.contents.zipWithIndex) {
val index = nameColor("%4d".format(idx))
println(index + ": " + nameToString(name))
sb.append(index + ": " + nameToString(name) + "\n")
}

def printContents(): Unit = {
println("Names:")
def printContents(): String = {
sb.append("Names:\n")
printNames()
println()
println("Trees:")
unpickle(new TreeSectionUnpickler)
unpickle(new PositionSectionUnpickler)
unpickle(new CommentSectionUnpickler)
sb.append("\n")
sb.append("Trees:\n")
unpickle(new TreeSectionUnpickler) match {
case Some(s) => sb.append(s)
case _ => Unit
}
sb.append("\n\n")
unpickle(new PositionSectionUnpickler) match {
case Some(s) => sb.append(s)
case _ => Unit
}
sb.append("\n\n")
unpickle(new CommentSectionUnpickler) match {
case Some(s) => sb.append(s)
case _ => Unit
}
sb.result
}

class TreeSectionUnpickler extends SectionUnpickler[Unit](TreePickler.sectionName) {
class TreeSectionUnpickler extends SectionUnpickler[String](TreePickler.sectionName) {
import TastyFormat._
def unpickle(reader: TastyReader, tastyName: NameTable): Unit = {

private[this] val sb: StringBuilder = new StringBuilder

def unpickle(reader: TastyReader, tastyName: NameTable): String = {
import reader._
var indent = 0
def newLine() = {
val length = treeColor("%5d".format(index(currentAddr) - index(startAddr)))
print(s"\n $length:" + " " * indent)
sb.append(s"\n $length:" + " " * indent)
}
def printNat() = print(Yellow(" " + readNat()).show)
def printNat() = sb.append(Yellow(" " + readNat()).show)
def printName() = {
val idx = readNat()
print(nameColor(" " + idx + " [" + nameRefToString(NameRef(idx)) + "]"))
sb.append(nameColor(" " + idx + " [" + nameRefToString(NameRef(idx)) + "]"))
}
def printTree(): Unit = {
newLine()
val tag = readByte()
print(" ");print(astTagToString(tag))
sb.append(" ");sb.append(astTagToString(tag))
indent += 2
if (tag >= firstLengthTreeTag) {
val len = readNat()
print(s"(${lengthColor(len.toString)})")
sb.append(s"(${lengthColor(len.toString)})")
val end = currentAddr + len
def printTrees() = until(end)(printTree())
tag match {
Expand All @@ -76,7 +93,7 @@ class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) {
printTrees()
}
if (currentAddr != end) {
println(s"incomplete read, current = $currentAddr, end = $end")
sb.append(s"incomplete read, current = $currentAddr, end = $end\n")
goto(end)
}
}
Expand All @@ -96,42 +113,51 @@ class TastyPrinter(bytes: Array[Byte])(implicit ctx: Context) {
}
indent -= 2
}
println(i"start = ${reader.startAddr}, base = $base, current = $currentAddr, end = $endAddr")
println(s"${endAddr.index - startAddr.index} bytes of AST, base = $currentAddr")
sb.append(i"start = ${reader.startAddr}, base = $base, current = $currentAddr, end = $endAddr\n")
sb.append(s"${endAddr.index - startAddr.index} bytes of AST, base = $currentAddr\n")
while (!isAtEnd) {
printTree()
newLine()
}
sb.result
}
}

class PositionSectionUnpickler extends SectionUnpickler[Unit]("Positions") {
def unpickle(reader: TastyReader, tastyName: NameTable): Unit = {
print(s" ${reader.endAddr.index - reader.currentAddr.index}")
class PositionSectionUnpickler extends SectionUnpickler[String]("Positions") {

private[this] val sb: StringBuilder = new StringBuilder

def unpickle(reader: TastyReader, tastyName: NameTable): String = {
sb.append(s" ${reader.endAddr.index - reader.currentAddr.index}")
val positions = new PositionUnpickler(reader).positions
println(s" position bytes:")
sb.append(s" position bytes:\n")
val sorted = positions.toSeq.sortBy(_._1.index)
for ((addr, pos) <- sorted) {
print(treeColor("%10d".format(addr.index)))
println(s": ${offsetToInt(pos.start)} .. ${pos.end}")
sb.append(treeColor("%10d".format(addr.index)))
sb.append(s": ${offsetToInt(pos.start)} .. ${pos.end}\n")
}
sb.result
}
}

class CommentSectionUnpickler extends SectionUnpickler[Unit]("Comments") {
def unpickle(reader: TastyReader, tastyName: NameTable): Unit = {
print(s" ${reader.endAddr.index - reader.currentAddr.index}")
class CommentSectionUnpickler extends SectionUnpickler[String]("Comments") {

private[this] val sb: StringBuilder = new StringBuilder

def unpickle(reader: TastyReader, tastyName: NameTable): String = {
sb.append(s" ${reader.endAddr.index - reader.currentAddr.index}")
val comments = new CommentUnpickler(reader).comments
println(s" comment bytes:")
sb.append(s" comment bytes:\n")
val sorted = comments.toSeq.sortBy(_._1.index)
for ((addr, cmt) <- sorted) {
print(treeColor("%10d".format(addr.index)))
println(s": ${cmt.raw} (expanded = ${cmt.isExpanded})")
sb.append(treeColor("%10d".format(addr.index)))
sb.append(s": ${cmt.raw} (expanded = ${cmt.isExpanded})\n")
}
sb.result
}
}

private def nameColor(str: String): String = Magenta(str).show
private def treeColor(str: String): String = Yellow(str).show
private def lengthColor(str: String): String = Cyan(str).show
protected def nameColor(str: String): String = Magenta(str).show
protected def treeColor(str: String): String = Yellow(str).show
protected def lengthColor(str: String): String = Cyan(str).show
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class DecompilationPrinter extends Phase {
private def printToOutput(out: PrintStream)(implicit ctx: Context): Unit = {
val unit = ctx.compilationUnit
if (ctx.settings.printTasty.value) {
new TastyPrinter(unit.pickled.head._2).printContents()
println(new TastyPrinter(unit.pickled.head._2).printContents())
} else {
val unitFile = unit.source.toString.replace("\\", "/").replace(".class", ".tasty")
out.println(s"/** Decompiled from $unitFile */")
Expand Down
44 changes: 44 additions & 0 deletions compiler/src/dotty/tools/dotc/decompiler/IDEDecompilerDriver.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package dotty.tools
package dotc
package decompiler

import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core._
import dotty.tools.dotc.core.tasty.TastyHTMLPrinter
import dotty.tools.dotc.reporting._
import dotty.tools.dotc.tastyreflect.ReflectionImpl

/**
* Decompiler to be used with IDEs
*/
class IDEDecompilerDriver(val settings: List[String]) extends dotc.Driver {

private val myInitCtx: Context = {
val rootCtx = initCtx.fresh.addMode(Mode.Interactive).addMode(Mode.ReadPositions).addMode(Mode.ReadComments)
rootCtx.setSetting(rootCtx.settings.YretainTrees, true)
rootCtx.setSetting(rootCtx.settings.fromTasty, true)
val ctx = setup(settings.toArray :+ "dummy.scala", rootCtx)._2
ctx.initialize()(ctx)
ctx
}

private val decompiler = new PartialTASTYDecompiler

def run(className: String): (String, String) = {
val reporter = new StoreReporter(null) with HideNonSensicalMessages

val run = decompiler.newRun(myInitCtx.fresh.setReporter(reporter))

implicit val ctx = run.runContext

run.compile(List(className))
run.printSummary()
val unit = ctx.run.units.head

val decompiled = new ReflectionImpl(ctx).showSourceCode.showTree(unit.tpdTree)
val tree = new TastyHTMLPrinter(unit.pickled.head._2).printContents()

reporter.removeBufferedMessages.foreach(message => System.err.println(message))
(tree, decompiled)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package dotty.tools.dotc.decompiler

import dotty.tools.dotc.core.Phases.Phase

/** Partial TASTYDecompiler that doesn't execute the backendPhases
* allowing to control decompiler output by manually running it
* on the CompilationUnits
*/
class PartialTASTYDecompiler extends TASTYDecompiler {
override def phases: List[List[Phase]] =
frontendPhases ::: picklerPhases ::: transformPhases
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package dotty.tools.languageserver

/**
* A `LanguageClient` that regroups all language server features
*/
trait DottyClient extends worksheet.WorksheetClient
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ import reporting._, reporting.diagnostic.{Message, MessageContainer, messages}
import typer.Typer
import util.{Set => _, _}
import interactive._, interactive.InteractiveDriver._
import decompiler.IDEDecompilerDriver
import Interactive.Include
import config.Printers.interactiv

import languageserver.config.ProjectConfig
import languageserver.worksheet.{Worksheet, WorksheetClient, WorksheetService}
import languageserver.worksheet.{Worksheet, WorksheetService}
import languageserver.decompiler.{TastyDecompilerService}

import lsp4j.services._

Expand All @@ -43,7 +45,7 @@ import lsp4j.services._
* - This implementation is based on the LSP4J library: https://github.com/eclipse/lsp4j
*/
class DottyLanguageServer extends LanguageServer
with TextDocumentService with WorkspaceService with WorksheetService { thisServer =>
with TextDocumentService with WorkspaceService with WorksheetService with TastyDecompilerService { thisServer =>
import ast.tpd._

import DottyLanguageServer._
Expand All @@ -56,8 +58,8 @@ class DottyLanguageServer extends LanguageServer

private[this] var rootUri: String = _

private[this] var myClient: WorksheetClient = _
def client: WorksheetClient = myClient
private[this] var myClient: DottyClient = _
def client: DottyClient = myClient

private[this] var myDrivers: mutable.Map[ProjectConfig, InteractiveDriver] = _

Expand Down Expand Up @@ -128,6 +130,25 @@ class DottyLanguageServer extends LanguageServer
drivers(configFor(uri))
}

/** The driver instance responsible for decompiling `uri` in `classPath` */
def decompilerDriverFor(uri: URI, classPath: String): IDEDecompilerDriver = thisServer.synchronized {
val config = configFor(uri)
val defaultFlags = List("-color:never")

implicit class updateDeco(ss: List[String]) {
def update(pathKind: String, pathInfo: String) = {
val idx = ss.indexOf(pathKind)
val ss1 = if (idx >= 0) ss.take(idx) ++ ss.drop(idx + 2) else ss
ss1 ++ List(pathKind, pathInfo)
}
}
val settings =
defaultFlags ++
config.compilerArguments.toList
.update("-classpath", (classPath +: config.dependencyClasspath).mkString(File.pathSeparator))
new IDEDecompilerDriver(settings)
}

/** A mapping from project `p` to the set of projects that transitively depend on `p`. */
def dependentProjects: Map[ProjectConfig, Set[ProjectConfig]] = thisServer.synchronized {
if (myDependentProjects == null) {
Expand All @@ -148,7 +169,7 @@ class DottyLanguageServer extends LanguageServer
myDependentProjects
}

def connect(client: WorksheetClient): Unit = {
def connect(client: DottyClient): Unit = {
myClient = client
}

Expand Down Expand Up @@ -184,7 +205,8 @@ class DottyLanguageServer extends LanguageServer
rootUri = params.getRootUri
assert(rootUri != null)

class DottyServerCapabilities(val worksheetRunProvider: Boolean = true) extends lsp4j.ServerCapabilities
class DottyServerCapabilities(val worksheetRunProvider: Boolean = true,
val tastyDecompiler: Boolean = true) extends lsp4j.ServerCapabilities

val c = new DottyServerCapabilities
c.setTextDocumentSync(TextDocumentSyncKind.Full)
Expand Down
4 changes: 2 additions & 2 deletions language-server/src/dotty/tools/languageserver/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ object Main {

println("Starting server")
val launcher =
new Launcher.Builder[worksheet.WorksheetClient]()
new Launcher.Builder[DottyClient]()
.setLocalService(server)
.setRemoteInterface(classOf[worksheet.WorksheetClient])
.setRemoteInterface(classOf[DottyClient])
.setInput(in)
.setOutput(out)
// For debugging JSON messages:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dotty.tools.languageserver.decompiler

import org.eclipse.lsp4j.TextDocumentIdentifier

// All case classes in this file should have zero-parameters secondary
// constructors to allow Gson to reflectively create instances on
// deserialization without relying on sun.misc.Unsafe.

/** The parameter for the `tasty/decompile` request. */
case class TastyDecompileParams(textDocument: TextDocumentIdentifier) {
def this() = this(null)
}

/** The response to a `tasty/decompile` request. */
case class TastyDecompileResult(tastyTree: String = null, scala: String = null, error: Int = 0) {
def this() = this(null, null, 0)
}

object TastyDecompileResult {
val ErrorTastyVersion = 1
val ErrorClassNotFound = 2
val ErrorOther = -1
}
Loading