From 6f33c6128df2168f4e9fb3911491f466f6fe9f90 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Fri, 28 Jun 2024 18:20:54 +0200 Subject: [PATCH 1/2] error when reading class file with unknown newer jdk version [Cherry-picked f430e449869d9d6b6cf05373086f3d52b0a11805][modified] --- .../core/classfile/ClassfileConstants.scala | 1 + .../dotc/core/classfile/ClassfileParser.scala | 34 +++++++++++-------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala index 6ad71c5fd1ce..c158dab0a8d6 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala @@ -11,6 +11,7 @@ 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 5b0e7bd873cd..4a84202d9887 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -25,6 +25,8 @@ import io.{AbstractFile, ZipArchive} import scala.util.control.NonFatal object ClassfileParser { + import ClassfileConstants.* + /** Marker trait for unpicklers that can be embedded in classfiles. */ trait Embedded @@ -50,6 +52,23 @@ object ClassfileParser { mapOver(tp) } } + + private[classfile] def parseHeader(classfile: AbstractFile)(using in: DataReader): Unit = { + 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)}") + val minorVersion = in.nextChar.toInt + val majorVersion = in.nextChar.toInt + if ((majorVersion < JAVA_MAJOR_VERSION) || + ((majorVersion == JAVA_MAJOR_VERSION) && + (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.") + } + } class ClassfileParser( @@ -82,7 +101,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() + parseHeader(classfile) this.pool = new ConstantPool val res = parseClass() this.pool = null @@ -96,19 +115,6 @@ class ClassfileParser( |${Option(e.getMessage).getOrElse("")}""") } - private def parseHeader()(using in: DataReader): Unit = { - 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)}") - val minorVersion = in.nextChar.toInt - val majorVersion = in.nextChar.toInt - if ((majorVersion < JAVA_MAJOR_VERSION) || - ((majorVersion == JAVA_MAJOR_VERSION) && - (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") - } - /** Return the class symbol of the given name. */ def classNameToSymbol(name: Name)(using Context): Symbol = val nameStr = name.toString From 9c651d1881543ce85028edee68c3478c43647cab Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Fri, 28 Jun 2024 18:21:56 +0200 Subject: [PATCH 2/2] track JDK version only when runtime exception occurs [Cherry-picked 2183bf97114c9478ebfc586cc6ffe5f1379a229d][modified] --- .../core/classfile/ClassfileConstants.scala | 1 - .../dotc/core/classfile/ClassfileParser.scala | 37 +++++++++++++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileConstants.scala index c158dab0a8d6..6ad71c5fd1ce 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 4a84202d9887..fb9aa4b02d58 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -25,6 +25,28 @@ import io.{AbstractFile, ZipArchive} import scala.util.control.NonFatal 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. */ @@ -53,7 +75,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)}") @@ -64,9 +86,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) } } @@ -89,6 +109,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) @@ -101,7 +122,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 @@ -110,9 +131,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. */