diff --git a/AGENTS.md b/AGENTS.md index 424b38c..309d45b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,9 @@ R CMD INSTALL . ```r # Run tests (in the R console) devtools::test() -# or without devtools: +# or without devtools (package must be installed and attached; +# NOT_CRAN=true also runs the tests that skip on aarch64): +library(watcher) testthat::test_dir("tests/testthat") ``` @@ -69,6 +71,9 @@ The package uses GitHub Actions workflows in `.github/workflows/`: **Callback Mechanism:** - File events trigger `process_events()` callback in C - Events are bundled by path and passed to R via the 'later' package's `execLaterNative2` +- Paths are deduplicated per callback (first occurrence kept) and copied in a + single allocation; combined with the patched-in event bubbling (see Build + System), paths reported by a single callback are always unique - R callbacks execute when R is idle or when `later::run_now()` is called - If no callback is provided, events are printed to stdout @@ -100,10 +105,15 @@ hand-maintained config header, not host probing. - `src/link.cpp`: empty `.cpp` that forces R to link with the C++ linker. - `tools/update_libfswatch.sh`: re-vendors libfswatch and regenerates the config header, object list, and Makevars; calls `tools/patch_libfswatch.sh`. -- `tools/patch_libfswatch.sh`: idempotent source patches — a null-format guard - in `string_utils`, self-guards on the fsevents/inotify/fanotify monitors, and - neutering of libfswatch's stdout/stderr/cerr logging (for the R CMD check - "compiled code" policy). +- `tools/patch_libfswatch.sh`: idempotent source patches — (1) empty + `string_utils.cpp` to an include-only TU: after the logging neutering its + only live references are two regex-error exception messages in `monitor.cpp`, + unreachable in watcher (no text filters are ever added), so those are + replaced with a static message; also fixes a GCC 16 `-Warray-bounds` false + positive. (2) Self-guards on the fsevents/inotify/fanotify monitors. (3) + Neutering of libfswatch's stdout/stderr/cerr logging (for the R CMD check + "compiled code" policy). (4) `bubble_events = true` by default (the C API + does not expose `set_bubble_events()`). **Key dependencies:** an R C/C++ toolchain plus `make`; pthread; the 'later' R package. A system libfswatch is used automatically when present. diff --git a/src/fswatch/libfswatch/src/libfswatch/c++/monitor.cpp b/src/fswatch/libfswatch/src/libfswatch/c++/monitor.cpp index 28b75b0..7e51c35 100644 --- a/src/fswatch/libfswatch/src/libfswatch/c++/monitor.cpp +++ b/src/fswatch/libfswatch/src/libfswatch/c++/monitor.cpp @@ -127,9 +127,7 @@ namespace fsw catch (const std::regex_error& error) { throw libfsw_exception( - string_utils::string_from_format( - _("An error occurred during the compilation of %s"), - filter.text.c_str()), + _("An error occurred during the compilation of a filter regex"), FSW_ERR_INVALID_REGEX); } } @@ -148,9 +146,7 @@ namespace fsw catch (const std::regex_error& error) { throw libfsw_exception( - string_utils::string_from_format( - _("An error occurred during the compilation of %s"), - filter.text.c_str()), + _("An error occurred during the compilation of a filter regex"), FSW_ERR_INVALID_REGEX); } } diff --git a/src/fswatch/libfswatch/src/libfswatch/c++/string/string_utils.cpp b/src/fswatch/libfswatch/src/libfswatch/c++/string/string_utils.cpp index 343267a..64d11d5 100644 --- a/src/fswatch/libfswatch/src/libfswatch/c++/string/string_utils.cpp +++ b/src/fswatch/libfswatch/src/libfswatch/c++/string/string_utils.cpp @@ -13,54 +13,8 @@ * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ -#include -#include -#include +/* watcher: bodies removed as dead code. Logging is neutered, and the only + * other callers (regex-error paths in monitor.cpp) are unreachable in watcher. + * Also sidesteps a GCC 16 -Warray-bounds false positive. + * See tools/patch_libfswatch.sh. */ #include "string_utils.hpp" - -using namespace std; - -namespace fsw -{ - namespace string_utils - { - string vstring_from_format(const char *format, va_list args) - { - if (!format) return string(); - size_t current_buffer_size = 0; - int required_chars = 512; - - vector buffer; - - do - { - current_buffer_size += required_chars; - buffer.resize(current_buffer_size); - required_chars = vsnprintf(&buffer[0], current_buffer_size, format, args); - - // If an encoding error occurs, break and write an empty string into the - // buffer. - if (required_chars < 0) - { - buffer.resize(1); - break; - } - } - while ((size_t) required_chars > current_buffer_size); - - return string(&buffer[0]); - } - - string string_from_format(const char *format, ...) - { - va_list args; - va_start(args, format); - - string ret = vstring_from_format(format, args); - - va_end(args); - - return ret; - } - } -} \ No newline at end of file diff --git a/tools/patch_libfswatch.sh b/tools/patch_libfswatch.sh index 71324ea..0b46dc0 100755 --- a/tools/patch_libfswatch.sh +++ b/tools/patch_libfswatch.sh @@ -10,8 +10,14 @@ # # Every patch is IDEMPOTENT: running this on already-patched sources is a no-op. # The patches are: -# 1. string_utils.cpp - guard against a null format string (GCC -# -Wformat-truncation in stricter CRAN/rhub builds). +# 1. string_utils.cpp body removal - after patch 3's logging neutering, +# string_from_format()/vstring_from_format() are referenced only by two +# regex-error exception messages in monitor.cpp, which watcher can never +# reach (it adds only event-flag filters, never text filters). Replace +# those call sites with a static message (1a) and empty string_utils.cpp +# to an include-only TU (1b). Also resolves a GCC 16 -Warray-bounds false +# positive against the upstream vector::resize implementation, which +# R CMD check reports as a significant warning. # 2. Monitor self-guards - wrap fsevents/inotify/fanotify monitor bodies in # their platform #ifdef so they compile to empty objects on the wrong OS # (kqueue/fen/windows monitors already self-guard upstream). @@ -59,19 +65,47 @@ add_guard() { echo -e "${GREEN} ✓ self-guard $macro applied${NC}" } -# --- Patch 1: string_utils.cpp null-format guard ----------------------------- -STRING_UTILS="$LIBFSW_SRC/c++/string/string_utils.cpp" -if [ -f "$STRING_UTILS" ]; then - if grep -q "if (!format) return" "$STRING_UTILS"; then - echo -e "${GREEN} ✓ string_utils null-format guard already present${NC}" - else - sed -i.bak 's|^ size_t current_buffer_size = 0;$| if (!format) return string();\ - size_t current_buffer_size = 0;|' "$STRING_UTILS" - rm -f "$STRING_UTILS.bak" - echo -e "${GREEN} ✓ string_utils null-format guard applied${NC}" +# --- Patch 1a: static regex-error messages in monitor.cpp -------------------- +# watcher never adds text filters (only event-flag filters), so the regex-error +# exceptions in add_filter()/add_prune_filter() are unreachable. Replace their +# string_from_format() messages with a static string so string_utils.cpp can be +# emptied (patch 1b). +MON_CPP="$LIBFSW_SRC/c++/monitor.cpp" +if grep -q 'compilation of a filter regex' "$MON_CPP" 2>/dev/null; then + echo -e "${GREEN} ✓ monitor.cpp regex-error messages already static${NC}" +else + perl -0777 -pi -e 's/ string_utils::string_from_format\(\n _\("An error occurred during the compilation of %s"\),\n filter\.text\.c_str\(\)\),/ _("An error occurred during the compilation of a filter regex"),/g' "$MON_CPP" + if grep -q 'filter\.text\.c_str()' "$MON_CPP"; then + echo "Error: failed to patch string_from_format call sites in $MON_CPP" >&2 + exit 1 fi + echo -e "${GREEN} ✓ monitor.cpp regex-error messages made static${NC}" +fi + +# --- Patch 1b: empty string_utils.cpp bodies (dead code) --------------------- +# After patch 3's logging neutering and patch 1a, nothing references +# string_from_format()/vstring_from_format(): empty the TU. The object stays in +# the build, compiling empty like the self-guarded monitors. This also resolves +# the GCC 16 -Warray-bounds false positive against the upstream +# vector::resize implementation, which R CMD check reports as a +# significant warning. +STRING_UTILS="$LIBFSW_SRC/c++/string/string_utils.cpp" +if grep -q 'watcher: bodies removed' "$STRING_UTILS" 2>/dev/null; then + echo -e "${GREEN} ✓ string_utils.cpp bodies already removed${NC}" else - echo -e "${YELLOW} ⚠ string_utils.cpp not found, skipping${NC}" + # Preserve the upstream licence header (everything before the first #include). + HEADER=$(awk '/^#include/{exit} {print}' "$STRING_UTILS") + { + printf '%s\n' "$HEADER" + cat <<'EOF' +/* watcher: bodies removed as dead code. Logging is neutered, and the only + * other callers (regex-error paths in monitor.cpp) are unreachable in watcher. + * Also sidesteps a GCC 16 -Warray-bounds false positive. + * See tools/patch_libfswatch.sh. */ +#include "string_utils.hpp" +EOF + } > "$STRING_UTILS.tmp" && mv "$STRING_UTILS.tmp" "$STRING_UTILS" + echo -e "${GREEN} ✓ string_utils.cpp bodies removed${NC}" fi # --- Patch 2: self-guard the three non-self-guarding monitors ----------------