Skip to content

v1.11.0 — Runtime audit: crash, hang, data-loss and reproducibility fixes

Latest

Choose a tag to compare

@evandeilton evandeilton released this 12 Aug 21:15

Runtime audit of the C++ engine, following the static audit that shipped in 1.10.0. This round was driven by instrumented builds (-fsanitize=address,undefined), a degenerate-input stress harness, and a golden-output regression suite covering all 37 exported algorithms (~3,200 result comparisons).

Every fix below is pinned by a test in tests/testthat/test-regression-audit.R, and each of those tests fails on 1.10.0.

Crashes and hangs

  • ob_categorical_ivb crashed the R session — a segmentation fault, not a catchable error — for any feature with no more categories than max_bins. With the default max_bins = 5 that meant every 2- to 5-level predictor: sex, marital status, region, education. The ncat <= max_bins fast path skipped the only place stats_cache was built, while result assembly dereferenced it unconditionally.

  • ob_numerical_ir, ob_numerical_jedi and ob_numerical_jedi_mwoe looped forever whenever min_bins exceeded the number of distinct feature values — routine when one min_bins is applied across a whole feature set. The loops contained no R_CheckUserInterrupt(), so Ctrl-C could not interrupt them either; the session had to be killed.

Silent data loss

  • ob_numerical_cm discarded observations. Its equal-frequency pre-binner, on hitting a run of tied values straddling a bin boundary, advanced past those records without assigning them to any bin. In testing this lost 26% of an integer feature and 40% of a coarse one, with the reported WoE/IV computed from the survivors alone.

Interval convention — standardised on (a, b]

The package previously disagreed with itself about what a bin is. Labels advertised (a;b] while many algorithms assigned values as [a;b); ob_apply_woe_num() had its two binary searches swapped, so both settings of include_upper_bound did the opposite of what the argument documents; and several pre-binners split runs of tied values, so their counts could not be derived from their own cutpoints at all. A value landing exactly on a cutpoint — routine for integer, rounded or currency features — could be scored into a different bin than the one it was trained in.

Measured across 63 algorithm/dataset combinations: 31 violated the documented convention before this release, 0 do now.

Reproducibility

  • ob_categorical_sab now honours set.seed(). It was seeded from std::random_device, so identical input returned a different binning on every call. Four runs under one seed previously gave bins 4/3/5/5 with three distinct IV values — unusable for auditable or regulated models.

  • obcorr() now returns a stable row order. Per-thread buffers were spliced together in an omp critical block, so row order depended on thread scheduling; two runs with the same data and same thread count could differ. Correlation values are unchanged.

CRAN policy

  • obcorr() no longer seizes every core. With the default threads = 0 it called omp_set_num_threads(std::thread::hardware_concurrency()). CRAN Repository Policy requires never using more than two cores by default; this was an archival risk. The default is now at most 2, honouring OMP_NUM_THREADS, with an explicit threads argument still respected.

  • Removed eight stale Dropbox conflict copies from src/ that R CMD build would have shipped and compiled, producing duplicate symbols.

Compatibility

This release changes numeric output. Of 3,240 golden comparisons against 1.10.0, 2,353 are unchanged; 12 numerical algorithms plus ob_categorical_sab differ, concentrated in data with ties (integer, rounded or currency features). Continuous features without ties are largely unaffected. No public R API changed.

Users with scorecards in production should expect bin boundaries to shift on discrete features, and re-validate. The previous results were silently incorrect at those boundaries.

Verification

R CMD check green on Ubuntu (devel, release, oldrel-1/2/3) and Windows (release). Test suite runs with 0 failures and 0 skips — the ob_numerical_sketch test disabled since 1.0.7 for a segfault is re-enabled, re-verified clean under -fsanitize=address,undefined. The full suite is also clean under ASan/UBSan.

Known issues

  • ob_categorical_dp and ob_categorical_fetb report WoE = IV = 0 for perfectly separating bins instead of applying smoothing.
  • Return fields are not uniform across algorithms: event_rate appears in 13 of 37, total_iv in 28 of 37, and iv is missing from both _dmiv variants.
  • ob_apply_woe_num() does not support the multinomial *_jedi_mwoe variants.