Skip to content

Commit d109c0b

Browse files
Merge pull request #4266 from dotty-staging/compile-decompiled-code
Test compilation of decompiled code
2 parents 2318433 + 3301cf4 commit d109c0b

14 files changed

+114
-106
lines changed

compiler/src/dotty/tools/dotc/decompiler/DecompilationPrinter.scala

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class DecompilationPrinter extends Phase {
2424
var os: OutputStream = null
2525
var ps: PrintStream = null
2626
try {
27-
os = File(outputDir + ".decompiled").outputStream(append = true)
27+
os = File(outputDir + "/decompiled.scala").outputStream(append = true)
2828
ps = new PrintStream(os)
2929
printToOutput(ps)
3030
} finally {
@@ -38,21 +38,15 @@ class DecompilationPrinter extends Phase {
3838
val unit = ctx.compilationUnit
3939
val pageWidth = ctx.settings.pageWidth.value
4040
val printLines = ctx.settings.printLines.value
41-
val doubleLine = "=" * pageWidth
42-
val line = "-" * pageWidth
43-
44-
out.println(doubleLine)
45-
out.println(unit.source)
46-
out.println(line)
4741

42+
out.println(s"/** Decompiled from $unit */")
4843
val printer = new DecompilerPrinter(ctx)
49-
5044
out.println(printer.toText(unit.tpdTree).mkString(pageWidth, printLines))
51-
out.println(line)
5245

5346
if (ctx.settings.printTasty.value) {
47+
out.println("/*")
5448
new TastyPrinter(unit.pickled.head._2).printContents()
55-
out.println(line)
49+
out.println("*/")
5650
}
5751
}
5852
}

compiler/src/dotty/tools/dotc/decompiler/Main.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ object Main extends dotc.Driver {
1414
assert(ctx.settings.fromTasty.value)
1515
val outputDir = ctx.settings.outputDir.value
1616
if (outputDir != ".")
17-
Files.deleteIfExists(Paths.get(outputDir + ".decompiled"))
17+
Files.deleteIfExists(Paths.get(outputDir + "/decompiled.scala"))
1818
new TASTYDecompiler
1919
}
2020

2121
override def setup(args0: Array[String], rootCtx: Context): (List[String], Context) = {
2222
var args = args0.filter(a => a != "-decompile")
23-
args = if (args.contains("-from-tasty")) args else "-from-tasty" +: args
23+
if (!args.contains("-from-tasty")) args = "-from-tasty" +: args
24+
if (args.contains("-d")) args = "-color:never" +: args
2425
super.setup(args, rootCtx)
2526
}
2627
}

compiler/src/dotty/tools/dotc/printing/DecompilerPrinter.scala

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package dotty.tools.dotc.printing
22

33
import dotty.tools.dotc.ast.Trees._
4-
import dotty.tools.dotc.ast.untpd.{PackageDef, Template, TypeDef}
4+
import dotty.tools.dotc.ast.untpd.{Tree, PackageDef, Template, TypeDef}
55
import dotty.tools.dotc.ast.{Trees, untpd}
66
import dotty.tools.dotc.printing.Texts._
77
import dotty.tools.dotc.core.Contexts._
@@ -55,8 +55,21 @@ class DecompilerPrinter(_ctx: Context) extends RefinedPrinter(_ctx) {
5555
}
5656

5757
override protected def toTextTemplate(impl: Template, ofNew: Boolean = false): Text = {
58-
val impl1 = impl.copy(parents = impl.parents.filterNot(_.symbol.maybeOwner == defn.ObjectClass))
59-
super.toTextTemplate(impl1, ofNew)
58+
def isSynthetic(parent: Tree): Boolean = {
59+
val sym = parent.symbol
60+
sym.maybeOwner == defn.ObjectClass ||
61+
(sym == defn.ProductClass && impl.symbol.owner.is(Case))
62+
}
63+
val parents = impl.parents.filterNot(isSynthetic)
64+
65+
// We don't print self type and constructor for objects
66+
val isObject = impl.constr.symbol.owner.is(Module)
67+
if (isObject) {
68+
val parentsText = keywordText(" extends") ~~ Text(parents.map(constrText), keywordStr(" with "))
69+
val bodyText = " {" ~~ toTextGlobal(impl.body, "\n") ~ "}"
70+
parentsText.provided(parents.nonEmpty) ~ bodyText
71+
}
72+
else super.toTextTemplate(impl.copy(parents = parents), ofNew)
6073
}
6174

6275
override protected def typeApplyText[T >: Untyped](tree: TypeApply[T]): Text = {

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -651,8 +651,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
651651
val selfText = {
652652
val selfName = if (self.name == nme.WILDCARD) keywordStr("this") else self.name.toString
653653
(selfName ~ optText(self.tpt)(": " ~ _) ~ " =>").close
654-
} provided !self.isEmpty
655-
654+
}.provided(!self.isEmpty)
656655
val body = if (ctx.settings.YtestPickler.value) {
657656
// Pickling/unpickling reorders the body members, so we need to homogenize
658657
val (params, rest) = impl.body partition {
@@ -664,9 +663,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
664663
params ::: rest
665664
} else impl.body
666665

667-
val bodyText = "{" ~~ selfText ~~ toTextGlobal(primaryConstrs ::: body, "\n") ~ "}"
666+
val bodyText = " {" ~~ selfText ~~ toTextGlobal(primaryConstrs ::: body, "\n") ~ "}"
668667

669-
prefix ~ (keywordText(" extends") provided (!ofNew && parents.nonEmpty)) ~~ parentsText ~~ bodyText
668+
prefix ~ keywordText(" extends").provided(!ofNew && parents.nonEmpty) ~~ parentsText ~ bodyText
670669
}
671670

672671
protected def templateText(tree: TypeDef, impl: Template): Text = {

compiler/test/dotty/tools/dotc/FromTastyTests.scala

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class FromTastyTests extends ParallelTesting {
2626
// > dotc -Ythrough-tasty -Ycheck:all <source>
2727

2828
implicit val testGroup: TestGroup = TestGroup("posTestFromTasty")
29-
val (step1, step2, step3) = compileTastyInDir("tests/pos", defaultOptions,
29+
compileTastyInDir("tests/pos", defaultOptions,
3030
blacklist = Set(
3131
// Wrong number of arguments (only on bootstrapped)
3232
"i3130b.scala",
@@ -36,12 +36,11 @@ class FromTastyTests extends ParallelTesting {
3636

3737
// MatchError in SymDenotation.sourceModule on a ThisType
3838
"t3612.scala",
39+
),
40+
recompilationBlacklist = Set(
41+
"simpleCaseObject"
3942
)
40-
)
41-
step1.checkCompile() // Compile all files to generate the class files with tasty
42-
step2.checkCompile() // Compile from tasty
43-
step3.checkCompile() // Decompile from tasty
44-
(step1 + step2 + step3).delete()
43+
).checkCompile()
4544
}
4645

4746
@Test def runTestFromTasty: Unit = {
@@ -51,21 +50,13 @@ class FromTastyTests extends ParallelTesting {
5150
// > dotr Test
5251

5352
implicit val testGroup: TestGroup = TestGroup("runTestFromTasty")
54-
val (step1, step2, step3) = compileTastyInDir("tests/run", defaultOptions,
55-
blacklist = Set(
56-
// Closure type miss match
57-
"eff-dependent.scala",
58-
)
59-
)
60-
step1.checkCompile() // Compile all files to generate the class files with tasty
61-
step2.checkRuns() // Compile from tasty and run the result
62-
step3.checkCompile() // Decompile from tasty
63-
(step1 + step2 + step3).delete()
64-
}
65-
66-
private implicit class tastyCompilationTuples(tup: (CompilationTest, CompilationTest)) {
67-
def +(that: (CompilationTest, CompilationTest)): (CompilationTest, CompilationTest) =
68-
(tup._1 + that._1, tup._2 + that._2)
53+
compileTastyInDir("tests/run", defaultOptions,
54+
blacklist = Set(
55+
// Closure type miss match
56+
"eff-dependent.scala",
57+
),
58+
recompilationBlacklist = Set()
59+
).checkRuns()
6960
}
7061
}
7162

compiler/test/dotty/tools/vulpix/ParallelTesting.scala

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,27 @@ package dotty
22
package tools
33
package vulpix
44

5-
import java.io.{ File => JFile }
5+
import java.io.{File => JFile}
66
import java.text.SimpleDateFormat
77
import java.util.HashMap
88
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
9-
import java.nio.file.{ Files, Path, Paths, NoSuchFileException }
10-
import java.util.concurrent.{ Executors => JExecutors, TimeUnit, TimeoutException }
9+
import java.nio.file.{Files, NoSuchFileException, Path, Paths}
10+
import java.util.concurrent.{TimeUnit, TimeoutException, Executors => JExecutors}
1111

1212
import scala.io.Source
1313
import scala.util.control.NonFatal
1414
import scala.util.Try
1515
import scala.collection.mutable
1616
import scala.util.matching.Regex
1717
import scala.util.Random
18-
1918
import dotc.core.Contexts._
20-
import dotc.reporting.{ Reporter, TestReporter }
19+
import dotc.reporting.{Reporter, TestReporter}
2120
import dotc.reporting.diagnostic.MessageContainer
2221
import dotc.interfaces.Diagnostic.ERROR
2322
import dotc.util.DiffUtil
24-
import dotc.{ Driver, Compiler }
23+
import dotc.{Compiler, Driver}
2524
import dotc.decompiler
25+
import dotty.tools.vulpix.TestConfiguration.defaultOptions
2626

2727
/** A parallel testing suite whose goal is to integrate nicely with JUnit
2828
*
@@ -425,16 +425,17 @@ trait ParallelTesting extends RunnerOrchestration { self =>
425425
reporter
426426
}
427427

428-
protected def decompile(flags0: TestFlags, suppressErrors: Boolean, targetDir: JFile): TestReporter = {
429-
val decompilationOutput = new JFile(targetDir.getPath)
430-
decompilationOutput.mkdir()
428+
protected def decompile(flags0: TestFlags, suppressErrors: Boolean, targetDir0: JFile): TestReporter = {
429+
val targetDir = new JFile(targetDir0.getParent + "_decompiled")
430+
val decompilationOutput = new JFile(targetDir + "/" + targetDir0.getName)
431+
decompilationOutput.mkdirs()
431432
val flags =
432433
flags0 and ("-d", decompilationOutput.getAbsolutePath) and
433434
"-decompile" and "-pagewidth" and "80"
434435

435436
def hasTastyFileToClassName(f: JFile): String =
436-
targetDir.toPath.relativize(f.toPath).toString.dropRight(".hasTasty".length).replace('/', '.')
437-
val classes = flattenFiles(targetDir).filter(isHasTastyFile).map(hasTastyFileToClassName).sorted
437+
targetDir0.toPath.relativize(f.toPath).toString.dropRight(".hasTasty".length).replace('/', '.')
438+
val classes = flattenFiles(targetDir0).filter(isHasTastyFile).map(hasTastyFileToClassName).sorted
438439

439440
val reporter =
440441
TestReporter.reporter(realStdout, logLevel =
@@ -522,7 +523,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
522523
checkFileOpt match {
523524
case Some(checkFile) =>
524525
val stripTrailingWhitespaces = "(.*\\S|)\\s+".r
525-
val output = Source.fromFile(outDir + ".decompiled").getLines().map {line =>
526+
val output = Source.fromFile(outDir.getParent + "_decompiled/" + outDir.getName + "/decompiled.scala").getLines().map {line =>
526527
stripTrailingWhitespaces.unapplySeq(line).map(_.head).getOrElse(line)
527528
}.mkString("\n")
528529

@@ -1257,8 +1258,8 @@ trait ParallelTesting extends RunnerOrchestration { self =>
12571258
* Tests in the first part of the tuple must be executed before the second.
12581259
* Both testsRequires explicit delete().
12591260
*/
1260-
def compileTastyInDir(f: String, flags0: TestFlags, blacklist: Set[String] = Set.empty)(
1261-
implicit testGroup: TestGroup): (CompilationTest, CompilationTest, CompilationTest) = {
1261+
def compileTastyInDir(f: String, flags0: TestFlags, blacklist: Set[String], recompilationBlacklist: Set[String])(
1262+
implicit testGroup: TestGroup): TastyCompilationTest = {
12621263
val outDir = defaultOutputDir + testGroup + "/"
12631264
val flags = flags0 and "-Yretain-trees"
12641265
val sourceDir = new JFile(f)
@@ -1284,13 +1285,52 @@ trait ParallelTesting extends RunnerOrchestration { self =>
12841285
// Create a CompilationTest and let the user decide whether to execute a pos or a neg test
12851286
val generateClassFiles = compileFilesInDir(f, flags0, blacklist)
12861287

1287-
(
1288+
val decompilationDir = outDir + sourceDir.getName + "_decompiled"
1289+
1290+
new TastyCompilationTest(
12881291
generateClassFiles.keepOutput,
12891292
new CompilationTest(targets).keepOutput,
1290-
new CompilationTest(targets2).keepOutput
1293+
new CompilationTest(targets2).keepOutput,
1294+
recompilationBlacklist,
1295+
decompilationDir,
1296+
shouldDelete = true
12911297
)
12921298
}
12931299

1300+
class TastyCompilationTest(step1: CompilationTest, step2: CompilationTest, step3: CompilationTest,
1301+
recompilationBlacklist: Set[String], decompilationDir: String, shouldDelete: Boolean)(implicit testGroup: TestGroup) {
1302+
1303+
def keepOutput: TastyCompilationTest =
1304+
new TastyCompilationTest(step1, step2, step3, recompilationBlacklist, decompilationDir, shouldDelete)
1305+
1306+
def checkCompile()(implicit summaryReport: SummaryReporting): this.type = {
1307+
step1.checkCompile() // Compile all files to generate the class files with tasty
1308+
step2.checkCompile() // Compile from tasty
1309+
step3.checkCompile() // Decompile from tasty
1310+
1311+
val step4 = compileFilesInDir(decompilationDir, defaultOptions, recompilationBlacklist).keepOutput
1312+
step4.checkCompile() // Recompile decompiled code
1313+
1314+
if (shouldDelete)
1315+
(step1 + step2 + step3 + step4).delete()
1316+
1317+
this
1318+
}
1319+
1320+
def checkRuns()(implicit summaryReport: SummaryReporting): this.type = {
1321+
step1.checkCompile() // Compile all files to generate the class files with tasty
1322+
step2.checkRuns() // Compile from tasty
1323+
step3.checkCompile() // Decompile from tasty
1324+
1325+
val step4 = compileFilesInDir(decompilationDir, defaultOptions, recompilationBlacklist).keepOutput
1326+
step4.checkRuns() // Recompile decompiled code
1327+
1328+
if (shouldDelete)
1329+
(step1 + step2 + step3 + step4).delete()
1330+
1331+
this
1332+
}
1333+
}
12941334

12951335
/** This function behaves similar to `compileFilesInDir` but it ignores
12961336
* sub-directories and as such, does **not** perform separate compilation

tests/pos/lambda.decompiled

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
================================================================================
2-
out/posTestFromTasty/pos/lambda/foo/Foo.class
3-
--------------------------------------------------------------------------------
1+
/** Decompiled from out/posTestFromTasty/pos/lambda/foo/Foo.class */
42
package foo {
53
class Foo() {
64
{
@@ -11,5 +9,4 @@ package foo {
119
}
1210
val a: Int => Int = (x: Int) => x.*(x)
1311
}
14-
}
15-
--------------------------------------------------------------------------------
12+
}

tests/pos/methodTypes.decompiled

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
================================================================================
2-
out/posTestFromTasty/pos/methodTypes/Foo.class
3-
--------------------------------------------------------------------------------
1+
/** Decompiled from out/posTestFromTasty/pos/methodTypes/Foo.class */
42
class Foo() {
53
val x: Int = 1
64
def y: Int = 2
75
def z(): Int = 3
8-
}
9-
--------------------------------------------------------------------------------
6+
}

tests/pos/simpleCaseObject.decompiled

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
================================================================================
2-
out/posTestFromTasty/pos/simpleCaseObject/foo/Foo.class
3-
--------------------------------------------------------------------------------
1+
/** Decompiled from out/posTestFromTasty/pos/simpleCaseObject/foo/Foo.class */
42
package foo {
5-
case object Foo() extends _root_.scala.Product { this: foo.Foo.type =>
3+
case object Foo {
64
override def hashCode(): Int = 1045991777
75
override def toString(): String = "Foo"
86
override def canEqual(that: Any): Boolean =
@@ -15,5 +13,4 @@ package foo {
1513
case _ => throw new IndexOutOfBoundsException(n.toString())
1614
}
1715
}
18-
}
19-
--------------------------------------------------------------------------------
16+
}

tests/pos/simpleClass-2.decompiled

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
================================================================================
2-
out/posTestFromTasty/pos/simpleClass-2/foo/A.class
3-
--------------------------------------------------------------------------------
1+
/** Decompiled from out/posTestFromTasty/pos/simpleClass-2/foo/A.class */
42
package foo {
53
class A() extends foo.B() {}
64
}
7-
--------------------------------------------------------------------------------
8-
================================================================================
9-
out/posTestFromTasty/pos/simpleClass-2/foo/B.class
10-
--------------------------------------------------------------------------------
5+
/** Decompiled from out/posTestFromTasty/pos/simpleClass-2/foo/B.class */
116
package foo {
127
class B() {}
13-
}
14-
--------------------------------------------------------------------------------
8+
}

tests/pos/simpleClass.decompiled

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
================================================================================
2-
out/posTestFromTasty/pos/simpleClass/foo/A.class
3-
--------------------------------------------------------------------------------
1+
/** Decompiled from out/posTestFromTasty/pos/simpleClass/foo/A.class */
42
package foo {
53
class A() {}
64
}
7-
--------------------------------------------------------------------------------
8-
================================================================================
9-
out/posTestFromTasty/pos/simpleClass/foo/B.class
10-
--------------------------------------------------------------------------------
5+
/** Decompiled from out/posTestFromTasty/pos/simpleClass/foo/B.class */
116
package foo {
127
class B() extends foo.A() {}
13-
}
14-
--------------------------------------------------------------------------------
8+
}

tests/pos/simpleDoWhile.decompiled

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
================================================================================
2-
out/posTestFromTasty/pos/simpleDoWhile/Foo.class
3-
--------------------------------------------------------------------------------
1+
/** Decompiled from out/posTestFromTasty/pos/simpleDoWhile/Foo.class */
42
class Foo() {
53
def foo: Unit =
64
{
@@ -11,5 +9,4 @@ class Foo() {
119
}
1210
while (i.!=(0))
1311
}
14-
}
15-
--------------------------------------------------------------------------------
12+
}

0 commit comments

Comments
 (0)