Summary
On a full-project (“branch”) analysis of a large Java code base, the SonarCloud Java security/taint engine (JavaSecuritySensor, com.sonar.security.*) runs out of heap with java.lang.OutOfMemoryError: Java heap space while simulating UCFGs. The crash is an unbounded StringBuilder allocation inside the analyzer, not steady-state heap pressure:
- The engine’s own per-UCFG
memory usagegauge never exceeds ~14.4 GB. - A GC log of the same run shows the live set then grows monotonically to the 40 GB heap ceiling and the final
Full GC(with soft-reference clearing) reclaims 0 bytes - i.e. the 40 GB is all live/reachable, ~36 GB of it in humongous objects (>16 MB each). - Raising the scanner engine heap to
-Xmx40gon a 50 GB container still OOMs. More heap only postpones the crash.
The analyzer already detects that several sinks are too expensive (Too high simulation costs for sink ... This sink will not be analyzed any further) and skips them - but on a subsequent UCFG it still enters an unbounded allocation and dies. This looks like a missing/ineffective guard on a single sink’s cost computation.
Environment
| Item | Value |
|---|---|
| Platform | SonarQube Cloud (SonarCloud) |
| Project key | vendavo_eps-core |
| Analysis type | Full project analysis (not a PR) |
org.sonarqube Gradle plugin |
7.3.1.8318 |
| Scanner engine JRE | OpenJDK 21.0.9+10-LTS, G1GC |
| Scanner engine heap | -Xmx40g -Xms4g (via sonar.scanner.javaOpts) |
| Container memory | 50 GB |
Build JDK / sonar.java.source |
25 |
| Files indexed | 15 536 |
| Run timestamp (UTC) | 2026-06-23, sensor started 18:09:40, OOM 18:20:12 |
Plugin/version banners from the console:
17:46:06 org.sonarqube Gradle plugin 7.3.1.8318
17:46:09 Communicating with SonarQube Cloud
17:46:13 Starting SonarScanner Engine...
17:46:21 Project key: vendavo_eps-core
17:46:58 Configured Java source version (sonar.java.source): 25
Symptom / timeline (from scanner console)
18:09:40 Sensor JavaSecuritySensor [security]
18:09:40 29 taint analysis rules enabled.
18:09:59 Analyzing 99984 UCFGs to detect vulnerabilities.
18:11:30 All rules entry points : 1332
18:11:30 Retained UCFGs : 31677
18:11:33 0 / 31677 UCFGs simulated, memory usage: 7579 MB
...
18:14:09 13749 / 31677 UCFGs simulated, memory usage: 14401 MB <- peak the gauge ever reports
...
18:16:54 23117 / 31677 UCFGs simulated, memory usage: 5631 MB <- LAST progress line (~73%)
(no further progress line is ever printed)
18:20:12 java.lang.OutOfMemoryError: Java heap space
18:20:53 > Task :sonar FAILED
The engine stops emitting progress after UCFG 23117 / 31677. For the next ~3.5 minutes it is stuck inside the simulation of a single UCFG, allocating without bound, then OOMs.
“Too high simulation costs” sinks that were cut off before the crash (14 total)
Before the crash the engine prints, for 14 distinct sinks:
Too high simulation costs for sink in <redacted-source-NN>:<line>. This sink will not be analyzed any further.
(Source paths anonymized - see the sanitized excerpt.) These 14 sinks are all in DB / SQL / OMI / object-metadata / formatter / tracer code - deeply branched string building and SQL/metadata assembly. The cost guard fires for them, but does not prevent the OOM on a later sink.
OOM stack trace (top frames; analyzer classes are obfuscated)
java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOf(Unknown Source)
at java.base/java.lang.AbstractStringBuilder.ensureCapacityInternal(Unknown Source)
at java.base/java.lang.AbstractStringBuilder.append(Unknown Source)
at java.base/java.lang.StringBuilder.append(Unknown Source)
at com.sonar.security.J.D.C.J.F(na:116)
at com.sonar.security.J.D.C.J.H(na:2236)
at com.sonar.security.J.D.C.J.H(na:2757)
at com.sonar.security.J.D.E.c.C(na:1428)
at com.sonar.security.J.D.E.c$$Lambda.get(Unknown Source)
at com.sonar.security.J.D.E.c.A(na:2442)
at com.sonar.security.J.D.E.c.B(na:575)
at com.sonar.security.J.D.E.c.A(na:2183)
at com.sonar.security.J.D.B.N.A(na:3023)
at com.sonar.security.J.D.E.c.C(na:832)
... (recursion through com.sonar.security.J.D.E.c.A/B/C and J.D.B.*) ...
at com.sonar.security.J.D.I$_B.A(na:3103)
at com.sonar.security.J.D.I$_B.D(na:2096)
at com.sonar.security.J.D.I$_B.C(na:26)
The allocation that fails is StringBuilder.append → Arrays.copyOf directly under com.sonar.security.J.D.C.J.F - i.e. the engine is building one ever-growing String inside the security analyzer.
GC-log evidence (proves it is unbounded live growth, not a sizing problem)
Same run, scanner engine JRE OpenJDK 21.0.9, G1GC, -Xmx40g -Xms4g, 50 GB container.
Healthy phase (first ~30 min): after-GC live set ~4-7 GB, pre-GC peaks ~12-15 GB, GC fully effective. (This is the same ~14 GB the per-UCFG gauge reports - it is NOT the failure point.)
Runaway phase (~18:17:55 onward, ~2.5 min): the after-GC floor ratchets up and never recovers:
18:17:55 GC(440) Pause Young 40728M->12072M(40960M) (last effective collection)
18:18:44 GC(470) Pause Young 40047M->22271M(40960M)
18:19:30 GC(522) Pause Full 30757M->30752M(40960M) (reclaimed 5 MB)
18:19:37 GC(523) Pause Full 30752M->30751M(40960M) (reclaimed 1 MB, took 7.2 s)
18:19:54 GC(545) Pause Full 39103M->39103M(40960M) (reclaimed 0)
18:20:03 GC(559) Pause Full 40223M->40223M(40960M) (reclaimed 0)
18:20:11 GC(574) Pause Full 40319M->40319M(40960M) "maximal full compaction, clearing soft references" -> 0 freed
18:20:12 -> OutOfMemoryError
At JVM exit: heap 98.7% full (41 377 474 K / 41 943 040 K), and 1136 humongous regions x 32 MB = ~36 GB - i.e. ~36 GB sits in individual objects larger than 16 MB. The last GC that ran before the OOM was triggered by a G1 Humongous Allocation.
A Full GC + soft-reference clear reclaiming 0 bytes proves the entire 40 GB is live and reachable - not garbage waiting to be collected. This matches the stack trace: a single StringBuilder (a humongous char[]/byte[]) being grown without bound.
Why we believe this is an analyzer defect (and not our configuration)
- More heap does not fix it. We ran the engine at 25 GB, 30 GB and 40 GB on containers up to 50 GB - all OOM. The GC log shows 0 reclaimable memory at the ceiling, so the live set genuinely exceeds 40 GB and is still growing when it dies.
- The cost guard is incomplete. The engine identifies 14 sinks as
Too high simulation costsand skips them, yet still OOMs on a later UCFG via an unboundedStringBuilder.append. The guard that protects those 14 sinks does not protect the allocation incom.sonar.security.J.D.C.J.F. - The gauge hides it. The per-UCFG
memory usagegauge maxes at ~14 GB because it is sampled between UCFG simulations; the blow-up happens within a single simulation/cost computation, where no sample is taken. - PR analysis is fine. The same code base analyzed in PR mode (data-flow scoped to changed files) completes normally - it simulates a small fraction of the 31677 UCFGs and never reaches the pathological sink. The failure is specific to full-project scope.
What we’d like
- Confirmation of whether the unbounded
StringBuilderbuild undercom.sonar.security.J.D.C.J.F(string/path/state serialization during sink cost computation or reporting) can be bounded the same way theToo high simulation costscutoff bounds the 14 sinks above. - Guidance on any supported way to keep PR taint analysis while making the full-project taint analysis resilient (server-side cap, per-sink budget, etc.) - short of deactivating the taint rules in the quality profile, which would also disable the valuable PR analysis.
Attachments
gc-sonar.log- full G1 GC log of the failing run (heap trajectory + humongous region counts).console-sonar-excerpt.log- sanitized excerpt of the scanner console,JavaSecuritySensorphase only (sensor start18:09:40→> Task :sonar FAILED18:20:53): rule/UCFG counts, all 14Too high simulation costslines, the simulation progress with thememory usagegauge, and the full OOM stack. The 14 source file paths are anonymized; the full console is withheld for confidentiality. We can share the real sink paths/line numbers over a private support channel if needed, but they are ordinary DB/SQL/OMI/formatter code - the issue is the analyzer’s allocation behavior, not the source.
console-sonar-excerpt.log (6.2 KB)
gc-sonar.log (884.4 KB)