Skip to content

Commit 3b96392

Browse files
committed
adding BytecodeWriters
1 parent 367ce51 commit 3b96392

File tree

1 file changed

+144
-0
lines changed

1 file changed

+144
-0
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/* NSC -- new Scala compiler
2+
* Copyright 2005-2012 LAMP/EPFL
3+
* @author Martin Odersky
4+
*/
5+
6+
package dotty.tools
7+
package dotc
8+
package backend.jvm
9+
10+
import java.io.{ DataOutputStream, FileOutputStream, IOException, OutputStream, File => JFile }
11+
import scala.tools.nsc.io._
12+
import java.util.jar.Attributes.Name
13+
import scala.language.postfixOps
14+
15+
/** Can't output a file due to the state of the file system. */
16+
class FileConflictException(msg: String, val file: AbstractFile) extends IOException(msg)
17+
18+
/** For the last mile: turning generated bytecode in memory into
19+
* something you can use. Has implementations for writing to class
20+
* files, jars, and disassembled/javap output.
21+
*/
22+
trait BytecodeWriters {
23+
24+
def outputDirectory(sym: Symbol): AbstractFile =
25+
settings.outputDirs outputDirFor enteringFlatten(sym.sourceFile)
26+
27+
/**
28+
* @param clsName cls.getName
29+
*/
30+
def getFile(base: AbstractFile, clsName: String, suffix: String): AbstractFile = {
31+
def ensureDirectory(dir: AbstractFile): AbstractFile =
32+
if (dir.isDirectory) dir
33+
else throw new FileConflictException(s"${base.path}/$clsName$suffix: ${dir.path} is not a directory", dir)
34+
var dir = base
35+
val pathParts = clsName.split("[./]").toList
36+
for (part <- pathParts.init) dir = ensureDirectory(dir) subdirectoryNamed part
37+
ensureDirectory(dir) fileNamed pathParts.last + suffix
38+
}
39+
def getFile(sym: Symbol, clsName: String, suffix: String): AbstractFile =
40+
getFile(outputDirectory(sym), clsName, suffix)
41+
42+
def factoryNonJarBytecodeWriter(): BytecodeWriter = {
43+
val emitAsmp = settings.Ygenasmp.isSetByUser
44+
val doDump = settings.Ydumpclasses.isSetByUser
45+
(emitAsmp, doDump) match {
46+
case (false, false) => new ClassBytecodeWriter { }
47+
case (false, true ) => new ClassBytecodeWriter with DumpBytecodeWriter { }
48+
case (true, false) => new ClassBytecodeWriter with AsmpBytecodeWriter
49+
case (true, true ) => new ClassBytecodeWriter with AsmpBytecodeWriter with DumpBytecodeWriter { }
50+
}
51+
}
52+
53+
trait BytecodeWriter {
54+
def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile): Unit
55+
def close(): Unit = ()
56+
}
57+
58+
class DirectToJarfileWriter(jfile: JFile) extends BytecodeWriter {
59+
val jarMainAttrs = (
60+
if (settings.mainClass.isDefault) Nil
61+
else List(Name.MAIN_CLASS -> settings.mainClass.value)
62+
)
63+
val writer = new Jar(jfile).jarWriter(jarMainAttrs: _*)
64+
65+
def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile) {
66+
assert(outfile == null,
67+
"The outfile formal param is there just because ClassBytecodeWriter overrides this method and uses it.")
68+
val path = jclassName + ".class"
69+
val out = writer.newOutputStream(path)
70+
71+
try out.write(jclassBytes, 0, jclassBytes.length)
72+
finally out.flush()
73+
74+
informProgress("added " + label + path + " to jar")
75+
}
76+
override def close() = writer.close()
77+
}
78+
79+
/*
80+
* The ASM textual representation for bytecode overcomes disadvantages of javap ouput in three areas:
81+
* (a) pickle dingbats undecipherable to the naked eye;
82+
* (b) two constant pools, while having identical contents, are displayed differently due to physical layout.
83+
* (c) stack maps (classfile version 50 and up) are displayed in encoded form by javap,
84+
* their expansion by ASM is more readable.
85+
*
86+
* */
87+
trait AsmpBytecodeWriter extends BytecodeWriter {
88+
import scala.tools.asm
89+
90+
private val baseDir = Directory(settings.Ygenasmp.value).createDirectory()
91+
92+
private def emitAsmp(jclassBytes: Array[Byte], asmpFile: io.File) {
93+
val pw = asmpFile.printWriter()
94+
try {
95+
val cnode = new asm.tree.ClassNode()
96+
val cr = new asm.ClassReader(jclassBytes)
97+
cr.accept(cnode, 0)
98+
val trace = new scala.tools.asm.util.TraceClassVisitor(new java.io.PrintWriter(new java.io.StringWriter()))
99+
cnode.accept(trace)
100+
trace.p.print(pw)
101+
}
102+
finally pw.close()
103+
}
104+
105+
abstract override def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile) {
106+
super.writeClass(label, jclassName, jclassBytes, outfile)
107+
108+
val segments = jclassName.split("[./]")
109+
val asmpFile = segments.foldLeft(baseDir: Path)(_ / _) changeExtension "asmp" toFile;
110+
111+
asmpFile.parent.createDirectory()
112+
emitAsmp(jclassBytes, asmpFile)
113+
}
114+
}
115+
116+
trait ClassBytecodeWriter extends BytecodeWriter {
117+
def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile) {
118+
assert(outfile != null,
119+
"Precisely this override requires its invoker to hand out a non-null AbstractFile.")
120+
val outstream = new DataOutputStream(outfile.bufferedOutput)
121+
122+
try outstream.write(jclassBytes, 0, jclassBytes.length)
123+
finally outstream.close()
124+
informProgress("wrote '" + label + "' to " + outfile)
125+
}
126+
}
127+
128+
trait DumpBytecodeWriter extends BytecodeWriter {
129+
val baseDir = Directory(settings.Ydumpclasses.value).createDirectory()
130+
131+
abstract override def writeClass(label: String, jclassName: String, jclassBytes: Array[Byte], outfile: AbstractFile) {
132+
super.writeClass(label, jclassName, jclassBytes, outfile)
133+
134+
val pathName = jclassName
135+
val dumpFile = pathName.split("[./]").foldLeft(baseDir: Path) (_ / _) changeExtension "class" toFile;
136+
dumpFile.parent.createDirectory()
137+
val outstream = new DataOutputStream(new FileOutputStream(dumpFile.path))
138+
139+
try outstream.write(jclassBytes, 0, jclassBytes.length)
140+
finally outstream.close()
141+
}
142+
}
143+
}
144+

0 commit comments

Comments
 (0)