Skip to content

Commit

Permalink
Merge pull request scala#5513 from tuvior/vscodedotty-tasty
Browse files Browse the repository at this point in the history
Decompile .tasty files with VS Code Extension
  • Loading branch information
nicolasstucki authored Jan 3, 2019
2 parents 01078c4 + 360e0d7 commit 89496e6
Show file tree
Hide file tree
Showing 17 changed files with 540 additions and 50 deletions.
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).append(": ").append(nameToString(name)).append("\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(treeColor(" " + readNat()))
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(" ").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,11 @@
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 protected def backendPhases: List[List[Phase]] = Nil
}
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

0 comments on commit 89496e6

Please sign in to comment.