From 2183bf97114c9478ebfc586cc6ffe5f1379a229d Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Tue, 31 Oct 2023 18:20:29 +0100 Subject: [PATCH] track JDK version only when runtime exception occurs --- .../core/classfile/ClassfileConstants.scala | 1 - .../dotc/core/classfile/ClassfileParser.scala | 37 +++++++++++++++---- .../classfile/ClassfileTastyUUIDParser.scala | 11 ++++-- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala index 699a4cc787eb..4aa60d973264 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala @@ -11,7 +11,6 @@ object ClassfileConstants { inline val JAVA_MINOR_VERSION = 3 inline val JAVA8_MAJOR_VERSION = 52 - inline val JAVA_LATEST_MAJOR_VERSION = 65 /** (see http://java.sun.com/docs/books/jvms/second_edition/jvms-clarify.html) * diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 698efb058570..a56ac695b57a 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -29,6 +29,28 @@ import scala.compiletime.uninitialized object ClassfileParser { + object Header: + opaque type Version = Long + + object Version: + val Unknown: Version = -1L + + def brokenVersionAddendum(classfileVersion: Version)(using Context): String = + if classfileVersion.exists then + val (maj, min) = (classfileVersion.majorVersion, classfileVersion.minorVersion) + val scalaVersion = config.Properties.versionNumberString + i""" (version $maj.$min), + | please check the JDK compatibility of your Scala version ($scalaVersion)""" + else + "" + + def apply(major: Int, minor: Int): Version = + (major.toLong << 32) | (minor.toLong & 0xFFFFFFFFL) + extension (version: Version) + def exists: Boolean = version != Unknown + def majorVersion: Int = (version >> 32).toInt + def minorVersion: Int = (version & 0xFFFFFFFFL).toInt + import ClassfileConstants._ /** Marker trait for unpicklers that can be embedded in classfiles. */ @@ -57,7 +79,7 @@ object ClassfileParser { } } - private[classfile] def parseHeader(classfile: AbstractFile)(using in: DataReader): Unit = { + private[classfile] def parseHeader(classfile: AbstractFile)(using in: DataReader): Header.Version = { val magic = in.nextInt if (magic != JAVA_MAGIC) throw new IOException(s"class file '${classfile}' has wrong magic number 0x${toHexString(magic)}, should be 0x${toHexString(JAVA_MAGIC)}") @@ -68,9 +90,7 @@ object ClassfileParser { (minorVersion < JAVA_MINOR_VERSION))) throw new IOException( s"class file '${classfile}' has unknown version $majorVersion.$minorVersion, should be at least $JAVA_MAJOR_VERSION.$JAVA_MINOR_VERSION") - if majorVersion > JAVA_LATEST_MAJOR_VERSION then - throw new IOException( - s"class file '${classfile}' has unknown version $majorVersion.$minorVersion, and was compiled by a newer JDK than supported by this Scala version, please update to a newer Scala version.") + Header.Version(majorVersion, minorVersion) } abstract class AbstractConstantPool(using in: DataReader) { @@ -263,6 +283,7 @@ class ClassfileParser( protected var classTParams: Map[Name, Symbol] = Map() private var Scala2UnpicklingMode = Mode.Scala2Unpickling + private var classfileVersion: Header.Version = Header.Version.Unknown classRoot.info = NoLoader().withDecls(instanceScope) moduleRoot.info = NoLoader().withDecls(staticScope).withSourceModule(staticModule) @@ -275,7 +296,7 @@ class ClassfileParser( def run()(using Context): Option[Embedded] = try ctx.base.reusableDataReader.withInstance { reader => implicit val reader2 = reader.reset(classfile) report.debuglog("[class] >> " + classRoot.fullName) - parseHeader(classfile) + classfileVersion = parseHeader(classfile) this.pool = new ConstantPool val res = parseClass() this.pool = null @@ -284,9 +305,11 @@ class ClassfileParser( catch { case e: RuntimeException => if (ctx.debug) e.printStackTrace() + val addendum = Header.Version.brokenVersionAddendum(classfileVersion) throw new IOException( - i"""class file ${classfile.canonicalPath} is broken, reading aborted with ${e.getClass} - |${Option(e.getMessage).getOrElse("")}""") + i""" class file ${classfile.canonicalPath} is broken$addendum, + | reading aborted with ${e.getClass}: + | ${Option(e.getMessage).getOrElse("")}""") } /** Return the class symbol of the given name. */ diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileTastyUUIDParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileTastyUUIDParser.scala index 1a3887abeae5..0393744dde5c 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileTastyUUIDParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileTastyUUIDParser.scala @@ -14,6 +14,8 @@ import dotty.tools.dotc.util._ import dotty.tools.io.AbstractFile import dotty.tools.tasty.TastyReader +import ClassfileParser.Header + import java.io.IOException import java.lang.Integer.toHexString import java.util.UUID @@ -23,10 +25,11 @@ class ClassfileTastyUUIDParser(classfile: AbstractFile)(ictx: Context) { import ClassfileConstants._ private var pool: ConstantPool = uninitialized // the classfile's constant pool + private var classfileVersion: Header.Version = Header.Version.Unknown def checkTastyUUID(tastyUUID: UUID)(using Context): Unit = try ctx.base.reusableDataReader.withInstance { reader => implicit val reader2 = reader.reset(classfile) - ClassfileParser.parseHeader(classfile) + this.classfileVersion = ClassfileParser.parseHeader(classfile) this.pool = new ConstantPool checkTastyAttr(tastyUUID) this.pool = null @@ -34,9 +37,11 @@ class ClassfileTastyUUIDParser(classfile: AbstractFile)(ictx: Context) { catch { case e: RuntimeException => if (ctx.debug) e.printStackTrace() + val addendum = Header.Version.brokenVersionAddendum(classfileVersion) throw new IOException( - i"""class file ${classfile.canonicalPath} is broken, reading aborted with ${e.getClass} - |${Option(e.getMessage).getOrElse("")}""") + i""" class file ${classfile.canonicalPath} is broken$addendum, + | reading aborted with ${e.getClass}: + | ${Option(e.getMessage).getOrElse("")}""") } private def checkTastyAttr(tastyUUID: UUID)(using ctx: Context, in: DataReader): Unit = {