From cfbb763aa7e08409eb17068abaa0313b06df68ed Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Tue, 31 May 2022 22:19:24 +0200 Subject: [PATCH] Use JarWriter only when target jar is empty --- .../tools/backend/jvm/ClassfileWriter.scala | 86 ++++++++++--------- .../dotty/tools/backend/jvm/GenBCode.scala | 7 +- .../tools/backend/jvm/PostProcessor.scala | 11 +-- .../jvm/PostProcessorFrontendAccess.scala | 17 ++-- 4 files changed, 60 insertions(+), 61 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/ClassfileWriter.scala b/compiler/src/dotty/tools/backend/jvm/ClassfileWriter.scala index e50f3ad4e5c9..947d4e62e5e6 100644 --- a/compiler/src/dotty/tools/backend/jvm/ClassfileWriter.scala +++ b/compiler/src/dotty/tools/backend/jvm/ClassfileWriter.scala @@ -20,31 +20,34 @@ class ClassfileWriter(frontendAccess: PostProcessorFrontendAccess) { private val dumpOutputDir: AbstractFile = getDirectoryOrNull(compilerSettings.dumpClassesDirectory) // if non-null, classfiles are written to a jar instead of the output directory - private val jarWriter: JarWriter = - val f = compilerSettings.outputDirectory - if f.hasExtension("jar") then - // If no main class was specified, see if there's only one - // entry point among the classes going into the jar. - val mainClass = compilerSettings.mainClass match { - case c @ Some(m) => - backendReporting.log(s"Main-Class was specified: ${m}") - c - case None => frontendAccess.getEntryPoints match { - case Nil => - backendReporting.log("No Main-Class designated or discovered.") - None + private val jarWriter: JarWriter | Null = compilerSettings.outputDirectory match { + case jar: JarArchive => + val mainClass = compilerSettings.mainClass.orElse { + // If no main class was specified, see if there's only one + // entry point among the classes going into the jar. + frontendAccess.getEntryPoints match { case name :: Nil => backendReporting.log(s"Unique entry point: setting Main-Class to $name") Some(name) case names => - backendReporting.log(s"No Main-Class due to multiple entry points:\n ${names.mkString("\n ")}") + if names.isEmpty then backendReporting.warning("No Main-Class designated or discovered.") + else backendReporting.warning(s"No Main-Class due to multiple entry points:\n ${names.mkString("\n ")}") None } } - - val jarMainAttrs = mainClass.map(c => Name.MAIN_CLASS -> c).toList - new Jar(f.file).jarWriter(jarMainAttrs: _*) - else null + jar.underlyingSource.map{ source => + if jar.isEmpty then + val jarMainAttrs = mainClass.map(Name.MAIN_CLASS -> _).toList + new Jar(source.file).jarWriter(jarMainAttrs: _*) + else + // Writing to non-empty JAR might be an undefined behaviour, e.g. in case if other files where + // created using `AbstractFile.bufferedOutputStream`instead of JarWritter + backendReporting.warning(s"Tried to write to non-empty JAR: $source") + null + }.orNull + + case _ => null + } private def getDirectoryOrNull(dir: Option[String]): AbstractFile = dir.map(d => new PlainDirectory(Directory(d))).orNull @@ -88,20 +91,9 @@ class ClassfileWriter(frontendAccess: PostProcessorFrontendAccess) { } } - def write(className: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): AbstractFile | Null = try { + def writeClass(className: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): AbstractFile | Null = try { // val writeStart = Statistics.startTimer(BackendStats.bcodeWriteTimer) - val outFile = if (jarWriter == null) { - val outFolder = compilerSettings.outputDirectory - val outFile = getFile(outFolder, className, ".class") - writeBytes(outFile, bytes) - outFile - } else { - val path = className + ".class" - val out = jarWriter.newOutputStream(path) - try out.write(bytes, 0, bytes.length) - finally out.flush() - null - } + val outFile = writeToJarOrFile(className, bytes, ".class") // Statistics.stopTimer(BackendStats.bcodeWriteTimer, writeStart) if (dumpOutputDir != null) { @@ -111,22 +103,34 @@ class ClassfileWriter(frontendAccess: PostProcessorFrontendAccess) { outFile } catch { case e: FileConflictException => - backendReporting.error(s"error writing $className: ${e.getMessage}", NoSourcePosition) + backendReporting.error(s"error writing $className: ${e.getMessage}") null case e: java.nio.file.FileSystemException => if compilerSettings.debug then e.printStackTrace() - backendReporting.error(s"error writing $className: ${e.getClass.getName} ${e.getMessage}", NoSourcePosition) + backendReporting.error(s"error writing $className: ${e.getClass.getName} ${e.getMessage}") null } - def writeTasty(className: InternalName, bytes: Array[Byte]): Unit = - val outFolder = compilerSettings.outputDirectory - val outFile = getFile(outFolder, className, ".tasty") - try writeBytes(outFile, bytes) - catch case ex: ClosedByInterruptException => - try outFile.delete() // don't leave an empty or half-written tastyfile around after an interrupt - catch case _: Throwable => () - finally throw ex + def writeTasty(className: InternalName, bytes: Array[Byte]): Unit = + writeToJarOrFile(className, bytes, ".tasty") + + private def writeToJarOrFile(className: InternalName, bytes: Array[Byte], suffix: String): AbstractFile | Null = { + if jarWriter == null then + val outFolder = compilerSettings.outputDirectory + val outFile = getFile(outFolder, className, suffix) + try writeBytes(outFile, bytes) + catch case ex: ClosedByInterruptException => + try outFile.delete() // don't leave an empty or half-written files around after an interrupt + catch case _: Throwable => () + finally throw ex + outFile + else + val path = className + suffix + val out = jarWriter.newOutputStream(path) + try out.write(bytes, 0, bytes.length) + finally out.flush() + null + } def close(): Unit = { if (jarWriter != null) jarWriter.close() diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index 9659d25b4617..5a4fba2cbffc 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -22,9 +22,8 @@ class GenBCode extends Phase { self => superCallsMap.update(sym, old + calls) } - private[jvm] val entryPoints = new mutable.HashSet[String]() - def registerEntryPoint(s: String): Unit = - entryPoints += s + private val entryPoints = new mutable.HashSet[String]() + def registerEntryPoint(s: String): Unit = entryPoints += s private var _backendInterface: DottyBackendInterface = _ def backendInterface(using Context): DottyBackendInterface = { @@ -52,7 +51,7 @@ class GenBCode extends Phase { self => private var _frontendAccess: PostProcessorFrontendAccess = _ def frontendAccess(using Context): PostProcessorFrontendAccess = { if _frontendAccess eq null then - _frontendAccess = PostProcessorFrontendAccess.Impl(backendInterface) + _frontendAccess = PostProcessorFrontendAccess.Impl(backendInterface, entryPoints) _frontendAccess } diff --git a/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala b/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala index dfe200a8a638..98e3b8ceefcb 100644 --- a/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala +++ b/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala @@ -18,7 +18,7 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, val bTypes: import int.given val backendUtils = new BackendUtils(this) - lazy val classfileWriter = ClassfileWriter(frontendAccess) + val classfileWriter = ClassfileWriter(frontendAccess) def postProcessAndSendToDisk(generatedDefs: GeneratedDefs): Unit = { val GeneratedDefs(classes, tasty) = generatedDefs @@ -30,21 +30,18 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, val bTypes: serializeClass(classNode) catch case e: java.lang.RuntimeException if e.getMessage != null && e.getMessage.nn.contains("too large!") => - backendReporting.error( - s"Could not write class ${classNode.name} because it exceeds JVM code size limits. ${e.getMessage}", - NoSourcePosition - ) + backendReporting.error(s"Could not write class ${classNode.name} because it exceeds JVM code size limits. ${e.getMessage}") null case ex: Throwable => ex.printStackTrace() - backendReporting.error(s"Error while emitting ${classNode.name}\n${ex.getMessage}",NoSourcePosition) + backendReporting.error(s"Error while emitting ${classNode.name}\n${ex.getMessage}") null if (bytes != null) { if (AsmUtils.traceSerializedClassEnabled && classNode.name.nn.contains(AsmUtils.traceSerializedClassPattern)) AsmUtils.traceClass(bytes) - val clsFile = classfileWriter.write(classNode.name.nn, bytes, sourceFile) + val clsFile = classfileWriter.writeClass(classNode.name.nn, bytes, sourceFile) if clsFile != null then onFileCreated(clsFile) } } diff --git a/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala b/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala index c194bd415975..041930680bc3 100644 --- a/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala +++ b/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala @@ -1,6 +1,6 @@ package dotty.tools.backend.jvm -import scala.collection.mutable.Clearable +import scala.collection.mutable.{Clearable, HashSet} import dotty.tools.dotc.util.* import dotty.tools.io.AbstractFile import java.util.{Collection => JCollection, Map => JMap} @@ -20,7 +20,7 @@ sealed abstract class PostProcessorFrontendAccess { def getEntryPoints: List[String] private val frontendLock: AnyRef = new Object() - @inline final def frontendSynch[T](x: => T): T = frontendLock.synchronized(x) + inline final def frontendSynch[T](inline x: => T): T = frontendLock.synchronized(x) } object PostProcessorFrontendAccess { @@ -35,11 +35,12 @@ object PostProcessorFrontendAccess { } sealed trait BackendReporting { - def error(message: String, pos: SourcePosition): Unit + def error(message: String): Unit + def warning(message: String): Unit def log(message: String): Unit } - class Impl[I <: DottyBackendInterface](val int: I) extends PostProcessorFrontendAccess { + class Impl[I <: DottyBackendInterface](val int: I, entryPoints: HashSet[String]) extends PostProcessorFrontendAccess { import int.given lazy val compilerSettings: CompilerSettings = buildCompilerSettings() @@ -67,13 +68,11 @@ object PostProcessorFrontendAccess { } object backendReporting extends BackendReporting { - def error(message: String, pos: SourcePosition): Unit = frontendSynch(report.error(message, pos)) + def error(message: String): Unit = frontendSynch(report.error(message, NoSourcePosition)) + def warning(message: String): Unit = frontendSynch(report.warning(message, NoSourcePosition)) def log(message: String): Unit = frontendSynch(report.log(message)) } - def getEntryPoints: List[String] = frontendSynch(Phases.genBCodePhase match { - case genBCode: GenBCode => genBCode.entryPoints.toList - case _ => Nil - }) + def getEntryPoints: List[String] = frontendSynch(entryPoints.toList) } } \ No newline at end of file