'm writing to report a reproducible bug in JAGS 5.0.0-beta that triggers a
libstdc++ assertion failure when computing DIC with pD=TRUE.
LOGIC ERROR:
basic_string::compare: __pos (which is 4294967295) > this->size() (which is 8)
Please send a bug report to martyn_plummer@users.sourceforge.net
Any call path that invokes dic.samples() with type="pD" can trigger it.
Minimal R example:
library(rjags)
model <- jags.model(textConnection("model { x ~ dnorm(0,1) }"),
data = list(x = 0), n.chains = 2, quiet = TRUE)
update(model, 100)
dic.samples(model, n.iter = 100, type = "pD")
Also triggered via R2jags::jags(..., pD = TRUE).
The error is intermittent — it does not fire on every single run, but appears
reliably within a few attempts. Observed with 2 and 3 chains on the 8-schools
model and on trivial single-node models.
The bug is in src/modules/diag/DensityEnums.cc, in the functions isWeighted()
and isTotal(). Both use the std::string::compare(size_type pos, size_type n,
const char* s) overload with string::npos as an argument. Under GCC 13.3.0
(libstdc++, Ubuntu 24.04), the npos value propagates into the __pos parameter
inside this overload, triggering the out_of_range assertion:
isWeighted (line 56):
return stat.compare(0, 4, "loo_") == 0;
// If stat.length() < 4, undefined behaviour in some implementations.
isTotal (lines 65–69):
unsigned long pos = stat.find_last_of("_");
if (pos == string::npos) return false;
return stat.compare(pos, string::npos, "_total") == 0;
// string::npos as the length argument can leak into __pos.
The second one is the primary culprit. Even though find_last_of guards against
npos, the compare(pos, npos, "_total") call exhibits UB in some optimisation
levels where npos (the length argument) is mis-routed into the position parameter
of the internal implementation.
Replace both fragile compare calls with safer alternatives:
isTotal — use rfind for the full suffix instead of find_last_of + compare:
string::size_type pos = stat.rfind("_total");
return pos != string::npos && pos + 6 == stat.length();
isWeighted — add a length guard:
if (stat.length() < 4) return false;
return stat.compare(0, 4, "loo_") == 0;
I have tested this fix against JAGS 5.0.0-beta by recompiling the diag module
and running 10 rounds of pD=TRUE + dic.samples() on the 8-schools model — all
passed with no errors.
JAGS: 5.0.0-beta (source: JAGS-5.0.0-beta.tar.gz)
rjags: 5-2
R: 4.6.0
OS: Ubuntu 24.04.4 LTS (x86_64)
Compiler: GCC 13.3.0 (g++ -std=gnu++11)
libstdc++: 6.0.33
The fixed DensityEnums.cc is attached for reference.
Thank you for JAGS — it remains an indispensable tool.
Best regards,
Yu-Sung Su
Anonymous
Thanks Yu-Sung. I am currently unable to reproduce the bug. Can you please described the compilation options you used?
Dear Dr Plummer,
I have attached the beta version of R2jags. You should be able to
reproduce the error using the example in the jags.Rd file.
Thanks!
YS
On Tue, Jul 21, 2026 at 2:50 AM Martyn Plummer martyn_plummer@users.sourceforge.net wrote:
--
Yu-Sung Su
Related
Bug reports: #66
I'm sorry but I cannot reproduce this. I have both Ubuntu 24.4 and Fedora 44. I have run the 8 schools example and a minimal example through an instrumentalized version of JAGS 5.0.0 beta (CXX="g++ -fsanitize=undefined,address,bounds") and can find no issues.
This seems to be a compiler bug which I can't reproduce. I agree that this code could be strengthened so I have committed a fix which avoids the casual use of
string::nposand includes length checks:and
C++20 has
basic_string::starts_withandbasic_string::ends_withbut I would rather stick with C++11 for now.