Skip to content

fix: handle post-Java 8 constant pool entries in LineNumbers - #3465

Open
pjfanning wants to merge 1 commit into
apache:mainfrom
pjfanning:linenumbers-condy
Open

fix: handle post-Java 8 constant pool entries in LineNumbers#3465
pjfanning wants to merge 1 commit into
apache:mainfrom
pjfanning:linenumbers-condy

Conversation

@pjfanning

Copy link
Copy Markdown
Member

Motivation

LineNumbers parses class files to recover the source file and line numbers used in actor and lambda error reporting. Its constant pool reader (LineNumbers.scala:122-168) covers the entry kinds present in class file format 52.0 (Java 8), but not the ones added since:

tag entry since
17 CONSTANT_Dynamic format 55.0 (Java 11)
19 CONSTANT_Module format 53.0 (Java 9)
20 CONSTANT_Package format 53.0 (Java 9)

Entry sizes in the constant pool are tag dependent, so an unrecognised tag makes every later entry unreadable. The match also had no default case, so this surfaced as a MatchError, which getInfo catches at line 200 and converts into UnknownSourceFormat. Source information therefore degraded silently:

UnknownSourceFormat(parse error: 17 (of class java.lang.Byte))

The stale class comment was the giveaway — it still read "works for all normal classes up to format 52:0 (JDK8)".

Modification

Add the three missing tags with their correct sizes (tag 17 is two shorts and one pool slot; 19 and 20 are one short and one slot), plus a default case that throws a described UnsupportedOperationException rather than a MatchError. Correct the class comment.

Scope — please read before weighing this

I want to be straight about how much this matters in practice. Neither scalac nor javac emits CONSTANT_Dynamic for the code in this repository today. I scanned 16,414 compiled classes — 5,518 from the Scala 2.13 build and 10,896 from Scala 3 — and found zero containing tag 17.

So this is a latent robustness fix for user class files, not a fix for a failure observed in Pekko. It becomes reachable when LineNumbers is pointed at a class compiled from another source (javac in configurations that emit condy, a future scalac, or another JVM language). The missing default case is the part I would argue for regardless: an unknown future tag should not reach users as a MatchError.

Result

Same fixture, before and after:

result
before UnknownSourceFormat(parse error: 17 (of class java.lang.Byte))
after CondyGen.java:4-7

Tests

New LineNumbersConstantPoolSpec in actor-tests. Since no compiler available here emits these entries, the test takes a real class file and splices the entry into its constant pool, then parses it. No binary fixture is checked in — the class file is read from the classpath at runtime and modified in memory.

Four tests: a baseline that the unmodified class still parses (so a failure below is attributable to the new entry), one for CONSTANT_Dynamic, one for CONSTANT_Module/CONSTANT_Package, and one asserting an unknown tag still yields UnknownSourceFormat rather than propagating.

Verified the tests bite: reverting only the production change fails the tag 17 and tag 19/20 tests, while the baseline and unknown-tag tests still pass.

  • actor-tests/testOnly org.apache.pekko.util.LineNumbersConstantPoolSpec org.apache.pekko.util.LineNumberSpec — 11 tests, all pass
  • sbt actor/mimaReportBinaryIssues — clean; LineNumbers internals are private, no signature changes
  • scalafmt run on both modules; header added by sbt headerCreateAll

The spec is placed in src/test/scala rather than the scala-2 / scala-3 variants, since it asserts nothing version specific.

References

Found during a sweep for patterns left over from the Java 8 / Scala 2.12 baseline. Most of that sweep came back clean — sun.misc.Unsafe is already on VarHandle, JavaConverters is fully migrated, onSpinWait and Files/readAllBytes are adopted — so this is the one item from it with real substance.

`LineNumbers` parses class files to recover source file and line number
information for actor and lambda error reporting. Its constant pool
reader handles the entry kinds that existed in class file format 52.0
(Java 8) but not the ones added since:

  - CONSTANT_Dynamic (tag 17, format 55.0 / Java 11)
  - CONSTANT_Module and CONSTANT_Package (tags 19 and 20, format 53.0)

Entry sizes in the constant pool are tag dependent, so an unrecognised
tag makes every subsequent entry unreadable. The match had no default
case either, so this surfaced as a MatchError, which `getInfo` catches
and turns into `UnknownSourceFormat`. The result was source information
silently degrading to "parse error: 17" rather than failing loudly.

Add the three missing tags and a default case that throws a described
exception instead of a MatchError, and correct the class comment, which
still claimed support only up to format 52.0.

Note that neither scalac nor javac emits CONSTANT_Dynamic for the code
in this repository today: scanning 16414 compiled classes across the
Scala 2.13 and Scala 3 builds found none. This is a latent robustness
fix for user class files rather than one for a failure seen in Pekko
itself.

The test splices each entry kind into the constant pool of a real class
file, since no compiler here produces them. It fails on the unfixed
parser for tags 17, 19 and 20, and covers that an unknown tag still
yields an UnknownSourceFormat result rather than propagating.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant