@@ -29,6 +29,28 @@ import scala.compiletime.uninitialized
2929
3030object ClassfileParser {
3131
32+ object Header :
33+ opaque type Version = Long
34+
35+ object Version :
36+ val Unknown : Version = - 1L
37+
38+ def brokenVersionAddendum (classfileVersion : Version )(using Context ): String =
39+ if classfileVersion.exists then
40+ val (maj, min) = (classfileVersion.majorVersion, classfileVersion.minorVersion)
41+ val scalaVersion = config.Properties .versionNumberString
42+ i """ (version $maj. $min),
43+ | please check the JDK compatibility of your Scala version ( $scalaVersion) """
44+ else
45+ " "
46+
47+ def apply (major : Int , minor : Int ): Version =
48+ (major.toLong << 32 ) | (minor.toLong & 0xFFFFFFFFL)
49+ extension (version : Version )
50+ def exists : Boolean = version != Unknown
51+ def majorVersion : Int = (version >> 32 ).toInt
52+ def minorVersion : Int = (version & 0xFFFFFFFFL).toInt
53+
3254 import ClassfileConstants ._
3355
3456 /** Marker trait for unpicklers that can be embedded in classfiles. */
@@ -57,6 +79,20 @@ object ClassfileParser {
5779 }
5880 }
5981
82+ private [classfile] def parseHeader (classfile : AbstractFile )(using in : DataReader ): Header .Version = {
83+ val magic = in.nextInt
84+ if (magic != JAVA_MAGIC )
85+ throw new IOException (s " class file ' ${classfile}' has wrong magic number 0x ${toHexString(magic)}, should be 0x ${toHexString(JAVA_MAGIC )}" )
86+ val minorVersion = in.nextChar.toInt
87+ val majorVersion = in.nextChar.toInt
88+ if ((majorVersion < JAVA_MAJOR_VERSION ) ||
89+ ((majorVersion == JAVA_MAJOR_VERSION ) &&
90+ (minorVersion < JAVA_MINOR_VERSION )))
91+ throw new IOException (
92+ s " class file ' ${classfile}' has unknown version $majorVersion. $minorVersion, should be at least $JAVA_MAJOR_VERSION. $JAVA_MINOR_VERSION" )
93+ Header .Version (majorVersion, minorVersion)
94+ }
95+
6096 abstract class AbstractConstantPool (using in : DataReader ) {
6197 protected val len = in.nextChar
6298 protected val starts = new Array [Int ](len)
@@ -247,6 +283,7 @@ class ClassfileParser(
247283 protected var classTParams : Map [Name , Symbol ] = Map ()
248284
249285 private var Scala2UnpicklingMode = Mode .Scala2Unpickling
286+ private var classfileVersion : Header .Version = Header .Version .Unknown
250287
251288 classRoot.info = NoLoader ().withDecls(instanceScope)
252289 moduleRoot.info = NoLoader ().withDecls(staticScope).withSourceModule(staticModule)
@@ -259,7 +296,7 @@ class ClassfileParser(
259296 def run ()(using Context ): Option [Embedded ] = try ctx.base.reusableDataReader.withInstance { reader =>
260297 implicit val reader2 = reader.reset(classfile)
261298 report.debuglog(" [class] >> " + classRoot.fullName)
262- parseHeader()
299+ classfileVersion = parseHeader(classfile )
263300 this .pool = new ConstantPool
264301 val res = parseClass()
265302 this .pool = null
@@ -268,22 +305,11 @@ class ClassfileParser(
268305 catch {
269306 case e : RuntimeException =>
270307 if (ctx.debug) e.printStackTrace()
308+ val addendum = Header .Version .brokenVersionAddendum(classfileVersion)
271309 throw new IOException (
272- i """ class file ${classfile.canonicalPath} is broken, reading aborted with ${e.getClass}
273- | ${Option (e.getMessage).getOrElse(" " )}""" )
274- }
275-
276- private def parseHeader ()(using in : DataReader ): Unit = {
277- val magic = in.nextInt
278- if (magic != JAVA_MAGIC )
279- throw new IOException (s " class file ' ${classfile}' has wrong magic number 0x ${toHexString(magic)}, should be 0x ${toHexString(JAVA_MAGIC )}" )
280- val minorVersion = in.nextChar.toInt
281- val majorVersion = in.nextChar.toInt
282- if ((majorVersion < JAVA_MAJOR_VERSION ) ||
283- ((majorVersion == JAVA_MAJOR_VERSION ) &&
284- (minorVersion < JAVA_MINOR_VERSION )))
285- throw new IOException (
286- s " class file ' ${classfile}' has unknown version $majorVersion. $minorVersion, should be at least $JAVA_MAJOR_VERSION. $JAVA_MINOR_VERSION" )
310+ i """ class file ${classfile.canonicalPath} is broken $addendum,
311+ | reading aborted with ${e.getClass}:
312+ | ${Option (e.getMessage).getOrElse(" " )}""" )
287313 }
288314
289315 /** Return the class symbol of the given name. */
0 commit comments