Repository navigation
JaCoCo instrumentation of CCJSqlParser is approaching the constant-pool limit #2866
Description
Problem
On current master (c84ad0ebc), JaCoCo instrumentation of the generated CCJSqlParser.class leaves only 808 entries before the class-file limit of 65,535 constant-pool entries.
When the limit is exceeded, JaCoCo fails with ClassTooLargeException and loads the parser without instrumentation. Tests still pass, but JaCoCo counts the parser as uncovered. Then jacocoTestCoverageVerification fails, and with it ./gradlew check and the gradle_check CI job:
Caused by: org.jacoco.agent.rt.internal_29a6edd.asm.ClassTooLargeException: Class too large: net/sf/jsqlparser/parser/CCJSqlParser
Rule violated for bundle JSQLParser: instructions covered ratio is 0.21, but expected minimum is 0.50
This happened on 2026-10-23 while combining grammar changes from #2847–#2849. Moving helpers into nested classes avoided the limit, but the remaining capacity is still small. Current master passes.
Root cause
The generated parser contains 5,591 methods, including 3,515 jj_3* lookahead routines. JaCoCo adds probes to measure coverage, and probe IDs of 32,768 or more require an additional constant-pool entry each.
Measured on c84ad0ebc:
| Metric | Value |
|---|---|
| Constant pool before instrumentation | 46,304 |
| JaCoCo probes | 51,180 |
| Constant pool after instrumentation | 64,727 |
| Remaining entries | 808 |
Grammar additions can increase both the generated class size and the probe count.
- The remaining room went from 8,161 entries on 2026-10-14 to 65 on 2026-10-22.
- perf: bound single-token lookahead probes #2829 brought it back to 967, and it is already down to 808.
- A typical grammar PR uses 10–65 entries.
Proposed short-term fix
Exclude only CCJSqlParser.class from:
- JaCoCo agent instrumentation.
jacocoTestReport.jacocoTestCoverageVerification.
test {
jacoco {
- excludes = ['net/sf/jsqlparser/parser/CCJSqlParserTokenManager']
+ excludes = ['net/sf/jsqlparser/parser/CCJSqlParserTokenManager',
+ 'net/sf/jsqlparser/parser/CCJSqlParser']
}
}
jacocoTestReport {
...
+ afterEvaluate {
+ classDirectories.setFrom(files(classDirectories.files.collect {
+ fileTree(dir: it, exclude: [
+ "**/parser/CCJSqlParserTokenManager.class",
+ "**/parser/CCJSqlParser.class"
+ ])
+ }))
+ }
}
jacocoTestCoverageVerification {
// Jacoco can't handle the TokenManager class
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: [
- "**CCJSqlParserTokenManager**"
+ "**/parser/CCJSqlParserTokenManager.class",
+ "**/parser/CCJSqlParser.class"
])
}))
}The exact class exclusion keeps nested parser helpers, such as CCJSqlParser$ObjectNames, instrumented.
I also found that the existing CCJSqlParserTokenManager exclusion does not work in coverage verification: its **CCJSqlParserTokenManager** pattern does not match the class-file path. The report also currently includes the uninstrumented TokenManager. The proposed change fixes both exclusions.
Verified on current master with all 12,262 tests:
| Coverage metric | Current | Proposed |
|---|---|---|
| Instruction coverage | 75.37% | 82.74% |
| Missed lines | 14,892 | 7,336 |
| Tests | Pass | Pass |
These numbers change because two classes leave the calculation, not because coverage improves:
- The uninstrumented TokenManager (0%, 815 lines) is no longer counted.
- The parser (82.19%, 6,741 missed lines) is no longer counted.
Both configurations pass the existing coverage thresholds.
Trade-off: We lose direct coverage measurements for the generated parser, including its grammar branches. However, all parser tests still run, and other classes, including nested helpers, remain covered. If the parser is split later (see below), the exclusion can be reverted.
Longer-term solution: split the generated parser
Excluding the parser from JaCoCo addresses the immediate issue. The parser itself, however, already uses 46,304 constant-pool entries without instrumentation, and javac fails with "too many constants" beyond 65,535. For comparison, it used 12,266 in 5.3 and 34,231 in 5.4.
Two contributors to its size are:
- Lookahead routines: 3,515
jj_3*methods generated into one class. - Grammar locations: since 5.4,
jj_consume_tokenandjj_scan_tokencalls pass grammar locations such as"18424:7", which are used in parse error messages. There are 7,353 distinct location strings, taking about 14,700 constant-pool entries (32% of the pool).
I tested a small proof of concept that moves the jj_3* routines into a non-static inner class while preserving their method bodies.
| Metric | Master | PoC |
|---|---|---|
| Parser constant pool (uninstrumented) | 46,304 | 37,834 |
| Remaining entries after JaCoCo | 808 | 23,451 |
| Tests | 12,262 passed | 12,262 passed |
The new inner class uses 17,060 entries and has 48,454 left after instrumentation. The parser remains instrumented, and preliminary performance measurements showed no clear difference (not a JMH benchmark).
This would require a change in the JavaCC 8 generator (manticore-projects/javacc-8-java) rather than JSQLParser's build configuration. It would substantially increase headroom, but it does not stop growth: applied to the 2026-10-14 parser, the parser class still used 5,637 entries over the following two weeks. Continued grammar growth and the location strings would still need attention.
If this direction is interesting, I can open a separate issue in manticore-projects/javacc-8-java with the PoC and measurements.
Additional notes
- Maven:
mvn verifydoes not enforce JaCoCo coverage thresholds, but its agent can encounter the same instrumentation failure. Consistent exclusions would require updating bothprepare-agentandreport. - Verification rules: The existing rule-level
excludeshave no effect, because the rules use the defaultBUNDLEelement and the lists are matched against the bundle name. The proposedclassDirectoriesexclusions remove only the two parser classes; the rule-level lists are left unchanged. - Measurement: The remaining capacity was measured using JaCoCo's
Instrumenter. JaCoCo 0.8.14 and 0.8.15 produced the same result.
Measurement code
import java.nio.file.Files;
import java.nio.file.Path;
import org.jacoco.core.instr.Instrumenter;
import org.jacoco.core.runtime.OfflineInstrumentationAccessGenerator;
public class JacocoHeadroom {
public static void main(String[] args) throws Exception {
byte[] original = Files.readAllBytes(Path.of(args[0]));
byte[] instrumented = new Instrumenter(new OfflineInstrumentationAccessGenerator())
.instrument(original, "CCJSqlParser");
int count = ((instrumented[8] & 0xff) << 8) | (instrumented[9] & 0xff);
System.out.println("constant_pool_count=" + count + " left=" + (65535 - count));
}
}The agent's runtime access code differs slightly from the offline one, so treat the number as approximate.
I'm happy to prepare a PR for the short-term fix if this approach is acceptable.
AI assistance: prepared with Claude Code.
Activity
manticore-projects commentedon Oct 28, 2026
Thank you much, this makes a lot of sense. Please provide a PR for merging.
manticore-projects commentedon Oct 28, 2026
Yes please, we did that in the past already and JavaCC.org is not responding anymore.
minleejae commentedon Oct 28, 2026
@manticore-projects Thanks! I'll open a PR for the short-term exclusion.
For the generator change, issues are disabled on
manticore-projects/javacc-8-java, so I'll send it there directly as a PR with the PoC and measurements.A side question, mostly out of curiosity: since JavaCC.org is no longer responding and JSQLParser's build depends on the
javacc-8-javafork, have you considered hosting the fork under the JSQLParser organization? It might make it easier for JSQLParser contributors to find and contribute to. I understand it may also serve your other projects, so feel free to ignore this if it doesn't fit.build: exclude the generated CCJSqlParser from JaCoCo (#2867)