From 4790d75f26de028388df927d3b40fa7ec4c8e2cb Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 15 Nov 2017 18:37:15 +0100 Subject: [PATCH 1/2] Create dotc decompiler * `dotc -decompile *` --- .../dotty/tools/dotc/CompilationUnit.scala | 2 +- .../decompiler/DecompilationPrinter.scala | 31 +++++++++++++++++++ .../dotty/tools/dotc/decompiler/Main.scala | 21 +++++++++++++ .../dotc/decompiler/TASTYDecompiler.scala | 16 ++++++++++ dist/bin/dotc | 2 ++ project/Build.scala | 17 +++++++--- project/scripts/sbtBootstrappedTests | 4 +-- project/scripts/sbtTests | 19 ++++++++++-- 8 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala create mode 100644 compiler/src/dotty/tools/dotc/decompiler/Main.scala create mode 100644 compiler/src/dotty/tools/dotc/decompiler/TASTYDecompiler.scala diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index c8a4e73706e6..63458e8fb578 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -28,7 +28,7 @@ object CompilationUnit { /** Make a compilation unit for top class `clsd` with the contends of the `unpickled` */ def mkCompilationUnit(clsd: ClassDenotation, unpickled: Tree, forceTrees: Boolean)(implicit ctx: Context): CompilationUnit = { assert(!unpickled.isEmpty, unpickled) - val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.sourceFile, Seq())) + val unit1 = new CompilationUnit(new SourceFile(clsd.symbol.associatedFile, Seq())) unit1.tpdTree = unpickled if (forceTrees) force.traverse(unit1.tpdTree) diff --git a/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala b/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala new file mode 100644 index 000000000000..6ad878321bb3 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala @@ -0,0 +1,31 @@ +package dotty.tools.dotc +package decompiler + +import dotty.tools.dotc.core.Contexts._ +import dotty.tools.dotc.core.Phases.Phase + +/** Phase that prints the trees in all loaded compilation units. + * + * @author Nicolas Stucki + */ +class DecompilationPrinter extends Phase { + + override def phaseName: String = "decompilationPrinter" + + override def run(implicit ctx: Context): Unit = { + val unit = ctx.compilationUnit + + val pageWidth = ctx.settings.pageWidth.value + + val doubleLine = "=" * pageWidth + val line = "-" * pageWidth + + println(doubleLine) + println(unit.source) + println(line) + + val code = unit.tpdTree.show + println(if (ctx.useColors) printing.SyntaxHighlighting(code) else code) + println(line) + } +} diff --git a/compiler/src/dotty/tools/dotc/decompiler/Main.scala b/compiler/src/dotty/tools/dotc/decompiler/Main.scala new file mode 100644 index 000000000000..e3b0376b0912 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/decompiler/Main.scala @@ -0,0 +1,21 @@ +package dotty.tools.dotc.decompiler + +import dotty.tools.dotc +import dotty.tools.dotc.core.Contexts._ + +/** Main class of the `dotc -decompiler` decompiler. + * + * @author Nicolas Stucki + */ +object Main extends dotc.Driver { + override protected def newCompiler(implicit ctx: Context): dotc.Compiler = { + assert(ctx.settings.fromTasty.value) + new TASTYDecompiler + } + + override def setup(args0: Array[String], rootCtx: Context): (List[String], Context) = { + var args = args0.filter(a => a != "-decompile") + args = if (args.contains("-from-tasty")) args else "-from-tasty" +: args + super.setup(args, rootCtx) + } +} diff --git a/compiler/src/dotty/tools/dotc/decompiler/TASTYDecompiler.scala b/compiler/src/dotty/tools/dotc/decompiler/TASTYDecompiler.scala new file mode 100644 index 000000000000..e9a49e178b74 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/decompiler/TASTYDecompiler.scala @@ -0,0 +1,16 @@ +package dotty.tools.dotc.decompiler + +import dotty.tools.dotc.fromtasty._ +import dotty.tools.dotc.core.Phases.Phase + +/** Compiler from tasty to user readable high text representation + * of the compiled scala code. + * + * @author Nicolas Stucki + */ +class TASTYDecompiler extends TASTYCompiler { + override def phases: List[List[Phase]] = List( + List(new ReadTastyTreesFromClasses), // Load classes from tasty + List(new DecompilationPrinter) // Print all loaded classes + ) +} diff --git a/dist/bin/dotc b/dist/bin/dotc index 15a34b8af32e..0ada07560cfc 100755 --- a/dist/bin/dotc +++ b/dist/bin/dotc @@ -31,6 +31,7 @@ default_java_opts="-Xmx768m -Xms768m" bootcp=true CompilerMain=dotty.tools.dotc.Main +DecompilerMain=dotty.tools.dotc.decompiler.Main ReplMain=dotty.tools.repl.Main PROG_NAME=$CompilerMain @@ -82,6 +83,7 @@ case "$1" in -Oshort) addJava "-XX:+TieredCompilation -XX:TieredStopAtLevel=1" && shift ;; -repl) PROG_NAME="$ReplMain" && shift ;; -compile) PROG_NAME="$CompilerMain" && shift ;; + -decompile) PROG_NAME="$DecompilerMain" && shift ;; -run) PROG_NAME="$ReplMain" && shift ;; -bootcp) bootcp=true && shift ;; -nobootcp) unset bootcp && shift ;; diff --git a/project/Build.scala b/project/Build.scala index 41fc82b772d6..29371946363a 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -87,6 +87,7 @@ object Build { lazy val dotr = inputKey[Unit]("run compiled binary using the correct classpath, or the user supplied classpath") + // Compiles the documentation and static site lazy val genDocs = taskKey[Unit]("run dottydoc to generate static documentation site") @@ -523,8 +524,8 @@ object Build { } }, run := dotc.evaluated, - dotc := runCompilerMain(false).evaluated, - repl := runCompilerMain(true).evaluated, + dotc := runCompilerMain().evaluated, + repl := runCompilerMain(repl = true).evaluated, // enable verbose exception messages for JUnit testOptions in Test += Tests.Argument( @@ -618,16 +619,22 @@ object Build { } ) - def runCompilerMain(repl: Boolean) = Def.inputTaskDyn { + def runCompilerMain(repl: Boolean = false) = Def.inputTaskDyn { val dottyLib = packageAll.value("dotty-library") val args0: List[String] = spaceDelimited("").parsed.toList - val args = args0.filter(arg => arg != "-repl") + val decompile = args0.contains("-decompile") + val args = args0.filter(arg => arg != "-repl" || arg != "-decompile") val main = if (repl) "dotty.tools.repl.Main" + else if (decompile) "dotty.tools.dotc.decompiler.Main" else "dotty.tools.dotc.Main" - val fullArgs = main :: insertClasspathInArgs(args, dottyLib) + val extraClasspath = + if (decompile && !args.contains("-classpath")) dottyLib + ":." + else dottyLib + + val fullArgs = main :: insertClasspathInArgs(args, extraClasspath) (runMain in Compile).toTask(fullArgs.mkString(" ", " ", "")) } diff --git a/project/scripts/sbtBootstrappedTests b/project/scripts/sbtBootstrappedTests index 9704a0bd1a83..8687e89a5dc4 100755 --- a/project/scripts/sbtBootstrappedTests +++ b/project/scripts/sbtBootstrappedTests @@ -8,7 +8,7 @@ ./project/scripts/sbt dist-bootstrapped/pack # check that `dotc` compiles and `dotr` runs it -echo "testing sbt dotc and dotr" +echo "testing ./bin/dotc and ./bin/dotr" mkdir out/scriptedtest0 ./bin/dotc tests/pos/sbtDotrTest.scala -d out/scriptedtest0 # FIXME #3477 @@ -21,7 +21,7 @@ mkdir out/scriptedtest0 # check that `dotc` compiles and `dotr` runs it -echo "testing sbt dotc -from-tasty and dotr -classpath" +echo "testing ./bin/dotc -from-tasty and dotr -classpath" mkdir out/scriptedtest1 mkdir out/scriptedtest2 ./bin/dotc tests/pos/sbtDotrTest.scala -d out/scriptedtest1/ diff --git a/project/scripts/sbtTests b/project/scripts/sbtTests index 61d4c4dd84ee..08fc8bc4ec0d 100755 --- a/project/scripts/sbtTests +++ b/project/scripts/sbtTests @@ -11,6 +11,7 @@ cat sbtdotr1.out if grep -e "dotr test ok" sbtdotr1.out; then echo "output ok" else + echo "failed output check" exit -1 fi @@ -23,13 +24,25 @@ cat sbtdotr2.out if grep -e "dotr test ok" sbtdotr2.out; then echo "output ok" else + echo "failed output check" + exit -1 +fi + +# check that `sbt dotc -decompile` runs +echo "testing sbt dotc -decompile" +./project/scripts/sbt ";dotc -decompile -code -color:never -classpath out/scriptedtest1 dotrtest.Test" > sbtdotc3.out +cat sbtdotc3.out +if grep -e "def main(args: Array\[String\]): Unit =" sbtdotc3.out; then + echo "output ok" +else + echo "failed output check" exit -1 fi echo "testing sbt dotr with no -classpath" -./project/scripts/sbt ";dotc tests/pos/sbtDotrTest.scala; dotr dotrtest.Test" > sbtdotp1.out -cat sbtdotp1.out -if grep -e "dotr test ok" sbtdotp1.out; then +./project/scripts/sbt ";dotc tests/pos/sbtDotrTest.scala; dotr dotrtest.Test" > sbtdotr3.out +cat sbtdotr3.out +if grep -e "dotr test ok" sbtdotr3.out; then echo "output ok" else exit -1 From 7fd019e1d721de396bffd91e46bd2a58da62c396 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Mon, 20 Nov 2017 21:03:11 +0100 Subject: [PATCH 2/2] Remove ad hoc code coloring --- .../src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala b/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala index 6ad878321bb3..9d38fa361730 100644 --- a/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala +++ b/compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala @@ -24,8 +24,7 @@ class DecompilationPrinter extends Phase { println(unit.source) println(line) - val code = unit.tpdTree.show - println(if (ctx.useColors) printing.SyntaxHighlighting(code) else code) + println(unit.tpdTree.show) println(line) } }