Skip to content

Commit 8c602b3

Browse files
authored
error when reading class file with unknown newer jdk version (#18618)
when reading a classfile causes a runtime exception, report the version of the classfile, and request that the user checks JDK compatibility. Considerations: - should we test this? currently we have no process for testing latest JDK, currently we still only test with JDK 16 and 8 fixes #18573
2 parents dc012c3 + 2183bf9 commit 8c602b3

File tree

2 files changed

+50
-32
lines changed

2 files changed

+50
-32
lines changed

compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,28 @@ import scala.compiletime.uninitialized
2929

3030
object 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. */

compiler/src/dotty/tools/dotc/core/classfile/ClassfileTastyUUIDParser.scala

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import dotty.tools.dotc.util._
1414
import dotty.tools.io.AbstractFile
1515
import dotty.tools.tasty.TastyReader
1616

17+
import ClassfileParser.Header
18+
1719
import java.io.IOException
1820
import java.lang.Integer.toHexString
1921
import java.util.UUID
@@ -23,33 +25,23 @@ class ClassfileTastyUUIDParser(classfile: AbstractFile)(ictx: Context) {
2325
import ClassfileConstants._
2426

2527
private var pool: ConstantPool = uninitialized // the classfile's constant pool
28+
private var classfileVersion: Header.Version = Header.Version.Unknown
2629

2730
def checkTastyUUID(tastyUUID: UUID)(using Context): Unit = try ctx.base.reusableDataReader.withInstance { reader =>
2831
implicit val reader2 = reader.reset(classfile)
29-
parseHeader()
32+
this.classfileVersion = ClassfileParser.parseHeader(classfile)
3033
this.pool = new ConstantPool
3134
checkTastyAttr(tastyUUID)
3235
this.pool = null
3336
}
3437
catch {
3538
case e: RuntimeException =>
3639
if (ctx.debug) e.printStackTrace()
40+
val addendum = Header.Version.brokenVersionAddendum(classfileVersion)
3741
throw new IOException(
38-
i"""class file ${classfile.canonicalPath} is broken, reading aborted with ${e.getClass}
39-
|${Option(e.getMessage).getOrElse("")}""")
40-
}
41-
42-
private def parseHeader()(using in: DataReader): Unit = {
43-
val magic = in.nextInt
44-
if (magic != JAVA_MAGIC)
45-
throw new IOException(s"class file '${classfile}' has wrong magic number 0x${toHexString(magic)}, should be 0x${toHexString(JAVA_MAGIC)}")
46-
val minorVersion = in.nextChar.toInt
47-
val majorVersion = in.nextChar.toInt
48-
if ((majorVersion < JAVA_MAJOR_VERSION) ||
49-
((majorVersion == JAVA_MAJOR_VERSION) &&
50-
(minorVersion < JAVA_MINOR_VERSION)))
51-
throw new IOException(
52-
s"class file '${classfile}' has unknown version $majorVersion.$minorVersion, should be at least $JAVA_MAJOR_VERSION.$JAVA_MINOR_VERSION")
42+
i""" class file ${classfile.canonicalPath} is broken$addendum,
43+
| reading aborted with ${e.getClass}:
44+
| ${Option(e.getMessage).getOrElse("")}""")
5345
}
5446

5547
private def checkTastyAttr(tastyUUID: UUID)(using ctx: Context, in: DataReader): Unit = {

0 commit comments

Comments
 (0)