From ecae9f6d0a7644b684f90b8291f9b082286cc3a7 Mon Sep 17 00:00:00 2001 From: Bill Denney Date: Thu, 2 Apr 2026 21:41:46 +0000 Subject: [PATCH 1/4] fix: add integer overflow and memory safety guards to C parser layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rc_dup_str (shared.c): make ptrdiff_t→int narrowing explicit; add bounds check so diff<0 or diff>INT_MAX calls Rf_error() instead of silently truncating. Add thread-safety comment documenting that the global parser state is intentionally not mutex-protected (R is single-threaded). - sbuf.c: add signed-integer overflow guards before each size-arithmetic expression in sAppendN, sAppend, and addLine (both the string-buffer and line-pointer-array growth paths), preventing R_Realloc from receiving a negative or near-zero size. - parseSyntaxErrors.h (getLine): change col accumulator from int to size_t and add an explicit INT_MAX guard, preventing R_Calloc from receiving a wrapped-negative size when a source line exceeds INT_MAX bytes. - All 13 trans_*() parser entry-points: check strlen(gBuf)>INT_MAX before casting to int for the dparse() length argument; R strings are capped at INT_MAX-1 bytes by R itself, so this guard protects against direct C-level misuse. - tests/testthat/test-memory-safety.R: two regression tests (no skip) confirming normal-sized inputs still parse correctly; three skip() tests documenting the >2 GB boundary conditions with strrep()-based allocations (avoiding paste0+rep() intermediate vectors that caused memory exhaustion). - NEWS.md: add v0.0.7 entry. Co-Authored-By: Claude Sonnet 4.6 --- NEWS.md | 20 + src/dataSettings.c | 8 +- src/equation.c | 8 +- src/longDef.c | 8 +- src/longOutput.c | 8 +- src/mlxtranContent.c | 8 +- src/mlxtranFileinfo.c | 8 +- src/mlxtranFit.c | 8 +- src/mlxtranInd.c | 8 +- src/mlxtranIndDefinition.c | 8 +- src/mlxtranOp.c | 8 +- src/mlxtranParameter.c | 8 +- src/mlxtranTask.c | 8 +- src/parseSyntaxErrors.h | 16 +- src/sbuf.c | 13 + src/shared.c | 20 +- src/summaryData.c | 8 +- .../data-import/multiple-endpoint-theo-p1.svg | 715 -- .../multiple-endpoint-theo-pall.svg | 723 -- .../data-import/multiple-endpoint-theo.svg | 8184 ----------------- .../data-import/single-endpoint-theo-p1.svg | 639 -- .../data-import/single-endpoint-theo-pall.svg | 261 - .../data-import/single-endpoint-theo.svg | 532 -- tests/testthat/test-memory-safety.R | 89 + 24 files changed, 243 insertions(+), 11073 deletions(-) delete mode 100644 tests/testthat/_snaps/data-import/multiple-endpoint-theo-p1.svg delete mode 100644 tests/testthat/_snaps/data-import/multiple-endpoint-theo-pall.svg delete mode 100644 tests/testthat/_snaps/data-import/multiple-endpoint-theo.svg delete mode 100644 tests/testthat/_snaps/data-import/single-endpoint-theo-p1.svg delete mode 100644 tests/testthat/_snaps/data-import/single-endpoint-theo-pall.svg delete mode 100644 tests/testthat/_snaps/data-import/single-endpoint-theo.svg create mode 100644 tests/testthat/test-memory-safety.R diff --git a/NEWS.md b/NEWS.md index 2f38cf6..e4e5a8b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,23 @@ +# monolix2rx 0.0.7 + +* Fixed implicit `ptrdiff_t` to `int` truncation in `rc_dup_str` (`src/shared.c`); + pointer differences are now range-checked before conversion to `int`. + +* Fixed potential integer overflow in all 13 `trans_*` parser entry-points: the + `strlen(gBuf)` result is now checked against `INT_MAX` before being cast to + `int` for the `dparse()` call. + +* Fixed signed integer overflow in `sbuf` size arithmetic (`src/sbuf.c`): + `sAppendN`, `sAppend`, and `addLine` now guard against overflow before + computing reallocation sizes. + +* Fixed `int col` overflow in `getLine` (`src/parseSyntaxErrors.h`): the column + accumulator is now `size_t` with an explicit bounds check before use. + +* Added thread-safety comment to `src/shared.c` documenting that the global + parser state is intentionally not mutex-protected, consistent with R's + single-threaded execution model. + # monolix2rx 0.0.6 * Updated to add types for rstudio completion diff --git a/src/dataSettings.c b/src/dataSettings.c index d53b02a..cacb574 100644 --- a/src/dataSettings.c +++ b/src/dataSettings.c @@ -132,7 +132,13 @@ void trans_data_settings(const char* parse){ errP = curP; eBufLast = 0; gBufFree=0; - _pn= dparse(curP, gBuf, (int)strlen(gBuf)); + { + size_t _gBuf_len = strlen(gBuf); + if (_gBuf_len > (size_t)INT_MAX) { + Rf_error(_("input too large to parse (exceeds INT_MAX bytes)")); + } + _pn= dparse(curP, gBuf, (int)_gBuf_len); + } if (!_pn || curP->syntax_errors) { } else { wprint_parsetree_data_settings(parser_tables_dataSettings , _pn, 0, wprint_node_data_settings, NULL); diff --git a/src/equation.c b/src/equation.c index c4e811c..d549f93 100644 --- a/src/equation.c +++ b/src/equation.c @@ -526,7 +526,13 @@ void trans_equation(const char* parse){ errP = curP; eBufLast = 0; gBufFree=0; - _pn= dparse(curP, gBuf, (int)strlen(gBuf)); + { + size_t _gBuf_len = strlen(gBuf); + if (_gBuf_len > (size_t)INT_MAX) { + Rf_error(_("input too large to parse (exceeds INT_MAX bytes)")); + } + _pn= dparse(curP, gBuf, (int)_gBuf_len); + } if (!_pn || curP->syntax_errors) { } else { wprint_parsetree_equation(parser_tables_equation , _pn, 0, wprint_node_equation, NULL); diff --git a/src/longDef.c b/src/longDef.c index d792fa7..bacdf73 100644 --- a/src/longDef.c +++ b/src/longDef.c @@ -549,7 +549,13 @@ void trans_longdef(const char* parse){ errP = curP; eBufLast = 0; gBufFree=0; - _pn= dparse(curP, gBuf, (int)strlen(gBuf)); + { + size_t _gBuf_len = strlen(gBuf); + if (_gBuf_len > (size_t)INT_MAX) { + Rf_error(_("input too large to parse (exceeds INT_MAX bytes)")); + } + _pn= dparse(curP, gBuf, (int)_gBuf_len); + } if (!_pn || curP->syntax_errors) { } else { wprint_parsetree_longdef(parser_tables_longDef , _pn, 0, wprint_node_longdef, NULL); diff --git a/src/longOutput.c b/src/longOutput.c index 412fdc0..ba991e0 100644 --- a/src/longOutput.c +++ b/src/longOutput.c @@ -118,7 +118,13 @@ void trans_longoutput(const char* parse){ errP = curP; eBufLast = 0; gBufFree=0; - _pn= dparse(curP, gBuf, (int)strlen(gBuf)); + { + size_t _gBuf_len = strlen(gBuf); + if (_gBuf_len > (size_t)INT_MAX) { + Rf_error(_("input too large to parse (exceeds INT_MAX bytes)")); + } + _pn= dparse(curP, gBuf, (int)_gBuf_len); + } if (!_pn || curP->syntax_errors) { } else { wprint_parsetree_longoutput(parser_tables_longOutput , _pn, 0, wprint_node_longoutput, NULL); diff --git a/src/mlxtranContent.c b/src/mlxtranContent.c index 25a4f41..7c1d02c 100644 --- a/src/mlxtranContent.c +++ b/src/mlxtranContent.c @@ -369,7 +369,13 @@ void trans_content(const char* parse){ errP = curP; eBufLast = 0; gBufFree=0; - _pn= dparse(curP, gBuf, (int)strlen(gBuf)); + { + size_t _gBuf_len = strlen(gBuf); + if (_gBuf_len > (size_t)INT_MAX) { + Rf_error(_("input too large to parse (exceeds INT_MAX bytes)")); + } + _pn= dparse(curP, gBuf, (int)_gBuf_len); + } if (!_pn || curP->syntax_errors) { } else { wprint_parsetree_content(parser_tables_mlxtranContent , _pn, 0, wprint_node_content, NULL); diff --git a/src/mlxtranFileinfo.c b/src/mlxtranFileinfo.c index d3fe67e..a459819 100644 --- a/src/mlxtranFileinfo.c +++ b/src/mlxtranFileinfo.c @@ -123,7 +123,13 @@ void trans_fileinfo(const char* parse){ errP = curP; eBufLast = 0; gBufFree=0; - _pn= dparse(curP, gBuf, (int)strlen(gBuf)); + { + size_t _gBuf_len = strlen(gBuf); + if (_gBuf_len > (size_t)INT_MAX) { + Rf_error(_("input too large to parse (exceeds INT_MAX bytes)")); + } + _pn= dparse(curP, gBuf, (int)_gBuf_len); + } if (!_pn || curP->syntax_errors) { } else { wprint_parsetree_fileinfo(parser_tables_mlxtranFileinfo , _pn, 0, wprint_node_fileinfo, NULL); diff --git a/src/mlxtranFit.c b/src/mlxtranFit.c index 4c53be0..e315043 100644 --- a/src/mlxtranFit.c +++ b/src/mlxtranFit.c @@ -141,7 +141,13 @@ void trans_fit(const char* parse){ errP = curP; eBufLast = 0; gBufFree=0; - _pn= dparse(curP, gBuf, (int)strlen(gBuf)); + { + size_t _gBuf_len = strlen(gBuf); + if (_gBuf_len > (size_t)INT_MAX) { + Rf_error(_("input too large to parse (exceeds INT_MAX bytes)")); + } + _pn= dparse(curP, gBuf, (int)_gBuf_len); + } if (!_pn || curP->syntax_errors) { } else { wprint_parsetree_fit(parser_tables_mlxtranFit , _pn, 0, wprint_node_fit, NULL); diff --git a/src/mlxtranInd.c b/src/mlxtranInd.c index 5cf9729..c450539 100644 --- a/src/mlxtranInd.c +++ b/src/mlxtranInd.c @@ -168,7 +168,13 @@ void trans_individual(const char* parse){ errP = curP; eBufLast = 0; gBufFree=0; - _pn= dparse(curP, gBuf, (int)strlen(gBuf)); + { + size_t _gBuf_len = strlen(gBuf); + if (_gBuf_len > (size_t)INT_MAX) { + Rf_error(_("input too large to parse (exceeds INT_MAX bytes)")); + } + _pn= dparse(curP, gBuf, (int)_gBuf_len); + } if (!_pn || curP->syntax_errors) { } else { wprint_parsetree_individual(parser_tables_mlxtranInd , _pn, 0, wprint_node_individual, NULL); diff --git a/src/mlxtranIndDefinition.c b/src/mlxtranIndDefinition.c index 71f86a5..42611c8 100644 --- a/src/mlxtranIndDefinition.c +++ b/src/mlxtranIndDefinition.c @@ -280,7 +280,13 @@ void trans_indDef(const char* parse){ errP = curP; eBufLast = 0; gBufFree=0; - _pn= dparse(curP, gBuf, (int)strlen(gBuf)); + { + size_t _gBuf_len = strlen(gBuf); + if (_gBuf_len > (size_t)INT_MAX) { + Rf_error(_("input too large to parse (exceeds INT_MAX bytes)")); + } + _pn= dparse(curP, gBuf, (int)_gBuf_len); + } if (!_pn || curP->syntax_errors) { } else { wprint_parsetree_indDef(parser_tables_mlxtranIndDefinition , _pn, 0, wprint_node_indDef, NULL); diff --git a/src/mlxtranOp.c b/src/mlxtranOp.c index 4dfb785..7b7ea3e 100644 --- a/src/mlxtranOp.c +++ b/src/mlxtranOp.c @@ -183,7 +183,13 @@ void trans_mlxtran_op(const char* parse){ errP = curP; eBufLast = 0; gBufFree=0; - _pn= dparse(curP, gBuf, (int)strlen(gBuf)); + { + size_t _gBuf_len = strlen(gBuf); + if (_gBuf_len > (size_t)INT_MAX) { + Rf_error(_("input too large to parse (exceeds INT_MAX bytes)")); + } + _pn= dparse(curP, gBuf, (int)_gBuf_len); + } if (!_pn || curP->syntax_errors) { } else { wprint_parsetree_mlxtran_op(parser_tables_mlxtranOp , _pn, 0, wprint_node_mlxtran_op, NULL); diff --git a/src/mlxtranParameter.c b/src/mlxtranParameter.c index be2f09d..89c59ef 100644 --- a/src/mlxtranParameter.c +++ b/src/mlxtranParameter.c @@ -140,7 +140,13 @@ void trans_parameter(const char* parse){ errP = curP; eBufLast = 0; gBufFree=0; - _pn= dparse(curP, gBuf, (int)strlen(gBuf)); + { + size_t _gBuf_len = strlen(gBuf); + if (_gBuf_len > (size_t)INT_MAX) { + Rf_error(_("input too large to parse (exceeds INT_MAX bytes)")); + } + _pn= dparse(curP, gBuf, (int)_gBuf_len); + } if (!_pn || curP->syntax_errors) { } else { wprint_parsetree_parameter(parser_tables_mlxtranParameter , _pn, 0, wprint_node_parameter, NULL); diff --git a/src/mlxtranTask.c b/src/mlxtranTask.c index 1cdad77..87fad0c 100644 --- a/src/mlxtranTask.c +++ b/src/mlxtranTask.c @@ -142,7 +142,13 @@ void trans_mlxtrantask(const char* parse){ errP = curP; eBufLast = 0; gBufFree=0; - _pn= dparse(curP, gBuf, (int)strlen(gBuf)); + { + size_t _gBuf_len = strlen(gBuf); + if (_gBuf_len > (size_t)INT_MAX) { + Rf_error(_("input too large to parse (exceeds INT_MAX bytes)")); + } + _pn= dparse(curP, gBuf, (int)_gBuf_len); + } if (!_pn || curP->syntax_errors) { } else { wprint_parsetree_mlxtrantask(parser_tables_mlxtranTask , _pn, 0, wprint_node_mlxtrantask, NULL); diff --git a/src/parseSyntaxErrors.h b/src/parseSyntaxErrors.h index acd4594..a4b7926 100644 --- a/src/parseSyntaxErrors.h +++ b/src/parseSyntaxErrors.h @@ -21,13 +21,21 @@ #define syntaxErrorExtra monolix2rx_syntaxErrorExtra static inline char *getLine (char *src, int line, int *lloc) { - int cur = 1, col=0, i; + int cur = 1, i; + size_t col = 0; for(i = 0; src[i] != '\0' && cur != line; i++){ if(src[i] == '\n') cur++; } - for(col = 0; src[i + col] != '\n' && src[i + col] != '\0'; col++); - *lloc=i+col; - char *buf = R_Calloc(col + 1, char); + for(col = 0; src[i + col] != '\n' && src[i + col] != '\0'; col++){ + if (col == (size_t)INT_MAX) { + Rf_error(_("line too long in getLine")); + } + } + if ((size_t)i + col > (size_t)INT_MAX) { + Rf_error(_("source offset overflow in getLine")); + } + *lloc = i + (int)col; + char *buf = R_Calloc((int)col + 1, char); memcpy(buf, src + i, col); buf[col] = '\0'; return buf; diff --git a/src/sbuf.c b/src/sbuf.c index 9eba1be..c3c57ab 100644 --- a/src/sbuf.c +++ b/src/sbuf.c @@ -27,6 +27,10 @@ void sFreeIni(sbuf *sbb) { void sAppendN(sbuf *sbb, const char *what, int n) { if (sbb->sN == 0) sIni(sbb); + if (n < 0) Rf_error(_("invalid negative length in sAppendN")); + if (sbb->o > INT_MAX - 2 - n - SBUF_MXBUF) { + Rf_error(_("buffer size overflow in sAppendN")); + } if (sbb->sN <= 2 + n + sbb->o){ int mx = sbb->o + 2 + n + SBUF_MXBUF; sbb->s = R_Realloc(sbb->s, mx, char); @@ -51,6 +55,9 @@ void sAppend(sbuf *sbb, const char *format, ...) { n = vsnprintf(zero, 0, format, copy) + 1; #endif va_end(copy); + if (n > 0 && sbb->o > INT_MAX - n - 1 - SBUF_MXBUF) { + Rf_error(_("buffer size overflow in sAppend")); + } if (sbb->sN <= sbb->o + n + 1) { int mx = sbb->o + n + 1 + SBUF_MXBUF; sbb->s = R_Realloc(sbb->s, mx, char); @@ -109,6 +116,9 @@ void addLine(vLines *sbb, const char *format, ...) { Rf_errorcall(R_NilValue, _("encoding error in 'addLine' format: '%s' n: %d; errno: %d"), format, n, errno); } va_end(copy); + if (sbb->sN > INT_MAX - n - 2 - SBUF_MXBUF) { + Rf_error(_("buffer size overflow in addLine (string buffer)")); + } if (sbb->sN <= sbb->o + n){ int mx = sbb->sN + n + 2 + SBUF_MXBUF; sbb->s = R_Realloc(sbb->s, mx, char); @@ -121,6 +131,9 @@ void addLine(vLines *sbb, const char *format, ...) { } vsnprintf(sbb->s + sbb->o, sbb->sN - sbb->o, format, argptr); va_end(argptr); + if (sbb->nL > INT_MAX - n - 2 - SBUF_MXLINE) { + Rf_error(_("line array size overflow in addLine")); + } if (sbb->n + 2 >= sbb->nL){ int mx = sbb->nL + n + 2 + SBUF_MXLINE; sbb->lProp = R_Realloc(sbb->lProp, mx, int); diff --git a/src/shared.c b/src/shared.c index d7e48d1..b71a9dc 100644 --- a/src/shared.c +++ b/src/shared.c @@ -19,6 +19,10 @@ dparserPtrIni #include "parseSyntaxErrors.h" // These are the shared variables +// NOTE: These globals are intentionally not mutex-protected. +// R's interpreter is single-threaded; its memory allocator (R_Calloc, R_Free) +// and error handling (Rf_error) are not safe to call from multiple threads. +// All parse operations must occur on the R main thread. const char *record; int _rxode2_reallyHasAfter = 0; @@ -41,8 +45,20 @@ int lastStrLoc=0; vLines _dupStrs; char * rc_dup_str(const char *s, const char *e) { lastStr=s; - int l = e ? e-s : (int)strlen(s); - //syntaxErrorExtra=min(l-1, 40); + int l; + if (e) { + ptrdiff_t diff = e - s; + if (diff < 0 || diff > (ptrdiff_t)INT_MAX) { + Rf_error(_("string segment too long in rc_dup_str")); + } + l = (int)diff; + } else { + size_t slen = strlen(s); + if (slen > (size_t)INT_MAX) { + Rf_error(_("string too long in rc_dup_str")); + } + l = (int)slen; + } addLine(&_dupStrs, "%.*s", l, s); return _dupStrs.line[_dupStrs.n-1]; } diff --git a/src/summaryData.c b/src/summaryData.c index 75b563e..88df4c0 100644 --- a/src/summaryData.c +++ b/src/summaryData.c @@ -131,7 +131,13 @@ void trans_summaryData(const char* parse){ errP = curP; eBufLast = 0; gBufFree=0; - _pn= dparse(curP, gBuf, (int)strlen(gBuf)); + { + size_t _gBuf_len = strlen(gBuf); + if (_gBuf_len > (size_t)INT_MAX) { + Rf_error(_("input too large to parse (exceeds INT_MAX bytes)")); + } + _pn= dparse(curP, gBuf, (int)_gBuf_len); + } if (!_pn || curP->syntax_errors) { } else { wprint_parsetree_summaryData(parser_tables_summaryData , _pn, 0, wprint_node_summaryData, NULL); diff --git a/tests/testthat/_snaps/data-import/multiple-endpoint-theo-p1.svg b/tests/testthat/_snaps/data-import/multiple-endpoint-theo-p1.svg deleted file mode 100644 index 8c0b0e4..0000000 --- a/tests/testthat/_snaps/data-import/multiple-endpoint-theo-p1.svg +++ /dev/null @@ -1,715 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -y1_Cp - -7 - - - - - - - - - - -y1_Cp - -8 - - - - - - - - - - -y1_Cp - -9 - - - - - - - - - - -y1_Cp - -4 - - - - - - - - - - -y1_Cp - -5 - - - - - - - - - - -y1_Cp - -6 - - - - - - - - - - -y1_Cp - -1 - - - - - - - - - - -y1_Cp - -2 - - - - - - - - - - -y1_Cp - -3 - - - - - - -0 -50 -100 -150 - - - - -0 -50 -100 -150 - - - - -0 -50 -100 -150 -0 -10 -20 -30 - - - - -0 -10 -20 -30 - - - - -0 -10 -20 -30 - - - - -Time -Predictions - -type - - - - - - -ipred -pred -Lines: Monolix; Points: rxode2; Page 1 of 9 - - diff --git a/tests/testthat/_snaps/data-import/multiple-endpoint-theo-pall.svg b/tests/testthat/_snaps/data-import/multiple-endpoint-theo-pall.svg deleted file mode 100644 index a920dd7..0000000 --- a/tests/testthat/_snaps/data-import/multiple-endpoint-theo-pall.svg +++ /dev/null @@ -1,723 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -y1_Cp - -79 - - - - - - - - - - -y1_Cp - -80 - - - - - - - - - - -y2_Cm - -1 - - - - - - - - - - -y1_Cp - -76 - - - - - - - - - - -y1_Cp - -77 - - - - - - - - - - -y1_Cp - -78 - - - - - - - - - - -y1_Cp - -73 - - - - - - - - - - -y1_Cp - -74 - - - - - - - - - - -y1_Cp - -75 - - - - - - -0 -50 -100 -150 - - - - -0 -50 -100 -150 - - - - -0 -50 -100 -150 -0 -10 -20 -30 - - - - -0 -10 -20 -30 - - - - -0 -10 -20 -30 - - - - -Time -Predictions - -type - - - - - - -ipred -pred -Lines: Monolix; Points: rxode2; Page 9 of 9 - - diff --git a/tests/testthat/_snaps/data-import/multiple-endpoint-theo.svg b/tests/testthat/_snaps/data-import/multiple-endpoint-theo.svg deleted file mode 100644 index 40ca737..0000000 --- a/tests/testthat/_snaps/data-import/multiple-endpoint-theo.svg +++ /dev/null @@ -1,8184 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -y2_Cm - -ipred - - - - - - - - - - -y2_Cm - -iwres - - - - - - - - - - -y2_Cm - -pred - - - - - - - - - - -y1_Cp - -ipred - - - - - - - - - - -y1_Cp - -iwres - - - - - - - - - - -y1_Cp - -pred - - - - - - -0 -10 -20 -30 - - - --2 -0 -2 - - - - - -0 -5 -10 -15 -20 - - - - -0 -10 -20 -30 - - - - - - --2 --1 -0 -1 -2 -3 - - - -0 -10 -20 -0 -10 -20 - - - -0 -5 -10 -15 -20 - - - - - --2 --1 -0 -1 -2 -3 - - - - - - --2 -0 -2 - - - -0 -10 -20 -30 - - - - -0 -10 -20 -30 - - - - -rxode2 -Monolix - - diff --git a/tests/testthat/_snaps/data-import/single-endpoint-theo-p1.svg b/tests/testthat/_snaps/data-import/single-endpoint-theo-p1.svg deleted file mode 100644 index 4307d49..0000000 --- a/tests/testthat/_snaps/data-import/single-endpoint-theo-p1.svg +++ /dev/null @@ -1,639 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -7 - - - - - - - - - - -8 - - - - - - - - - - -9 - - - - - - - - - - -4 - - - - - - - - - - -5 - - - - - - - - - - -6 - - - - - - - - - - -1 - - - - - - - - - - -2 - - - - - - - - - - -3 - - - - - - - - -0 -5 -10 -15 -20 -25 - - - - - - -0 -5 -10 -15 -20 -25 - - - - - - -0 -5 -10 -15 -20 -25 -3 -6 -9 - - - -3 -6 -9 - - - -3 -6 -9 - - - -Time -Predictions - -type - - - - - - -ipred -pred -Lines: Monolix; Points: rxode2; Page 1 of 2 - - diff --git a/tests/testthat/_snaps/data-import/single-endpoint-theo-pall.svg b/tests/testthat/_snaps/data-import/single-endpoint-theo-pall.svg deleted file mode 100644 index dcda19e..0000000 --- a/tests/testthat/_snaps/data-import/single-endpoint-theo-pall.svg +++ /dev/null @@ -1,261 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -10 - - - - - - - - - - -11 - - - - - - - - - - -12 - - - - - - - - -0 -5 -10 -15 -20 -25 - - - - - - -0 -5 -10 -15 -20 -25 - - - - - - -0 -5 -10 -15 -20 -25 -3 -6 -9 - - - -Time -Predictions - -type - - - - - - -ipred -pred -Lines: Monolix; Points: rxode2; Page 2 of 2 - - diff --git a/tests/testthat/_snaps/data-import/single-endpoint-theo.svg b/tests/testthat/_snaps/data-import/single-endpoint-theo.svg deleted file mode 100644 index 90de8dc..0000000 --- a/tests/testthat/_snaps/data-import/single-endpoint-theo.svg +++ /dev/null @@ -1,532 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -ipred - - - - - - - - - - -iwres - - - - - - - - - - -pred - - - - - - -2.5 -5.0 -7.5 -10.0 - - - --2 -0 -2 - - - -3 -6 -9 -3 -6 -9 - - - --2 -0 -2 - - - -2.5 -5.0 -7.5 -10.0 - - - - -rxode2 -Monolix - - diff --git a/tests/testthat/test-memory-safety.R b/tests/testthat/test-memory-safety.R new file mode 100644 index 0000000..21106d4 --- /dev/null +++ b/tests/testthat/test-memory-safety.R @@ -0,0 +1,89 @@ +# Tests for integer overflow and memory safety fixes +# +# Issues addressed: +# - rc_dup_str: implicit ptrdiff_t to int truncation (shared.c) +# - dparse: (int)strlen(gBuf) overflow in all 13 parser entry-points +# - sbuf: signed integer overflow in size arithmetic (sbuf.c) +# - getLine: int col overflow in parseSyntaxErrors.h +# +# NOTE on the >2GB skipped tests: R's internal CHARSXP type uses a signed +# 32-bit integer for string length, capping individual R strings at +# INT_MAX (2,147,483,647) bytes. Because of this, the overflow guards in +# the C code protect primarily against direct C-level misuse (e.g., calls +# from C code that bypasses R's string limit). The tests below document +# the boundary behaviour and require ~2GB of free RAM to run. + +test_that("rc_dup_str handles normal strings without error", { + # Regression: short strings must work correctly after the overflow guards + .ret <- monolix2rx:::.equation("x_0 = V\nddt_x = -k*x", monolix2rx:::.pk("")) + expect_type(.ret$rx, "character") + expect_true(length(.ret$rx) > 0) +}) + +test_that("equation parser handles multi-statement input correctly", { + # Regression: multi-statement equations still parse cleanly after all guards + .ret <- monolix2rx:::.equation( + "x_0 = V\ny_0 = 1\nddt_x = -k*x\nddt_y = k*x - k2*y", + monolix2rx:::.pk("") + ) + expect_type(.ret$rx, "character") + expect_true(any(grepl("d/dt", .ret$rx))) +}) + +test_that("integer overflow protection: dparse input approaching INT_MAX bytes", { + skip(paste( + "requires ~2GB free RAM;", + "tests the (int)strlen(gBuf) overflow guard before each dparse() call.", + "Without the fix the cast silently wraps to a negative value for inputs", + "> INT_MAX bytes, causing dparse to crash. R strings are internally capped", + "at INT_MAX-1 bytes so the guard fires for C-level misuse; this test", + "exercises the largest string R can construct to verify the path is safe.", + "NOTE: use strrep() not paste0(rep()) to avoid a large intermediate vector." + )) + # 6 bytes x 357,913,941 = 2,147,483,646 bytes (INT_MAX - 1): the largest + # string strrep can produce before R itself errors on string length. + # The overflow guard fires only for > INT_MAX, so this input passes through; + # the test confirms no crash or corruption occurs near the boundary. + huge_str <- strrep("a = b\n", 357913941L) + expect_no_error( + monolix2rx:::.equation(huge_str, monolix2rx:::.pk("")) + ) +}) + +test_that("integer overflow protection: sbuf size arithmetic near INT_MAX", { + skip(paste( + "requires ~2GB free RAM;", + "tests signed integer overflow guards in sbuf.c sAppend/sAppendN/addLine.", + "Without the fix, sbb->o + n wraps to a negative int when the accumulated", + "buffer approaches INT_MAX, causing R_Realloc to allocate a tiny buffer and", + "corrupt the heap. The guard fires before that arithmetic.", + "NOTE: use strrep() not paste0(rep()) to avoid a large intermediate vector." + )) + # 35 bytes x 51,000,000 = 1,785,000,000 bytes (~1.78GB); feeds the parser + # with enough content to accumulate near the sbuf overflow boundary. + huge_eq <- strrep("var_a_b_c_d_e_f_g = var_h_i_j_k_l\n", 51000000L) + # The equation parser processes all lines; cumulative rc_dup_str calls + # grow _dupStrs toward INT_MAX. The guard prevents heap corruption. + expect_no_error( + monolix2rx:::.equation(huge_eq, monolix2rx:::.pk("")) + ) +}) + +test_that("integer overflow protection: getLine col accumulation near INT_MAX", { + skip(paste( + "requires ~2GB free RAM;", + "tests size_t col overflow guard in getLine (parseSyntaxErrors.h).", + "Without the fix, int col wraps at INT_MAX+1 and R_Calloc(col+1) receives", + "a negative/tiny size, corrupting the heap. The guard fires at col == INT_MAX.", + "A syntax error is triggered so getLine is actually called on the long line.", + "NOTE: use strrep() not paste0(rep()) to avoid a large intermediate vector." + )) + # Construct a valid-but-huge LHS with an invalid RHS to force a syntax error, + # which causes getLine to be called on the single giant line (~2GB, no newlines). + # 200,000,000 x 10 bytes = 2,000,000,000 bytes (~2GB, under INT_MAX). + giant_line <- strrep("x_var_abc", 200000000L) + expect_error( + monolix2rx:::.equation(paste0(giant_line, " = !!!bad"), + monolix2rx:::.pk("")) + ) +}) From 15e5eafa504242277aad950cad0605b27f3cbda4 Mon Sep 17 00:00:00 2001 From: mattfidler Date: Wed, 8 Apr 2026 19:00:35 -0500 Subject: [PATCH 2/4] rm snaps --- tests/testthat/_snaps/content.md | 19 ---- tests/testthat/_snaps/dataSettings.md | 7 -- tests/testthat/_snaps/fileinfo.md | 9 -- tests/testthat/_snaps/fit.md | 8 -- tests/testthat/_snaps/ind-def.md | 12 --- tests/testthat/_snaps/ind.md | 29 ------ tests/testthat/_snaps/longDef.md | 7 -- tests/testthat/_snaps/longOut.md | 8 -- tests/testthat/_snaps/longitudinal.md | 8 -- tests/testthat/_snaps/mlxtran.md | 128 -------------------------- tests/testthat/_snaps/mlxtranOp.md | 17 ---- tests/testthat/_snaps/parameter.md | 11 --- tests/testthat/_snaps/task.md | 11 --- 13 files changed, 274 deletions(-) delete mode 100644 tests/testthat/_snaps/content.md delete mode 100644 tests/testthat/_snaps/dataSettings.md delete mode 100644 tests/testthat/_snaps/fileinfo.md delete mode 100644 tests/testthat/_snaps/fit.md delete mode 100644 tests/testthat/_snaps/ind-def.md delete mode 100644 tests/testthat/_snaps/ind.md delete mode 100644 tests/testthat/_snaps/longDef.md delete mode 100644 tests/testthat/_snaps/longOut.md delete mode 100644 tests/testthat/_snaps/longitudinal.md delete mode 100644 tests/testthat/_snaps/mlxtran.md delete mode 100644 tests/testthat/_snaps/mlxtranOp.md delete mode 100644 tests/testthat/_snaps/parameter.md delete mode 100644 tests/testthat/_snaps/task.md diff --git a/tests/testthat/_snaps/content.md b/tests/testthat/_snaps/content.md deleted file mode 100644 index 994910b..0000000 --- a/tests/testthat/_snaps/content.md +++ /dev/null @@ -1,19 +0,0 @@ -# content - - Code - print(tmp) - Output - ID = {use=identifier} - TIME = {use=time} - EVID = {use=eventidentifier} - AMT = {use=amount} - YTYPE = {use=observationtype} - ADM = {use=administration} - DV = {use=observation, name={y1, y2}, yname={'1', '2'}, type={continuous, continuous}} - E0 = {use = regressor} - Emax = {use = regressor} - WT = {use=covariate, type=continuous} - CRCL = {use=covariate, type=continuous} - Race = {type=categorical, categories={Caucasian, Black, Latin}} - Sex = {type=categorical, categories={M, F}} - diff --git a/tests/testthat/_snaps/dataSettings.md b/tests/testthat/_snaps/dataSettings.md deleted file mode 100644 index 51fd9a4..0000000 --- a/tests/testthat/_snaps/dataSettings.md +++ /dev/null @@ -1,7 +0,0 @@ -# [SETTINGS] - - Code - print(tmp2) - Output - dataType = {'dv'=plasma} - diff --git a/tests/testthat/_snaps/fileinfo.md b/tests/testthat/_snaps/fileinfo.md deleted file mode 100644 index 787ea2e..0000000 --- a/tests/testthat/_snaps/fileinfo.md +++ /dev/null @@ -1,9 +0,0 @@ -# fileinfo - - Code - print(.fi) - Output - file = 'pk.turnover.emax3-monolix.csv' - delimiter = comma - header = {ID, TIME, EVID, AMT, DV, ADM, YTYPE, nlmixrRowNums} - diff --git a/tests/testthat/_snaps/fit.md b/tests/testthat/_snaps/fit.md deleted file mode 100644 index d8eee0a..0000000 --- a/tests/testthat/_snaps/fit.md +++ /dev/null @@ -1,8 +0,0 @@ -# fit parsing - - Code - print(tmp) - Output - data = {y1, y2} - model = {rx_prd_cp, rx_prd_effect} - diff --git a/tests/testthat/_snaps/ind-def.md b/tests/testthat/_snaps/ind-def.md deleted file mode 100644 index 2622d67..0000000 --- a/tests/testthat/_snaps/ind-def.md +++ /dev/null @@ -1,12 +0,0 @@ -# standard individual definition - - Code - print(tmp) - Output - F = {distribution=logitnormal, typical=F_pop, sd=omega_F, min=0, max=1} - ka = {distribution=lognormal, typical=ka_pop, no-variability} - V = {distribution=lognormal, typical=V_pop, sd=omega_V} - Cl = {distribution=lognormal, typical=Cl_pop, sd=omega_Cl} - correlation = {r(Cl, V)=corr1_V_Cl} - - diff --git a/tests/testthat/_snaps/ind.md b/tests/testthat/_snaps/ind.md deleted file mode 100644 index e17c573..0000000 --- a/tests/testthat/_snaps/ind.md +++ /dev/null @@ -1,29 +0,0 @@ -# input categorical covariates and regressors - - Code - print(.tmp) - Output - input = {V_pop, omega_V, ka_pop, omega_ka, Cl_pop, omega_Cl, logtAge, Race, Sex, logtWeight, beta_Cl_Race_Caucasian, beta_Cl_Race_Latin, beta_Cl_Smoke_yes, beta_Cl_logtAge, beta_V_logtWeight, E0} - E0 = {use = regressor} - Race = {type=categorical, categories={Caucasian, Black, Latin}} - Sex = {type=categorical, categories={M, F}} - ---- - - Code - print(.tmp) - Output - input = {AGE, DOSE, SEX} - DOSE = {type=categorical, categories={'50 mg', '100 mg'}} - SEX = {type=categorical, categories={Female, Male}} - ---- - - Code - print(.tmp) - Output - input = {V_pop, omega_V, ka_pop, omega_ka, Cl_pop, omega_Cl, logtAge, Race, Sex, logtWeight, beta_Cl_Race_Caucasian, beta_Cl_Race_Latin, beta_Cl_Smoke_yes, beta_Cl_logtAge, beta_V_logtWeight, E0} - E0 = {use = regressor} - Race = {type=categorical, categories={'Caucasian 1', Black, Latin}} - Sex = {type=categorical, categories={M, F}} - diff --git a/tests/testthat/_snaps/longDef.md b/tests/testthat/_snaps/longDef.md deleted file mode 100644 index b02ca0d..0000000 --- a/tests/testthat/_snaps/longDef.md +++ /dev/null @@ -1,7 +0,0 @@ -# [LONGITUDINAL] DEFINITION: - - Code - print(tmp) - Output - Seizure = {type=event, eventType=intervalCensored, maxEventNumber=1, rightCensoringTime=120, intervalLength=10, hazard=haz} - diff --git a/tests/testthat/_snaps/longOut.md b/tests/testthat/_snaps/longOut.md deleted file mode 100644 index ebbf8a7..0000000 --- a/tests/testthat/_snaps/longOut.md +++ /dev/null @@ -1,8 +0,0 @@ -# long output - - Code - print(.ret) - Output - output = {Conc, Effect} - table = {Ap, T12} - diff --git a/tests/testthat/_snaps/longitudinal.md b/tests/testthat/_snaps/longitudinal.md deleted file mode 100644 index f311dee..0000000 --- a/tests/testthat/_snaps/longitudinal.md +++ /dev/null @@ -1,8 +0,0 @@ -# longitudinal() - - Code - print(f) - Output - input = {pkadd__err, prop__err, pdadd__err} - file = 'pk.turnover.emax3-monolix.txt' - diff --git a/tests/testthat/_snaps/mlxtran.md b/tests/testthat/_snaps/mlxtran.md deleted file mode 100644 index 303982e..0000000 --- a/tests/testthat/_snaps/mlxtran.md +++ /dev/null @@ -1,128 +0,0 @@ -# mlxtran initial list - - Code - print(v) - Output - DESCRIPTION: - model translated from `babelmixr2` and `nlmixr2` function pk.turnover.emax3 to pk.turnover.emax3-monolix.txt - - - [FILEINFO] - ; parsed: $DATAFILE$FILEINFO$FILEINFO - file = 'pk.turnover.emax3-monolix.csv' - delimiter = comma - header = {ID, TIME, EVID, AMT, DV, ADM, YTYPE, nlmixrRowNums} - - [CONTENT] - ; parsed: $DATAFILE$CONTENT$CONTENT - ID = {use=identifier} - TIME = {use=time} - EVID = {use=eventidentifier} - AMT = {use=amount} - YTYPE = {use=observationtype} - ADM = {use=administration} - DV = {use=observation, name={y1, y2}, yname={'1', '2'}, type={continuous, continuous}} - - - [INDIVIDUAL] - ; parsed: $MODEL$INDIVIDUAL$INDIVIDUAL - input = {ktr_pop, omega_ktr, ka_pop, omega_ka, cl_pop, omega_cl, v_pop, omega_v, emax_pop, omega_emax, ec50_pop, omega_ec50, kout_pop, omega_kout, e0_pop, omega_e0} - - DEFINITION: - ; parsed: $MODEL$INDIVIDUAL$DEFINITION - ktr = {distribution=lognormal, typical=ktr_pop, sd=omega_ktr} - ka = {distribution=lognormal, typical=ka_pop, sd=omega_ka} - cl = {distribution=lognormal, typical=cl_pop, sd=omega_cl} - v = {distribution=lognormal, typical=v_pop, sd=omega_v} - emax = {distribution=logitnormal, typical=emax_pop, sd=omega_emax, min=0, max=1} - ec50 = {distribution=lognormal, typical=ec50_pop, sd=omega_ec50} - kout = {distribution=lognormal, typical=kout_pop, sd=omega_kout} - e0 = {distribution=lognormal, typical=e0_pop, sd=omega_e0} - - [LONGITUDINAL] - ; parsed: $MODEL$LONGITUDINAL$LONGITUDINAL - input = {pkadd__err, prop__err, pdadd__err, ktr, ka, cl, v, emax, ec50, kout, e0} - - DEFINITION: - ; parsed: $MODEL$LONGITUDINAL$DEFINITION - rx_prd_cp = {distribution=normal, prediction=rx_pred_cp, errorModel=combined2(pkadd__err, prop__err)} - rx_prd_effect = {distribution=normal, prediction=rx_pred_effect, errorModel=constant(pdadd__err)} - - PK: - ; parsed: $MODEL$LONGITUDINAL$PK - compartment(cmt = 1, amount = depot, volume = 1.0) - - depot(adm = 1, target = depot, Tlag = 0, p = 1) - - EQUATION: - ; parsed: $MODEL$LONGITUDINAL$EQUATION - DCP = center/v - PD = 1-emax*DCP/(ec50+DCP) - effect_0 = e0 - kin = e0*kout - ddt_depot = - ktr*depot - ddt_gut = ktr*depot-ka*gut - ddt_center = ka*gut-cl/v*center - ddt_effect = kin*PD-kout*effect - cp = center/v - rx_pred_cp = cp - rx_pred_effect = effect - - OUTPUT: - ; parsed: $MODEL$LONGITUDINAL$OUTPUT - output = {rx_pred_cp, rx_pred_effect} - - - ; parsed: $FIT$FIT - data = {y1, y2} - model = {rx_prd_cp, rx_prd_effect} - - - ; parsed: $PARAMETER$PARAMETER - ktr_pop = {value=1, method=MLE} - ka_pop = {value=1, method=MLE} - cl_pop = {value=0.1, method=MLE} - v_pop = {value=10, method=MLE} - prop__err = {value=0.1, method=MLE} - pkadd__err = {value=0.1, method=MLE} - emax_pop = {value=0.8, method=MLE} - ec50_pop = {value=0.5, method=MLE} - kout_pop = {value=0.05, method=MLE} - e0_pop = {value=100, method=MLE} - pdadd__err = {value=10, method=MLE} - omega_ktr = {value=1, method=MLE} - omega_ka = {value=1, method=MLE} - omega_cl = {value=1.4142135623731, method=MLE} - omega_v = {value=1, method=MLE} - omega_emax = {value=0.707106781186548, method=MLE} - omega_ec50 = {value=0.707106781186548, method=MLE} - omega_kout = {value=0.707106781186548, method=MLE} - omega_e0 = {value=0.707106781186548, method=MLE} - - - [TASKS] - ; parsed: $MONOLIX$TASKS$TASKS - populationParameters() - individualParameters(method = {conditionalMode}) - fim(method = Linearization) - logLikelihood(method = Linearization) - plotResult(method = {outputplot, indfits, obspred, residualsscatter, residualsdistribution, parameterdistribution, covariatemodeldiagnosis, randomeffects, covariancemodeldiagnosis, saemresults}) - - [SETTINGS] - GLOBAL: - ; parsed: $MONOLIX$SETTINGS$GLOBAL - exportpath = 'pk.turnover.emax3-monolix' - - POPULATION: - ; parsed: $MONOLIX$SETTINGS$POPULATION - exploratoryautostop = no - smoothingautostop = no - burniniterations = 5 - exploratoryiterations = 250 - simulatedannealingiterations = 250 - smoothingiterations = 200 - exploratoryalpha = 0 - exploratoryinterval = 200 - omegatau = 0.95 - errormodeltau = 0.95 - diff --git a/tests/testthat/_snaps/mlxtranOp.md b/tests/testthat/_snaps/mlxtranOp.md deleted file mode 100644 index 4cbd138..0000000 --- a/tests/testthat/_snaps/mlxtranOp.md +++ /dev/null @@ -1,17 +0,0 @@ -# mlxtranOp - - Code - print(.tmp) - Output - exportpath = 'pk.turnover.emax3-monolix' - exploratoryautostop = no - smoothingautostop = no - burniniterations = 5 - exploratoryiterations = 250 - simulatedannealingiterations = 250 - smoothingiterations = 200 - exploratoryalpha = 0 - exploratoryinterval = 200 - omegatau = 0.95 - errormodeltau = 0.95 - diff --git a/tests/testthat/_snaps/parameter.md b/tests/testthat/_snaps/parameter.md deleted file mode 100644 index 05ad0d8..0000000 --- a/tests/testthat/_snaps/parameter.md +++ /dev/null @@ -1,11 +0,0 @@ -# parameters - - Code - print(tmp) - Output - ktr_pop = {value=1, method=MLE} - ka_pop = {value=1, method=FIXED} - cl_pop = {value=0.1, method=MLE} - v_pop = {value=10, method=MLE} - prop__err = {value=0.1, method=MLE} - diff --git a/tests/testthat/_snaps/task.md b/tests/testthat/_snaps/task.md deleted file mode 100644 index b4efe94..0000000 --- a/tests/testthat/_snaps/task.md +++ /dev/null @@ -1,11 +0,0 @@ -# TASK - - Code - print(.ret) - Output - populationParameters() - individualParameters(method = {conditionalMode}) - fim(method = Linearization) - logLikelihood(method = Linearization) - plotResult(method = {outputplot, indfits, obspred, residualsscatter, residualsdistribution, parameterdistribution, covariatemodeldiagnosis, randomeffects, covariancemodeldiagnosis, saemresults}, color = red) - From cd1924d92f73d4864e16f7654f91b7177b1b7da4 Mon Sep 17 00:00:00 2001 From: mattfidler Date: Wed, 8 Apr 2026 19:02:50 -0500 Subject: [PATCH 3/4] CF fixes --- tests/testthat/test-memory-safety.R | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/testthat/test-memory-safety.R b/tests/testthat/test-memory-safety.R index 21106d4..c5f2c6c 100644 --- a/tests/testthat/test-memory-safety.R +++ b/tests/testthat/test-memory-safety.R @@ -15,16 +15,16 @@ test_that("rc_dup_str handles normal strings without error", { # Regression: short strings must work correctly after the overflow guards - .ret <- monolix2rx:::.equation("x_0 = V\nddt_x = -k*x", monolix2rx:::.pk("")) + .ret <- .equation("x_0 = V\nddt_x = -k*x", monolix2rx:::.pk("")) expect_type(.ret$rx, "character") expect_true(length(.ret$rx) > 0) }) test_that("equation parser handles multi-statement input correctly", { # Regression: multi-statement equations still parse cleanly after all guards - .ret <- monolix2rx:::.equation( + .ret <- .equation( "x_0 = V\ny_0 = 1\nddt_x = -k*x\nddt_y = k*x - k2*y", - monolix2rx:::.pk("") + .pk("") ) expect_type(.ret$rx, "character") expect_true(any(grepl("d/dt", .ret$rx))) @@ -46,7 +46,7 @@ test_that("integer overflow protection: dparse input approaching INT_MAX bytes", # the test confirms no crash or corruption occurs near the boundary. huge_str <- strrep("a = b\n", 357913941L) expect_no_error( - monolix2rx:::.equation(huge_str, monolix2rx:::.pk("")) + .equation(huge_str, .pk("")) ) }) @@ -65,7 +65,7 @@ test_that("integer overflow protection: sbuf size arithmetic near INT_MAX", { # The equation parser processes all lines; cumulative rc_dup_str calls # grow _dupStrs toward INT_MAX. The guard prevents heap corruption. expect_no_error( - monolix2rx:::.equation(huge_eq, monolix2rx:::.pk("")) + .equation(huge_eq, .pk("")) ) }) @@ -83,7 +83,7 @@ test_that("integer overflow protection: getLine col accumulation near INT_MAX", # 200,000,000 x 10 bytes = 2,000,000,000 bytes (~2GB, under INT_MAX). giant_line <- strrep("x_var_abc", 200000000L) expect_error( - monolix2rx:::.equation(paste0(giant_line, " = !!!bad"), - monolix2rx:::.pk("")) + .equation(paste0(giant_line, " = !!!bad"), + .pk("")) ) }) From 37cd0ca694fc6be7a5a17670acc568a1ed80b6d6 Mon Sep 17 00:00:00 2001 From: mattfidler Date: Wed, 8 Apr 2026 19:03:32 -0500 Subject: [PATCH 4/4] Last CF fix --- tests/testthat/test-memory-safety.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-memory-safety.R b/tests/testthat/test-memory-safety.R index c5f2c6c..d1dc21b 100644 --- a/tests/testthat/test-memory-safety.R +++ b/tests/testthat/test-memory-safety.R @@ -15,7 +15,7 @@ test_that("rc_dup_str handles normal strings without error", { # Regression: short strings must work correctly after the overflow guards - .ret <- .equation("x_0 = V\nddt_x = -k*x", monolix2rx:::.pk("")) + .ret <- .equation("x_0 = V\nddt_x = -k*x", .pk("")) expect_type(.ret$rx, "character") expect_true(length(.ret$rx) > 0) })