Skip to content

Make R SDK CRAN-conformant via vendored source build#179

Merged
clemensv merged 2 commits into
masterfrom
feature/r-cran-conformance
Jul 14, 2026
Merged

Make R SDK CRAN-conformant via vendored source build#179
clemensv merged 2 commits into
masterfrom
feature/r-cran-conformance

Conversation

@clemensv

Copy link
Copy Markdown
Contributor

Make the R SDK CRAN-conformant (vendored source build)

Why

The R package previously downloaded a prebuilt libjson_structure shared
library from GitHub Releases at install / first load. CRAN forbids
install-time and runtime network access
, so that model could never pass
R CMD check --as-cran and the package could not be published. This PR
refactors the package to be self-contained and built from source on every
platform.

What changed

Vendored source build (no runtime download)

  • Vendor the JSON Structure C engine translation units, the public headers
    under r/src/json_structure/, and cJSON v1.7.18 (MIT, © Dave Gamble)
    into r/src/. The package now compiles the engine at install time.
  • cJSON attribution recorded in r/inst/COPYRIGHTS.
  • New r/tools/vendor-engine.R re-syncs the engine .c/headers from c/
    into r/src/ (excluded from the tarball via .Rbuildignore).
  • Removed the runtime binary installer (R/binary_installer.R + its docs) and
    the download/dlopen path in ffi.R/zzz.R.

Windows std::regex deadlock → pure-C matcher

MinGW libstdc++ std::regex deadlocks on first use whenever its DLL is
loaded by R on Windows
(lazy locale init via __gthread_once under the
fully-static Rtools toolchain). It is unfixable by link flags, static/shared
runtime selection, or thread warmups — and CRAN mandates that exact static
toolchain.

  • Replace the vendored regex_utils.cpp with regex_utils.c, a
    self-contained ECMAScript-subset backtracking matcher: pure C, no
    threads/TLS, char-class bitmaps, greedy/lazy quantifiers, anchors, word
    boundaries, escapes, groups, alternation. Invalid/unsupported constructs
    (lookbehind, named groups, inline flags, \p{…}, lookahead) are rejected
    as invalid
    , matching std::regex's throw-on-parse behavior. ReDoS is
    bounded by a step budget.
  • The package is now pure C — no libstdc++ dependency at all.
  • The upstream C SDK (c/src/regex_utils.cpp) is unchanged; only the R
    vendored copy differs. regex_utils.h documents the split.

C engine (shared source of truth)

  • Replace three bounded snprintf copies in c/src/instance_validator.c and
    c/src/schema_validator.c with memcpy to silence -Wformat-truncation,
    so the vendored build stays warning-free after any re-sync. C SDK tests
    unaffected
    (all pass).

Build / CI / docs

  • Makevars: Unix keeps $(SHLIB_PTHREAD_FLAGS) for the engine's allocator
    lock; Makevars.win links nothing extra (SRWLOCK from kernel32). No C++.
  • .github/workflows/r.yml: a single source-build matrix over
    ubuntu / macos / windows; R CMD check with --as-cran and error-on
    warning; the shared corpus is exercised via JSONSTRUCTURE_TEST_ASSETS.
    The separate C-build and Windows jobs are removed.
  • README / NEWS / cran-comments updated and man/ regenerated for the
    pure-C, source-build model.

Validation

R CMD check --as-cran --no-manual on a fresh tarball:

Status: 2 NOTEs
  * New submission (Maintainer: Clemens Vasters <clemensv@microsoft.com>)
  * pragma(s) suppressing diagnostics: 'src/cJSON.c'

0 errors, 0 warnings, 2 notes (both expected and documented in
cran-comments.md). The shared test-assets/ corpus runs during the check:
[ FAIL 0 | WARN 0 | SKIP 1 | PASS 74 ] (the single skip is the optional
primer-samples directory, not shipped in the tarball). Verified as a pure-C
build (all gcc, no g++/libstdc++) and with no Windows regex hang.

Notes

  • No CRAN submission is performed by this PR; it makes the package
    conformant so a future submission (or r-universe) is possible.

clemensv and others added 2 commits July 14, 2026 09:46
Refactor the R package to compile the JSON Structure C engine from
vendored sources at install time instead of downloading a prebuilt
shared library at runtime. CRAN forbids install/runtime network access,
so the download model could never be accepted; this makes the package
self-contained and installable from source on every platform.

Engine sourcing
- Vendor the engine translation units (error_codes.c, instance_validator.c,
  json_source_locator.c, schema_validator.c, types.c), the public headers
  under src/json_structure/, and cJSON v1.7.18 (MIT, (c) Dave Gamble) into
  r/src/. cJSON attribution is recorded in inst/COPYRIGHTS.
- Add tools/vendor-engine.R to re-sync the engine sources/headers from c/
  into r/src/ (kept out of the build tarball via .Rbuildignore).
- Remove the runtime binary installer (R/binary_installer.R and its docs);
  drop the download/dlopen path from ffi.R/zzz.R.

Windows std::regex deadlock -> pure-C matcher
- MinGW libstdc++ std::regex deadlocks on first use whenever its DLL is
  loaded by R on Windows (lazy locale init via __gthread_once under the
  fully-static Rtools toolchain). This is unfixable by link flags, so the
  vendored regex_utils.cpp is replaced with regex_utils.c, a self-contained
  ECMAScript-subset backtracking matcher (pure C, no threads/TLS, ReDoS
  step budget). The package is now pure C: no libstdc++ dependency.
- The upstream C SDK (c/src/regex_utils.cpp) is unchanged; only the R
  vendored copy differs. regex_utils.h documents the split.

C engine (shared source of truth)
- Replace three bounded snprintf copies in instance_validator.c and
  schema_validator.c with memcpy to silence -Wformat-truncation so the
  vendored build is warning-free after any re-sync. C SDK tests unaffected.

Build, CI, docs
- Makevars: Unix keeps $(SHLIB_PTHREAD_FLAGS) for the engine allocator lock;
  Makevars.win links nothing extra (SRWLOCK from kernel32). No C++/libstdc++.
- .github/workflows/r.yml: single source-build matrix over
  ubuntu/macos/windows, R CMD check with --as-cran and error-on warning,
  corpus exercised via JSONSTRUCTURE_TEST_ASSETS; drop the separate C-build
  and Windows jobs.
- Update README/NEWS/cran-comments and regenerate man/ for the pure-C,
  source-build model.

R CMD check --as-cran: 0 errors, 0 warnings, 2 notes (new submission;
cJSON pragma), with the shared test-assets corpus passing during the check.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The CRAN "checking compiled code" check flags `__sprintf_chk` (from
`sprintf`) on Linux/macOS as a disallowed entry point, producing a WARNING
that fails `R CMD check --as-cran` under error-on=warning. (MinGW on Windows
does not emit the fortified symbol, so the local Windows check did not surface
it.)

Replace all `sprintf()` calls in the vendored `src/cJSON.c` with bounded
`snprintf()` (using the target buffer sizes). Behavior is unchanged since the
buffers were already adequately sized; the package's testthat corpus still
passes. Disclose the modification in cran-comments.md, inst/COPYRIGHTS, the
cJSON.c header, and NEWS.md, and correct the COPYRIGHTS engine file list to
reference regex_utils.c (pure C) rather than the upstream regex_utils.cpp.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@clemensv
clemensv merged commit 11f64cd into master Jul 14, 2026
21 checks passed
@clemensv
clemensv deleted the feature/r-cran-conformance branch July 14, 2026 08:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant