|
| 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