diff --git a/CHANGES.md b/CHANGES.md index 77f1ad08f..c3da149e0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,34 @@ # STLSoft - CHANGES +## 1.11.1-rc10 - 20th October 2026 + +* additions: + * Added **CONCEPTS.md** — documents the **Resizeable Buffer** concept (requirements, models / non-models, probe-then-fill gold contract for traits `rb` overloads); + * Added example **examples/by_concept/resizeable_buffer** — probe-then-fill into `auto_buffer` / `vector` via `platformstl` traits `rb` overloads and `resizeable_buffer_resize`; + * **winstl::system_traits** — added `get_module_directory(hModule, rb)` (A+W), gold probe-then-fill companion to the raw overload and to `get_module_filename(rb)`; +* fixes: + * **unixstl::system_traits** — `get_home_directory(rb)` and `get_environment_variable(rb)` follow the gold pattern (early-out on probe `0`, conditional resize, `ENOMEM` on failure); environment `rb` demand includes the trailing NUL (`cchValue + 1`); + * **unixstl** / **winstl** `filesystem_traits` — `get_full_path_name` / `get_short_path_name` / `get_current_directory` `rb` overloads no longer resize-then-`&rb[0]` on an empty buffer when the probe returns `0`; resize failure sets `ENOMEM` / `ERROR_OUTOFMEMORY`; + * **unixstl::filesystem_traits** — `ensure_dir_end(rb)` leaves a lone-NUL `""` unchanged (`n > 1`, matching WinSTL and the C-string overload); + * **winstl::filesystem_traits** — `remove_dir_end(rb)` shrinks `size()` to the post-strip length so a later `ensure_dir_end(rb)` cannot write past the NUL; + * **unixstl::filesystem_traits** — `remove_dir_end` (C-string and `rb`, A+W) now strips **all** trailing path separators, matching WinSTL (was strip-one); + * **unixstl** / **winstl** `filesystem_traits` — every `resizeable_buffer_resize` failure on the `rb` / scratch paths above reports last-error (including WinSTL `ensure_dir_end(rb)` and the UnixSTL full-path cwd scratch grow); + * **unixstl::readdir_sequence** — non-exception `absolutePath` failure path no longer contains a broken `\'0'` write; soft-fails with an empty string (parallel to the throw when exceptions are enabled); + * **unixstl** / **winstl** `filesystem_traits` — `get_full_path_name_impl` scratch for a non-NUL-terminated counted slice is sized `1 + len` (was `1 + (len - 1)`), so writing the trailing NUL is in-bounds; WinSTL quoted-path handling takes this branch; + * **stlsoft::resizeable_buffer_resize** — invoke `rb.resize(newSize)` directly (with SFINAE detection of one-argument `bool` / `ss_bool_t` `resize`) so pre-C++11-style `resize(size, value = value())` models work; VC6 path adds a two-argument `void` member-pointer overload; +* docs: + * Enriched Doxygen on **stlsoft/util/resizeable_buffer_helpers.hpp** (`resizeable_buffer_resize` parameters, return values, failure policy, caller OOM duty); + * **FAQ.md** — Q6 on the Resizeable Buffer concept; + * **EXAMPLES.md** — catalogues **by_concept/resizeable_buffer**; +* testing: + * Component tests for unixstl `system_traits` home / environment `rb` overloads; + * Component tests for unixstl / winstl filesystem path `rb` overloads; + * Unit tests for unixstl / winstl `ensure_dir_end` / `remove_dir_end` (including size / round-trip, failing-resize OOM reporting, and unixstl strip-all trailing separators); + * Unit tests for `stlsoft::resizeable_buffer_resize` (including a stub with defaulted fill-argument `resize`, G5); + * Unit tests for **winstl** `get_module_directory(rb)` (`auto_buffer` / `vector`, invalid handle, grow-from-empty, pre-sized buffer); + + ## 1.11.1-rc9 - 18th October 2026 * additions: diff --git a/CONCEPTS.md b/CONCEPTS.md new file mode 100644 index 000000000..2a598770e --- /dev/null +++ b/CONCEPTS.md @@ -0,0 +1,196 @@ +# STLSoft - Concepts + + +## Table of Contents + +- [Introduction](#introduction) +- [Classic buffer-and-length OS APIs](#classic-buffer-and-length-os-apis) + - [What goes wrong](#what-goes-wrong) + - [Gold standard: probe, then fill](#gold-standard-probe-then-fill) +- [The Resizeable Buffer concept](#the-resizeable-buffer-concept) + - [Motivation](#motivation) + - [Requirements](#requirements) + - [Models and non-models](#models-and-non-models) + - [Helper: `resizeable_buffer_resize`](#helper-resizeable_buffer_resize) + - [Contract for traits overloads that take an `rb`](#contract-for-traits-overloads-that-take-an-rb) +- [See also](#see-also) + + +## Introduction + +This document records **concepts** used across **STLSoft** — structural +requirements that are not (necessarily) reified as language-level concepts +or base classes, but that several APIs assume. The first entry is the +**Resizeable Buffer** concept used by **UnixSTL** / **WinSTL** +`system_traits` and `filesystem_traits` overloads that accept a generic +buffer rather than a raw pointer and length. + + +## Classic buffer-and-length OS APIs + +A large class of operating-system and C library functions take a +**destination buffer** and a **capacity** (in characters or bytes), and +return either a written length or a failure code. Familiar examples +include `getcwd` / `_getcwd`, `GetCurrentDirectory`, +`GetEnvironmentVariable`, `ExpandEnvironmentStrings`, and +`GetModuleFileName`. + +Callers typically need either: + +* to **fill** a buffer they already own; or +* to **discover** how large a buffer is required before allocating; + + +### What goes wrong + +Historical APIs do not share one contract. Common failure modes include: + +* **Fixed ceilings** — e.g. relying solely on `_MAX_PATH` / `PATH_MAX`, + which is wrong for long paths and for systems without a compile-time + maximum; +* **Truncation without a true demand** — e.g. raw Windows' + [`GetModuleFileName`](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamew) + may truncate into the supplied buffer and return a value equal to the + capacity, without reporting the size that would have been sufficient + (callers then grow-and-retry, or wrap the API); +* **Ambiguous “empty destination”** — passing a **non-null** buffer with + **capacity 0** is *not* a reliable size probe on many APIs: it often + means “write nothing into this empty buffer” and returns `0` or fails, + rather than returning the required length; +* **Inconsistent NUL character accounting** — some returns count the + terminating `NUL`, some do not, and some change meaning between the + “too small” and “success” cases; + + +### Gold standard: probe, then fill + +The normative, unambiguous pattern preferred in **STLSoft** facades is a +**two-phase** protocol: + +1. **Probe** — call with a **null** buffer and a **zero** capacity + (`buff == nullptr` and `cch == 0`). The function returns a **demand**: + a character count of *at least* the full result length **plus one** for + the terminating `NUL` (it may return more than that minimum); +2. **Allocate** — obtain storage of at least that demand (stack, heap, + `auto_buffer`, `vector`, …); +3. **Fill** — call again with a non-null buffer and that capacity. On + success the return is the **length of the string excluding the + `NUL`**; + +Preconditions on the raw overloads take the form: + +```text +0 == cch || nullptr != buff +``` + +so that a zero capacity is allowed **only** when the buffer pointer is +null (the probe), and a non-null buffer always has a positive capacity +for a fill. + +**Do not** probe by passing a non-null buffer with length zero. That +conflates “empty destination” with “size query” and is not portable +across OS APIs. Prefer **null buffer + zero length** for the probe; +wrappers that must paper over defective native APIs (such as +`GetModuleFileName`) should still *present* this gold contract to their +callers. + +This probe-vs-fill convention is the gold standard summarised in the +discussion of required-length probing +([Ask Brave — probe with 0 length vs null buffer](https://search.brave.com/ask?q=What+about+when+wanting+to+probe+the+actual+required+length.+Should+the+caller+pass+0+length%2C+or+NUL...&source=quick-answer-followup&conversation=09aef0cd1c6f4bfdcbbb5fc05969c1dbd832)). + + +## The Resizeable Buffer concept + +### Motivation + +Repeating probe / allocate / fill at every call site is verbose and +easy to get wrong (especially NUL accounting and failure paths). +**STLSoft** therefore offers overloads that take a **resizeable +buffer** — any contiguous, resizable character sequence that can grow +to the demanded size — and perform the two-phase protocol internally. + + +### Requirements + +A type `T` models **Resizeable Buffer** for an element type `CharT` when +it provides at least: + +| Member | Meaning | +| --- | --- | +| `value_type` | Element type (typically `char` or `wchar_t`); | +| `size_type` | Unsigned size type; | +| `size() const` | Number of elements currently managed **including** the trailing `NUL` when the buffer is used as a C-string; | +| `empty() const` | Equivalent to `0 == size()`; | +| `operator[](size_type)` | Mutable access; contiguous storage so that `&rb[0]` addresses the first element when `size() > 0`; | +| `operator[](size_type) const` | Const access (needed by read-only helpers such as `has_dir_end`); | +| `resize(size_type)` | Change the number of managed elements. May return `bool` (`true` on success) or `void` (failure via exception or as documented for that type); | + +**Invariants** assumed by path-oriented APIs: + +* when `!empty()`, `rb[size() - 1] == CharT(0)` (trailing `NUL`); +* `resize` **preserves** existing elements up to the new size when + shrinking or growing (so helpers such as `ensure_dir_end` may append + in place); +* storage is **contiguous**; + + +### Models and non-models + +| Type | Models? | Notes | +| --- | --- | --- | +| `stlsoft::auto_buffer` | ✅ | Preferred; `resize` returns `bool`; | +| `std::vector` | ✅ | `resize` returns `void`; use `resizeable_buffer_resize`; | +| `stlsoft::basic_path_buffer<…>` | ❌ | `size()` **excludes** the `NUL`; off-by-one if passed as an `rb`; | +| `winstl::basic_file_path_buffer<…>` | ❌ | `resize` is a no-op in some specialisations; | + + +### Helper: `resizeable_buffer_resize` + +Declared in +[`stlsoft/util/resizeable_buffer_helpers.hpp`](./include/stlsoft/util/resizeable_buffer_helpers.hpp) +(Doxygen on the file and on the function documents parameters, return +values, failure policy, and the expectation that callers set +`ENOMEM` / `ERROR_OUTOFMEMORY` themselves). + +`stlsoft::resizeable_buffer_resize(rb, newSize)` invokes `rb.resize(newSize)` +whether that member returns `bool` or `void`, and whether `resize` is a +one-argument member or a two-argument member with a defaulted fill (as on +pre-C++11 `std::vector`), and always returns `bool` (`true` on success). +Prefer it over calling `rb.resize` directly in generic code. + + +### Contract for traits overloads that take an `rb` + +Overloads of the form `get_*(…, T_resizeableBuffer& rb)` (and similar) +should follow this gold sequence: + +1. **Probe** via the raw overload with a null buffer and zero capacity; +2. If the probe returns `0` (failure) — **leave `rb` unchanged**, do + **not** take `&rb[0]`, return `0`; +3. If `rb.size() < cchRequired` — call `resizeable_buffer_resize(rb, + cchRequired)`; on failure set an appropriate out-of-memory status + (`ENOMEM` / `ERROR_OUTOFMEMORY`) and return `0` without taking + `&rb[0]` on an empty buffer; +4. **Fill** via the raw overload into `&rb[0]` with capacity `rb.size()`; +5. Optional out-parameters that point into the result (e.g. a basename + separator pointer) are computed **only after** the final fill — never + across a resize; + +On success, the return value matches the raw fill overload (typically +the string length **excluding** the `NUL`). The buffer’s `size()` remains +the allocated demand (typically at least length + 1). + + +## See also + +* Example: + [`examples/by_concept/resizeable_buffer/`](./examples/by_concept/resizeable_buffer/main.cpp); +* Pseudo-struct documentation in + `unixstl::filesystem_traits` / `winstl::filesystem_traits` + (`struct resizeable_buffer` under `\name concepts`); +* Unit tests: + [`test/unit/stlsoft/util/resizeable_buffer_helpers/`](./test/unit/stlsoft/util/resizeable_buffer_helpers/); +* [FAQ.md](./FAQ.md), [INSTALL.md](./INSTALL.md), [EXAMPLES.md](./EXAMPLES.md); + + + diff --git a/Doxyfile b/Doxyfile index 7ba982b98..99277d457 100644 --- a/Doxyfile +++ b/Doxyfile @@ -2,7 +2,7 @@ PROJECT_NAME = "STLSoft" PROJECT_BRIEF = "Robust, lightweight, cross-platform, template software" -PROJECT_NUMBER = 1.11.1-rc9 +PROJECT_NUMBER = 1.11.1-rc10 # Overridden at generation time by generate_doxygen.sh (respects # SIS_CMAKE_BUILD_DIR). diff --git a/EXAMPLES.md b/EXAMPLES.md index e5104e6a9..2b29561ba 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -3,6 +3,7 @@ | Name | Source & Description | Platform | Summary | | ---- | -------------------- | -------- | ------- | | **By concept**: | | | | +| **resizeable_buffer** | [by_concept/resizeable_buffer](./examples/by_concept/resizeable_buffer/main.cpp) | * | Probe-then-fill into `auto_buffer` / `vector` via traits `rb` overloads ([CONCEPTS.md](./CONCEPTS.md)) | | **time_functions** | [by_concept/string_access_shims/time_functions](./examples/by_concept/string_access_shims/time_functions/main.cpp) | * | Format time-related types via string-access shims (e.g. `struct tm`, `std::chrono`) | | | | | | | **By project**: | | | | diff --git a/FAQ.md b/FAQ.md index bc09e57c0..b8a2e2e69 100644 --- a/FAQ.md +++ b/FAQ.md @@ -12,6 +12,7 @@ it will be used to create one. - [Q3: "How do I use STLSoft?"](#q3-how-do-i-use-stlsoft) - [Q4: "Does STLSoft have unit-tests?"](#q4-does-stlsoft-have-unit-tests) - [Q5: "Is STLSoft still relevant given the widespread support for 'Modern C++'?"](#q5-is-stlsoft-still-relevant-given-the-widespread-support-for-modern-c) +- [Q6: "What is a Resizeable Buffer?"](#q6-what-is-a-resizeable-buffer) # FAQs: @@ -65,4 +66,22 @@ This is a good question. The Synesis open-source libraries — including and more — continue to be maintained in light of contemporary C++ practice, while still supporting a wide range of compilers and platforms. That work informs the future of **STLSoft**, which may mean further investment in the existing 1.x stream or, eventually, a 2.x stream less burdened by historical compiler-feature detection. +## Q6: "What is a Resizeable Buffer?" + +A **Resizeable Buffer** is a contiguous, growable character sequence (for +example `stlsoft::auto_buffer` or `std::vector`) that UnixSTL / WinSTL +`system_traits` and `filesystem_traits` overloads accept as a generic +`rb` instead of a raw pointer and length. Those overloads follow a +**probe-then-fill** contract: query demand with a null buffer and zero +capacity, resize only when needed (via +`stlsoft::resizeable_buffer_resize`), set `ENOMEM` / +`ERROR_OUTOFMEMORY` on resize failure, and never take `&rb[0]` on an +empty buffer. + +See [**CONCEPTS.md**](./CONCEPTS.md), the helper +[`stlsoft/util/resizeable_buffer_helpers.hpp`](./include/stlsoft/util/resizeable_buffer_helpers.hpp), +and the example +[`examples/by_concept/resizeable_buffer/`](./examples/by_concept/resizeable_buffer/main.cpp). + + diff --git a/INSTALL.md b/INSTALL.md index 7b04011e3..24c9e4e0e 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -23,17 +23,17 @@ There are three options for installing **STLSoft**, depending on how you obtain If you obtain the latest release as an archive from the [**STLSoft** GitHub Releases](https://github.com/synesissoftware/STLSoft/releases) page (legacy archives may also still appear on [SourceForge](https://sourceforge.net/projects/stlsoft/)): -1. Download the latest distribution, e.g. [**STLSoft-1.11.1-rc9.zip**](https://github.com/synesissoftware/STLSoft/archive/refs/tags/1.11.1-rc9.zip); +1. Download the latest distribution, e.g. [**STLSoft-1.11.1-rc10.zip**](https://github.com/synesissoftware/STLSoft/archive/refs/tags/1.11.1-rc10.zip); 2. Unzip it to a directory of your choice, as in: ```bash $ mkdir -p ~/open-source $ cd ~/open-source -$ unzip STLSoft-1.11.1-rc9.zip +$ unzip STLSoft-1.11.1-rc10.zip ``` -3. Define an environment variable `STLSOFT`, whose value is the directory in which you unzipped it, e.g. `STLSOFT=~/open-source/STLSoft-1.11.1-rc9`, and then specify `$(STLSOFT)/include` (**UNIX**) or `%STLSOFT%\include` (**Windows**) in your project files and makefiles; +3. Define an environment variable `STLSOFT`, whose value is the directory in which you unzipped it, e.g. `STLSOFT=~/open-source/STLSoft-1.11.1-rc10`, and then specify `$(STLSOFT)/include` (**UNIX**) or `%STLSOFT%\include` (**Windows**) in your project files and makefiles; ### Cloning project, using environment variables @@ -100,16 +100,16 @@ $ sudo cmake --install ${SIS_CMAKE_BUILD_DIR:-./_build} --config Release Unless you are certain that your projects, and all the projects upon which they depend, all use **STLSoft** via **CMake** then it is recommended that you define an `STLSOFT` environment variable. For example, if you're using **UNIX** and you install to -**/usr/local/stlsoft/STLSoft-1.11.1-rc9** +**/usr/local/stlsoft/STLSoft-1.11.1-rc10** then you should set the environment variable `STLSOFT` to -**/usr/local/stlsoft/STLSoft-1.11.1-rc9** -in which case the file `stlsoft/stlsoft.h`, for example, will be located in `/usr/local/stlsoft/STLSoft-1.11.1-rc9/include/stlsoft/stlsoft.h`. +**/usr/local/stlsoft/STLSoft-1.11.1-rc10** +in which case the file `stlsoft/stlsoft.h`, for example, will be located in `/usr/local/stlsoft/STLSoft-1.11.1-rc10/include/stlsoft/stlsoft.h`. Conversely, if you're using **Windows** and you install to -**C:\3pty\STLSoft\STLSoft-1.11.1-rc9** +**C:\3pty\STLSoft\STLSoft-1.11.1-rc10** then you should set the environment variable `STLSOFT` to -**C:\3pty\STLSoft\STLSoft-1.11.1-rc9** -in which case the file `stlsoft/stlsoft.h`, for example, will be located in `C:\3pty\STLSoft\STLSoft-1.11.1-rc9\include\stlsoft\stlsoft.h`. +**C:\3pty\STLSoft\STLSoft-1.11.1-rc10** +in which case the file `stlsoft/stlsoft.h`, for example, will be located in `C:\3pty\STLSoft\STLSoft-1.11.1-rc10\include\stlsoft\stlsoft.h`. The makefiles / project files of several other open-source projects expect this symbol, including: diff --git a/NEWS.md b/NEWS.md index f4fa50a6d..74f96de6c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,7 @@ | Date | News Item | Details | | ------------------- | ----------------------------------- | ------- | +| 20th October 2026 | Release of [STLSoft 1.11.1-rc10](https://github.com/synesissoftware/STLSoft/releases/tag/1.11.1-rc10) | Resizeable Buffer concept & traits `rb` hardening; **CONCEPTS.md**; `get_module_directory(rb)`; `resizeable_buffer_resize` docs; example **by_concept/resizeable_buffer**; C1–C5 `rb` / `dir_end` fixes; unixstl strip-all `remove_dir_end`; `readdir_sequence` no-exception soft-fail; full-path OOB size fix; `resizeable_buffer_resize` default-arg `resize` (G5) | | 18th October 2026 | Release of [STLSoft 1.11.1-rc9](https://github.com/synesissoftware/STLSoft/releases/tag/1.11.1-rc9) | `get_current_process_path()` (+ resizeable-buffer overloads) on **unixstl** / **winstl** `system_traits` with component tests; zero-first optional buffers; **unixstl** `wchar_t` path classification and separators; `path_str_*`; wide `get_home_directory()` and `get_environment_variable()`; `\pre` normalised to bare exprs with `nullptr`; subproject version bumps | | 10th October 2026 | Release of [STLSoft 1.11.1-rc8](https://github.com/synesissoftware/STLSoft/releases/tag/1.11.1-rc8) | environment_variable_strtoll()`; `unixstl::filesystem_traits<>::rewind_dir()`; `class_type` and `getenv_s()` empty-variable fixes; component tests; `cmake --build` helper scripts; **run_all_component_tests.sh** / **run_all_automated_tests.sh**; **generate_doxygen.sh**; Linguist classification | | 30th September 2026 | Release of [STLSoft 1.11.1-rc7](https://github.com/synesissoftware/STLSoft/releases/tag/1.11.1-rc7) | counted NULs; `wchar_t` `string_begins_with`; bit intrinsics; `write_binary()`; `file_lines` mmap (~10–20× vs `fgetc`); compare and allocator units; MinGW static runtime; short test directories; nested **test/performance** | diff --git a/README.md b/README.md index 5bd14781c..cdfaa79e5 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ The overarching characteristic of **STLSoft** is that it is *lightweight*. Speci **NOTE**: **STLSoft** is *not* a framework. Each library component is as thin as possible for its given function. The intent is that **STLSoft** components are used as building blocks for higher-level software — applications, classes, libraries, servers. **STLSoft** has been used extensively in all these guises. -Further reading: [FAQ.md](./FAQ.md), [INSTALL.md](./INSTALL.md), [http://www.stlsoft.org/](http://www.stlsoft.org/). +Further reading: [CONCEPTS.md](./CONCEPTS.md), [FAQ.md](./FAQ.md), [INSTALL.md](./INSTALL.md), [http://www.stlsoft.org/](http://www.stlsoft.org/). ## Installation diff --git a/TODO.md b/TODO.md index e96c49588..a904a7782 100644 --- a/TODO.md +++ b/TODO.md @@ -36,9 +36,9 @@ * [ ] Remove **WTL**; * [ ] Resolve finally the structure and semantics of the top-level execution scripts (**run_all_unit_tests.sh**, **run_all_scratch_tests.sh**, **run_all_examples.sh**, **execute_performance_tests.sh**, and Windows `.cmd` counterparts) on all platforms — discovery globs, execute-bit / artifact restore, zero-match failure, verbosity, and CI wiring; * [ ] bring in some essential unit-tests from interal repo; -* [ ] Being the CONCEPTS documentation: +* [ ] Bring in the CONCEPTS documentation: * [ ] auto-buffer; - * [ ] resizeable-buffer; + * [x] ~~~resizeable-buffer~~~ - ✅; * [ ] string-access-shims; * [ ] Null Object (variable); * [ ] Externals (esp. not introducing sub-project's headers proper); @@ -48,7 +48,7 @@ ### 1.11.2 -Dedicated increment: **test analysis and coverage only** — no other product changes in this release. +Dedicated increment: **test analysis and coverage** (primary), plus **Resizeable Buffer** follow-ups deferred from the **1.11.1** lock. * [ ] Existing test consistency (deep analysis); * [ ] Complete coverage of currently tested components (improvement / completion); @@ -58,6 +58,14 @@ Dedicated increment: **test analysis and coverage only** — no other product ch * [ ] Remove **WTL**; * [ ] Resolve finally the structure and semantics of the top-level execution scripts (**run_all_unit_tests.sh**, **run_all_scratch_tests.sh**, **run_all_examples.sh**, **execute_performance_tests.sh**, and Windows `.cmd` counterparts) on all platforms — discovery globs, execute-bit / artifact restore, zero-match failure, verbosity, and CI wiring; * [ ] bring in some essential unit-tests from interal repo; +* [ ] Resizeable Buffer — follow-ups deferred from the **1.11.1** concept/implementation lock (do **not** reopen C1–C5, D1–D4, G2, G3, G5–G7 without cause; gold contract is **CONCEPTS.md** — *The Resizeable Buffer concept* and *Contract for traits overloads that take an `rb`*; helper **stlsoft/util/resizeable_buffer_helpers.hpp**; exemplar `rb` overloads include `get_current_process_path(rb)` and winstl `get_module_directory(rb)`): + * [ ] **G1** — unixstl `system_traits::expand_environment_strings` is **declare-only** (ANSI + wide; both the C-string `(src, buffer, cch)` and `rb` overloads have no definitions in **include/unixstl/system/system_traits.hpp**); WinSTL already implements both (incl. an `rb` overload); callers such as **include/platformstl/filesystem/cwd_stack.hpp** invoke it and will fail to link on UNIX if used — **either** implement (gold probe-then-fill; env/expansion demand must account for trailing NUL; resize failure → `ENOMEM`; unit/component tests) **or** undeclare/remove the stubs and fix/guard callers; same gap is noted under **1.11.6**; + * [ ] **G4** — settle **caller empty-`rb` policy**: whether traits `rb` overloads require / document that callers always pass an empty buffer (`0 == rb.size()`) versus allowing a pre-sized/`capacity`-bearing `rb` (today gold probes with a null buffer + zero capacity and only resizes when `rb.size() < cchRequired`; some older `rb` overloads still probe via `0 == rb.size() ? NULL : &rb[0]`) — record the decision in **CONCEPTS.md** and align overload docs/impls/tests to it; + * [ ] **C6** — Resizeable Buffer is **structurally typed** only: look-alike types can compile without honouring trailing-NUL / `size()`-includes-NUL invariants; **path_buffer** / **file_path_buffer** remain documented non-models in **CONCEPTS.md** — decide whether to add debug asserts (e.g. trailing NUL when `!empty()`), concept-check helpers, and/or harden docs/tests so misuse is caught; + * [ ] **C7** — gold probe-then-fill has **no retry** if the required size changes between the null probe and the fill (TOCTOU on env/cwd/path); rare in practice — either document as accepted limitation in **CONCEPTS.md** or add a bounded grow-and-retry loop with tests; + * [ ] **P1** — several winstl `system_traits` `rb` overloads (e.g. `get_module_filename`, `get_home_directory`, `expand_environment_strings`) always perform a **second** fill API call even when the first call already succeeded with `rb.size() >= cchRequired`; prefer returning the first-call result when the buffer was already large enough (no behaviour change on the normative path) — measure/guard with existing unit coverage; + * [ ] **P2** — unixstl `filesystem_traits` `rb` path helpers (notably relative `get_full_path_name(rb)`) **query twice** and/or allocate heavy scratch (`path_max()`-sized `auto_buffer`, large stack cwd) — reduce duplicate work (compute once into scratch then copy, or reuse probe data) without changing the public contract; add/extend component tests under **test/component/unixstl/filesystem/filesystem_traits/**; + * [ ] **P3** — `ensure_dir_end(rb)` grows by exactly **+1**, which forces a heap realloc when an `auto_buffer` is already on its internal array boundary; internal callers often avoid the `rb` form for this reason — consider amortized growth or document that pointer/`&sz[0]` forms remain preferred for hot paths; cover with unit tests if behaviour changes; ### 1.11.3 @@ -82,7 +90,8 @@ Dedicated increment: **test analysis and coverage only** — no other product ch ### 1.11.6 -* [ ] Flesh out declared-but-undefined `unixstl::system_traits` methods (Linux system calls; emulations/workarounds elsewhere) — see also [**KNOWN_ISSUES.md**](./KNOWN_ISSUES.md); +* [ ] Flesh out declared-but-undefined `unixstl::system_traits` methods (Linux system calls; emulations/workarounds elsewhere) — see also [**KNOWN_ISSUES.md**](./KNOWN_ISSUES.md): + * [ ] `expand_environment_strings` (ANSI + wide; C-string and `rb` overloads) — implement or undeclare; full pickup brief under **1.11.2** Resizeable Buffer **G1**; ### 1.11.7 diff --git a/examples/by_concept/CMakeLists.txt b/examples/by_concept/CMakeLists.txt index 52b1a73c7..e0d99ef8d 100644 --- a/examples/by_concept/CMakeLists.txt +++ b/examples/by_concept/CMakeLists.txt @@ -1,2 +1,3 @@ # SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +add_subdirectory(resizeable_buffer) add_subdirectory(string_access_shims) diff --git a/examples/by_concept/resizeable_buffer/CMakeLists.txt b/examples/by_concept/resizeable_buffer/CMakeLists.txt new file mode 100644 index 000000000..454f5cdf2 --- /dev/null +++ b/examples/by_concept/resizeable_buffer/CMakeLists.txt @@ -0,0 +1 @@ +define_example_program(resizeable_buffer main.cpp) diff --git a/examples/by_concept/resizeable_buffer/main.cpp b/examples/by_concept/resizeable_buffer/main.cpp new file mode 100644 index 000000000..ffc52ea2c --- /dev/null +++ b/examples/by_concept/resizeable_buffer/main.cpp @@ -0,0 +1,159 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: by_concept/resizeable_buffer/main.cpp + * + * Purpose: C++ example program demonstrating the Resizeable Buffer + * concept: probe-then-fill into `stlsoft::auto_buffer` and + * `std::vector` via `platformstl` traits `rb` overloads (see + * CONCEPTS.md). + * + * Created: 15th October 2026 + * Updated: 15th October 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include +#include + +#include +#include + +#include +#include + +#include +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * types + */ + +typedef platformstl::system_traits system_traits_t; +typedef platformstl::filesystem_traits filesystem_traits_t; +typedef stlsoft::auto_buffer ab_t; +typedef std::vector vec_t; + + +/* ///////////////////////////////////////////////////////////////////////// + * helpers + */ + +static +void +print_cstr_field( + char const* label +, char const* s +, size_t n +) +{ + std::cout + << label + << " (" + << n + << " chars): '" + << (NULL != s ? s : "") + << "'" + << std::endl; +} + + +/* ///////////////////////////////////////////////////////////////////////// + * main() + */ + +int main(int /* argc */, char* /* argv */[]) +{ + /* Preferred model: stlsoft::auto_buffer — empty demand, traits resize. + */ + { + ab_t home(0); + size_t const n = system_traits_t::get_home_directory(home); + + if (0 == n) + { + std::cerr << "get_home_directory failed" << std::endl; + } + else + { + print_cstr_field("home (auto_buffer)", home.data(), n); + } + } + + /* Also a model: std::vector — same rb overload contract. + */ + { + vec_t cwd; + size_t const n = filesystem_traits_t::get_current_directory(cwd); + + if (0 == n) + { + std::cerr << "get_current_directory failed" << std::endl; + } + else + { + print_cstr_field("cwd (vector)", &cwd[0], n); + } + } + + /* Environment value into an empty auto_buffer (traits resize via + * resizeable_buffer_resize internally). + */ + { + char const name[] = "PATH"; + ab_t path(0); + size_t const n = system_traits_t::get_environment_variable(name, path); + + if (0 == n) + { + std::cerr << "get_environment_variable(\"" << name << "\") failed" << std::endl; + } + else + { + print_cstr_field("PATH (auto_buffer)", path.data(), n); + } + } + + /* Direct use of the helper: grow a vector before writing into it. + */ + { + vec_t scratch; + + if (!stlsoft::resizeable_buffer_resize(scratch, 8)) + { + std::cerr << "resizeable_buffer_resize failed" << std::endl; + + return EXIT_FAILURE; + } + + scratch[0] = 'r'; + scratch[1] = 'b'; + scratch[2] = '\0'; + + print_cstr_field("helper grown vector", &scratch[0], 2); + } + + /* Path helper on an rb: append a directory separator when missing. + */ + { + vec_t dir; + size_t const n = filesystem_traits_t::get_current_directory(dir); + + if (0 != n) + { + size_t const n2 = filesystem_traits_t::ensure_dir_end(dir); + + print_cstr_field("cwd with dir-end", &dir[0], n2); + } + } + + return EXIT_SUCCESS; +} + + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/include/stlsoft/stlsoft.h b/include/stlsoft/stlsoft.h index eaffb4b3a..766798625 100644 --- a/include/stlsoft/stlsoft.h +++ b/include/stlsoft/stlsoft.h @@ -5,7 +5,7 @@ * and platform discriminations, and definitions of types. * * Created: 15th January 2002 - * Updated: 18th October 2026 + * Updated: 20th October 2026 * * Home: http://stlsoft.org/ * @@ -424,12 +424,13 @@ # define _STLSOFT_VER_1_11_1_RC7 0x010B01C7 /*!< Version 1.11.1 rc 7 (30th September 2026) */ # define _STLSOFT_VER_1_11_1_RC8 0x010B01C8 /*!< Version 1.11.1 rc 8 (10th October 2026) */ # define _STLSOFT_VER_1_11_1_RC9 0x010B01C9 /*!< Version 1.11.1 rc 9 (18th October 2026) */ +# define _STLSOFT_VER_1_11_1_RC10 0x010B01CA /*!< Version 1.11.1 rc 10 (20th October 2026) */ #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ #define _STLSOFT_VER_MAJOR 1 #define _STLSOFT_VER_MINOR 11 #define _STLSOFT_VER_PATCH 1 -#define _STLSOFT_VER_ALPHABETA 0xC9 +#define _STLSOFT_VER_ALPHABETA 0xCA #define _STLSOFT_VER \ (0\ diff --git a/include/stlsoft/util/resizeable_buffer_helpers.hpp b/include/stlsoft/util/resizeable_buffer_helpers.hpp index 079fef90b..fa2f51c38 100644 --- a/include/stlsoft/util/resizeable_buffer_helpers.hpp +++ b/include/stlsoft/util/resizeable_buffer_helpers.hpp @@ -4,11 +4,11 @@ * Purpose: Utility functions for working with resizeable-buffers. * * Created: 24th December 2020 - * Updated: 20th March 2025 + * Updated: 16th October 2026 * * Home: http://stlsoft.org/ * - * Copyright (c) 2020-2025, Matthew Wilson and Synesis Information Systems + * Copyright (c) 2020-2026, Matthew Wilson and Synesis Information Systems * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,9 +42,28 @@ /** \file stlsoft/util/resizeable_buffer_helpers.hpp * - * \brief [C++] Utility functions for working with resizeable-buffers - * templates + * \brief [C++] Helpers for types that model the Resizeable Buffer + * concept * (\ref group__library__Utility "Utility" Library). + * + * A Resizeable Buffer is a contiguous, growable character sequence + * used by UnixSTL / WinSTL system_traits and + * filesystem_traits overloads that take a generic + * rb rather than a raw pointer and length. Preferred models + * include \c stlsoft::auto_buffer and \c std::vector; see + * CONCEPTS.md in the project root for the full + * requirements table, non-models, and the probe-then-fill gold contract + * for traits overloads. + * + * This header provides \c stlsoft::resizeable_buffer_resize, which adapts + * T::resize whether that member returns \c bool or \c void + * (and whether \c resize is a one-argument member or a two-argument member + * with a defaulted fill value, as on pre-C++11 \c std::vector) so generic + * code has one failure channel (\c bool). + * + * \see CONCEPTS.md — The Resizeable Buffer concept + * \see examples/by_concept/resizeable_buffer/main.cpp + * \see test/unit/stlsoft/util/resizeable_buffer_helpers/ */ #ifndef STLSOFT_INCL_STLSOFT_UTIL_HPP_RESIZEABLE_BUFFER_HELPERS @@ -52,9 +71,9 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define STLSOFT_VER_STLSOFT_UTIL_HPP_RESIZEABLE_BUFFER_HELPERS_MAJOR 1 -# define STLSOFT_VER_STLSOFT_UTIL_HPP_RESIZEABLE_BUFFER_HELPERS_MINOR 0 -# define STLSOFT_VER_STLSOFT_UTIL_HPP_RESIZEABLE_BUFFER_HELPERS_REVISION 4 -# define STLSOFT_VER_STLSOFT_UTIL_HPP_RESIZEABLE_BUFFER_HELPERS_EDIT 7 +# define STLSOFT_VER_STLSOFT_UTIL_HPP_RESIZEABLE_BUFFER_HELPERS_MINOR 1 +# define STLSOFT_VER_STLSOFT_UTIL_HPP_RESIZEABLE_BUFFER_HELPERS_REVISION 1 +# define STLSOFT_VER_STLSOFT_UTIL_HPP_RESIZEABLE_BUFFER_HELPERS_EDIT 9 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -69,9 +88,9 @@ # pragma message(__FILE__) #endif /* STLSOFT_TRACE_INCLUDE */ -#ifndef STLSOFT_INCL_STLSOFT_META_HPP_SIZE_OF -# include -#endif /* !STLSOFT_INCL_STLSOFT_META_HPP_SIZE_OF */ +#ifndef STLSOFT_INCL_STLSOFT_META_HPP_YESNO +# include +#endif /* !STLSOFT_INCL_STLSOFT_META_HPP_YESNO */ /* ///////////////////////////////////////////////////////////////////////// @@ -91,11 +110,17 @@ namespace stlsoft #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION STLSOFT_OPEN_WORKER_NS_(ximpl_stlsoft_util_rb_helpers_) -#if 0 -#elif defined(STLSOFT_COMPILER_IS_MSVC) && \ - _MSC_VER < 1310 +# if 0 +# elif defined(STLSOFT_COMPILER_IS_MSVC) && \ + _MSC_VER < 1310 - /* Have to muck around with state here to get the old girl to work */ + /* Have to muck around with state here to get the old girl to work. + * + * VC6 cannot use the SFINAE / direct-call path below. Keep + * pointer-to-member overload sets, and add a two-argument void + * resize (pre-C++11 vector shape) without adding a two-argument + * bool resize (that would make auto_buffer's &resize ambiguous). + */ template< ss_typename_param_k T_rb @@ -106,6 +131,8 @@ struct rb_helper_traits_vc6 T_rb* prb; public: + typedef ss_typename_type_k T_rb::value_type value_type; + ss_explicit_k rb_helper_traits_vc6( T_rb& rb @@ -116,7 +143,7 @@ struct rb_helper_traits_vc6 bool do_resize( size_t newSize - , bool (T_rb::*pfn)(size_t) + , bool (T_rb::*pfn)(size_t) ) { return (prb->*pfn)(newSize); @@ -125,13 +152,24 @@ struct rb_helper_traits_vc6 bool do_resize( size_t newSize - , void (T_rb::*pfn)(size_t) + , void (T_rb::*pfn)(size_t) ) { (prb->*pfn)(newSize); return true; } + + bool + do_resize( + size_t newSize + , void (T_rb::*pfn)(size_t, value_type) + ) + { + (prb->*pfn)(newSize, value_type()); + + return true; + } }; template< @@ -145,45 +183,86 @@ traits_for( { return rb_helper_traits_vc6(rb); } -#else +# else /* ? compiler */ + + /* Prefer a direct rb.resize(newSize) call so that: + * - one-argument resize(size) works; + * - two-argument resize(size, value = value()) works via the default + * (pre-C++11 vector); + * - C++11 vector's two overloads are not made ambiguous by forming + * &T::resize against competing member-pointer parameter types. + * + * Detect a one-argument bool / ss_bool_t-returning resize via SFINAE on + * the member-pointer type so auto_buffer's failure channel is preserved. + */ template< ss_typename_param_k T_resizeableBuffer -, ss_typename_param_k T_character > -struct rb_helper_traits +struct rb_has_bool_one_arg_resize { - typedef T_character char_type; - typedef T_resizeableBuffer buffer_type; - - typedef bool (buffer_type::*pfn_b)(size_t); - typedef void (buffer_type::*pfn_v)(size_t); - + typedef char yes_type_; + typedef char (&no_type_)[2]; + + template< + ss_typename_param_k U + , ss_bool_t (U::*)(ss_size_t) + > + struct ptm + {}; + + template< + ss_typename_param_k U + > static - bool - do_resize( - buffer_type& buffer - , size_t newSize - , pfn_b pfn - ) - { - return (buffer.*pfn)(newSize); - } - + yes_type_ + test( + ptm* + ); + + template< + ss_typename_param_k U + > static - bool - do_resize( - buffer_type& buffer - , size_t newSize - , pfn_v pfn - ) - { - (buffer.*pfn)(newSize); + no_type_ + test( + ... + ); - return true; - } + enum { value = sizeof(test(0)) == sizeof(yes_type_) }; }; -#endif + +template< + ss_typename_param_k T_resizeableBuffer +> +inline +bool +rb_invoke_resize( + T_resizeableBuffer& rb +, ss_size_t newSize +, yes_type +) +{ + return rb.resize(newSize); +} + +template< + ss_typename_param_k T_resizeableBuffer +> +inline +bool +rb_invoke_resize( + T_resizeableBuffer& rb +, ss_size_t newSize +, no_type +) +{ + rb.resize(newSize); + + return true; +} +# endif /* compiler */ + STLSOFT_CLOSE_WORKER_NS_(ximpl_stlsoft_util_rb_helpers_) #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -192,11 +271,53 @@ STLSOFT_CLOSE_WORKER_NS_(ximpl_stlsoft_util_rb_helpers_) * functions */ -/** Invokes resize() on the given buffer, regardless of - * whether the buffer's type's method returns \c bool or \c void +/** Invokes resize() on a Resizeable Buffer, unifying + * bool- and void-returning members. + * + * Prefer this helper over calling rb.resize(newSize) + * directly in generic code (traits templates, concept examples) so that + * failure is always reported as false without caring which + * overload of resize the model provides. + * + * \tparam T_resizeableBuffer A type that models Resizeable Buffer + * for its value_type. Must provide a nested + * value_type and a resize member that can be + * invoked as rb.resize(newSize) — either a one-argument + * resize(size_type) returning \c bool / \c ss_bool_t or + * \c void, or a two-argument resize(size_type, value_type) + * with a defaulted fill (as on pre-C++11 \c std::vector). * - * \param rb Reference to the resizeable buffer instance - * \param newSize The new size + * \param rb Reference to the buffer instance to resize + * \param newSize The requested number of elements after the call + * (for path/string use this is typically the probed demand + * including the trailing NUL) + * + * \retval true The resize succeeded. For \c void-returning + * resize, success means the call returned without + * throwing (models such as \c std::vector typically throw + * std::bad_alloc on exhaustion). For \c bool-returning + * resize (e.g. \c stlsoft::auto_buffer), the member's + * return value is propagated. + * \retval false The resize failed. Only reachable when + * T_resizeableBuffer::resize returns \c bool / \c ss_bool_t + * and that call returned \c false. On failure the buffer is left + * unchanged when the model obeys the strong guarantee (as + * \c stlsoft::auto_buffer does). + * + * \note This function does not set errno / + * GetLastError. Callers that implement the traits + * rb gold contract should, on \c false, set + * ENOMEM or ERROR_OUTOFMEMORY (as + * appropriate) and return without taking &rb[0] on an + * empty buffer. + * + * \note Element preservation, contiguity, and trailing-NUL invariants + * are properties of the buffer model, not of this helper; see + * CONCEPTS.md. + * + * \see stlsoft::auto_buffer::resize + * \see CONCEPTS.md — Helper: resizeable_buffer_resize and + * Contract for traits overloads that take an rb */ template< ss_typename_param_k T_resizeableBuffer @@ -222,17 +343,19 @@ resizeable_buffer_resize( return traits_for(rb).do_resize(newSize, &T_resizeableBuffer::resize); #else - typedef T_resizeableBuffer buffer_t; - typedef ss_typename_type_k buffer_t::value_type value_t; - STLSOFT_WORKER_NS_USING_(ximpl_stlsoft_util_rb_helpers_, rb_helper_traits); + STLSOFT_WORKER_NS_USING_(ximpl_stlsoft_util_rb_helpers_, rb_has_bool_one_arg_resize); + STLSOFT_WORKER_NS_USING_(ximpl_stlsoft_util_rb_helpers_, rb_invoke_resize); - typedef rb_helper_traits< - buffer_t - , value_t - > traits_t; + typedef value_to_yesno_type< + rb_has_bool_one_arg_resize::value + > yesno_t; - return traits_t::do_resize(rb, newSize, &T_resizeableBuffer::resize); + return rb_invoke_resize( + rb + , newSize + , ss_typename_type_k yesno_t::type() + ); #endif #ifdef STLSOFT_COMPILER_IS_MSVC @@ -244,7 +367,9 @@ resizeable_buffer_resize( #endif /* compiler */ } -/* ////////////////////////////////////////////////////////////////////// */ +/* ///////////////////////////////////////////////////////////////////////// + * namespace + */ #ifndef STLSOFT_NO_NAMESPACE } // namespace stlsoft @@ -262,4 +387,3 @@ resizeable_buffer_resize( #endif /* !STLSOFT_INCL_STLSOFT_UTIL_HPP_RESIZEABLE_BUFFER_HELPERS */ /* ///////////////////////////// end of file //////////////////////////// */ - diff --git a/include/unixstl/filesystem/filesystem_traits.hpp b/include/unixstl/filesystem/filesystem_traits.hpp index a839e6c37..1d643c05d 100644 --- a/include/unixstl/filesystem/filesystem_traits.hpp +++ b/include/unixstl/filesystem/filesystem_traits.hpp @@ -5,7 +5,7 @@ * Unicode specialisations thereof. * * Created: 15th November 2002 - * Updated: 15th October 2026 + * Updated: 19th October 2026 * * Thanks: To Sergey Nikulov, for spotting a preprocessor typo that broke * GCC -pedantic; to Michal Makowski and Zar Eindl for reporting @@ -60,8 +60,8 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_MAJOR 5 # define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_MINOR 0 -# define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_REVISION 1 -# define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_EDIT 193 +# define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_REVISION 2 +# define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_EDIT 194 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -427,7 +427,7 @@ struct filesystem_traits T_resizeableBuffer const& rb ); - /// Removes the path name separator from the end of \c dir, if present + /// Removes all trailing path name separators from the end of \c dir /// /// \pre nullptr != dir /// @@ -438,12 +438,12 @@ struct filesystem_traits char_type* dir ); - /// Removes the path name separator from the end of \c dir, if present + /// Removes all trailing path name separators from the end of \c dir /// /// \param path Pointer to characters of path to be examined /// \param len Number of characters in \c path to be examined /// \param pLenToDecrease An optional pointer to a variable that will - /// be decremented if a path name separator is removed + /// be decremented once per path name separator removed /// /// \return \c dir /// @@ -459,7 +459,7 @@ struct filesystem_traits , size_type* pLenToDecrease ); - /// Removes the path name separator from the end of \c rb, if present + /// Removes all trailing path name separators from the end of \c rb /// /// \param rb a reference to a \c resizeable_buffer /// @@ -1003,6 +1003,15 @@ struct filesystem_traits size_type const len = str_len(dir); + /* Empty string: leave unchanged (and avoid the capacity-0 append + * path, which GCC -Warray-bounds may flag when inlined into a + * tiny stack buffer). + */ + if (0 == len) + { + return dir; + } + return ensure_dir_end(dir, len, 0, NULL); } @@ -1070,10 +1079,15 @@ struct filesystem_traits { size_type const n = rb.size(); - if (0 != n) + /* Require more than a lone NUL so "" stays "" (matches + * winstl and the C-string overload). + */ + if (n > 1) { if (!resizeable_buffer_resize(rb, n + 1)) { + set_last_error(ENOMEM); + return 0; } @@ -1185,7 +1199,7 @@ struct filesystem_traits pLenToDecrease = &dummy; } - if (0 != len) + for (; 0 != len; ) { char_type& last = dir[len - 1]; @@ -1194,6 +1208,11 @@ struct filesystem_traits last = char_type(0); --*pLenToDecrease; + --len; + } + else + { + break; } } @@ -1209,19 +1228,30 @@ struct filesystem_traits T_resizeableBuffer& rb ) { - if (has_dir_end(rb)) + size_type const n = rb.size(); + + if (0 == n) { - size_type const n = rb.size(); + return 0; + } + + size_type lenToDecrease = n - 1; + + remove_dir_end(rb.data(), n - 1, &lenToDecrease); - if (!resizeable_buffer_resize(rb, n - 1)) + size_type const cchRequired = lenToDecrease + 1; + + if (rb.size() != cchRequired) + { + if (!resizeable_buffer_resize(rb, cchRequired)) { + set_last_error(ENOMEM); + return 0; } - - rb[n - 2] = char_type(0); } - return rb.empty() ? 0 : rb.size() - 1; + return lenToDecrease; } public: // path classification and analysis @@ -1821,6 +1851,8 @@ struct filesystem_traits if (!resizeable_buffer_resize(cwd, n2 + len + 1)) { + set_last_error(ENOMEM); + return 0; } @@ -2026,7 +2058,8 @@ return 0; if ('\0' != fileName[len]) { - buffer_type_ fileName_(1 + (len - 1)); + // len characters of payload + trailing NUL + buffer_type_ fileName_(1 + len); // May be being compiled absent exception support, so need to check the // file path buffers. (This _could_ be done with a compile-time #ifdef, @@ -2041,9 +2074,10 @@ return 0; } else { + char_copy(&fileName_[0], fileName, len); fileName_[len] = '\0'; - return get_full_path_name_impl2(char_copy(&fileName_[0], fileName, len) + return get_full_path_name_impl2(fileName_.data() , len , buffer , cchBuffer); @@ -2124,13 +2158,27 @@ return 0; , T_resizeableBuffer& rb ) { - size_type const cchRequired = get_full_path_name(fileName, static_cast(NULL), 0); + size_type const cchRequired = get_full_path_name( + fileName + , static_cast(NULL) + , 0 + ); - if (!resizeable_buffer_resize(rb, cchRequired)) + if (0 == cchRequired) { return 0; } + if (rb.size() < cchRequired) + { + if (!resizeable_buffer_resize(rb, cchRequired)) + { + set_last_error(ENOMEM); + + return 0; + } + } + return get_full_path_name(fileName, &rb[0], rb.size()); } @@ -2240,13 +2288,26 @@ return 0; T_resizeableBuffer& rb ) { - size_type const cchRequired = get_current_directory(static_cast(NULL), 0); + size_type const cchRequired = get_current_directory( + static_cast(NULL) + , 0 + ); - if (!resizeable_buffer_resize(rb, cchRequired)) + if (0 == cchRequired) { return 0; } + if (rb.size() < cchRequired) + { + if (!resizeable_buffer_resize(rb, cchRequired)) + { + set_last_error(ENOMEM); + + return 0; + } + } + return get_current_directory(&rb[0], rb.size()); } @@ -2784,6 +2845,15 @@ struct filesystem_traits size_type const len = str_len(dir); + /* Empty string: leave unchanged (and avoid the capacity-0 append + * path, which GCC -Warray-bounds may flag when inlined into a + * tiny stack buffer). + */ + if (0 == len) + { + return dir; + } + return ensure_dir_end(dir, len, 0, NULL); } @@ -2851,10 +2921,15 @@ struct filesystem_traits { size_type const n = rb.size(); - if (0 != n) + /* Require more than a lone NUL so "" stays "" (matches + * winstl and the C-string overload). + */ + if (n > 1) { if (!resizeable_buffer_resize(rb, n + 1)) { + set_last_error(ENOMEM); + return 0; } @@ -2966,7 +3041,7 @@ struct filesystem_traits pLenToDecrease = &dummy; } - if (0 != len) + for (; 0 != len; ) { char_type& last = dir[len - 1]; @@ -2975,6 +3050,11 @@ struct filesystem_traits last = char_type(0); --*pLenToDecrease; + --len; + } + else + { + break; } } @@ -2990,19 +3070,30 @@ struct filesystem_traits T_resizeableBuffer& rb ) { - if (has_dir_end(rb)) + size_type const n = rb.size(); + + if (0 == n) { - size_type const n = rb.size(); + return 0; + } + + size_type lenToDecrease = n - 1; - if (!resizeable_buffer_resize(rb, n - 1)) + remove_dir_end(rb.data(), n - 1, &lenToDecrease); + + size_type const cchRequired = lenToDecrease + 1; + + if (rb.size() != cchRequired) + { + if (!resizeable_buffer_resize(rb, cchRequired)) { + set_last_error(ENOMEM); + return 0; } - - rb[n - 2] = char_type(0); } - return rb.empty() ? 0 : rb.size() - 1; + return lenToDecrease; } public: // path classification and analysis diff --git a/include/unixstl/filesystem/readdir_sequence.hpp b/include/unixstl/filesystem/readdir_sequence.hpp index b9b971390..9a3f55b6f 100644 --- a/include/unixstl/filesystem/readdir_sequence.hpp +++ b/include/unixstl/filesystem/readdir_sequence.hpp @@ -4,7 +4,7 @@ * Purpose: readdir_sequence class. * * Created: 15th January 2002 - * Updated: 29th May 2025 + * Updated: 16th October 2026 * * Home: http://stlsoft.org/ * @@ -53,8 +53,8 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_READDIR_SEQUENCE_MAJOR 5 # define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_READDIR_SEQUENCE_MINOR 6 -# define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_READDIR_SEQUENCE_REVISION 1 -# define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_READDIR_SEQUENCE_EDIT 178 +# define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_READDIR_SEQUENCE_REVISION 2 +# define UNIXSTL_VER_UNIXSTL_FILESYSTEM_HPP_READDIR_SEQUENCE_EDIT 179 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -631,14 +631,9 @@ readdir_sequence::prepare_directory_( STLSOFT_THROW_X(readdir_sequence_exception("failed to obtain full path of search directory", e, directory)); #else /* ? STLSOFT_CF_EXCEPTION_SUPPORT */ - // "+nul" - if (!path.resize(1 + n)) - { - return string_type(); - } - - traits_type::char_copy(&path[0], directory, n); - path[n] = \'0'; + // Soft-fail parallel to the throw above (and to the + // allocation-failure return earlier in this function) + return string_type(); #endif /* STLSOFT_CF_EXCEPTION_SUPPORT */ } } diff --git a/include/unixstl/system/system_traits.hpp b/include/unixstl/system/system_traits.hpp index e5ac6dc9f..027eb1120 100644 --- a/include/unixstl/system/system_traits.hpp +++ b/include/unixstl/system/system_traits.hpp @@ -352,9 +352,17 @@ struct system_traits , size_type cchBuffer ); - /// Gets the full path name of the current user's home directory + /// Gets the full path name of the current user's home directory into a + /// resizeable buffer (probe-then-fill; see CONCEPTS.md). /// - /// \param rb Reference to a resizeable buffer + /// \param rb Reference to a resizeable buffer that will be resized as + /// required to hold the path and a terminating NUL character; + /// + /// \return The length of the path (excluding the NUL character) on + /// success; 0 on failure. On query failure \c rb is left unchanged; + /// on resize failure ENOMEM is set and \c rb is left + /// unchanged. In neither failure case is &rb[0] taken + /// on an empty buffer. template< ss_typename_param_k T_resizeableBuffer > @@ -427,11 +435,19 @@ struct system_traits , size_type cchBuffer ); - /// Gets an environment variable into the given buffer + /// Gets an environment variable into a resizeable buffer + /// (probe-then-fill; see CONCEPTS.md). /// - /// \param name The name of the variable to find + /// \param name The name of the variable to find; /// \param rb Reference to a resizeable buffer into which the - /// environment variable value is to be written + /// environment variable value is to be written. Resized to the + /// probed value length plus one for the terminating NUL (the raw + /// null-buffer probe returns the length excluding the NUL); + /// + /// \return The length of the value (excluding the NUL character) on + /// success; 0 on failure (missing variable, or resize failure). On + /// query failure \c rb is left unchanged; on resize failure + /// ENOMEM is set and \c rb is left unchanged. template< ss_typename_param_k T_resizeableBuffer > @@ -748,9 +764,25 @@ struct system_traits T_resizeableBuffer& rb ) { - size_type const cchRequired = get_home_directory(static_cast(NULL), 0); + size_type const cchRequired = get_home_directory( + static_cast(NULL) + , 0 + ); - rb.resize(cchRequired); + if (0 == cchRequired) + { + return 0; + } + + if (rb.size() < cchRequired) + { + if (!STLSOFT_NS_QUAL(resizeable_buffer_resize)(rb, cchRequired)) + { + set_last_error(ENOMEM); + + return 0; + } + } return get_home_directory(&rb[0], rb.size()); } @@ -851,14 +883,31 @@ struct system_traits , T_resizeableBuffer& rb ) { - size_type const cchRequired = get_environment_variable(name, static_cast(NULL), 0); - - if (0 == cchRequired) + /* The raw null-buffer probe returns the value length excluding the + * NUL; the resizeable-buffer overload needs demand including NUL. + */ + size_type const cchValue = get_environment_variable( + name + , static_cast(NULL) + , 0 + ); + + if (0 == cchValue) { return 0; } - rb.resize(cchRequired); + size_type const cchRequired = cchValue + 1; + + if (rb.size() < cchRequired) + { + if (!STLSOFT_NS_QUAL(resizeable_buffer_resize)(rb, cchRequired)) + { + set_last_error(ENOMEM); + + return 0; + } + } return get_environment_variable(name, &rb[0], rb.size()); } diff --git a/include/winstl/filesystem/filesystem_traits.hpp b/include/winstl/filesystem/filesystem_traits.hpp index 70c1ec21c..7b81a5595 100644 --- a/include/winstl/filesystem/filesystem_traits.hpp +++ b/include/winstl/filesystem/filesystem_traits.hpp @@ -5,7 +5,7 @@ * Unicode specialisations thereof. * * Created: 15th November 2002 - * Updated: 15th October 2026 + * Updated: 19th October 2026 * * Home: http://stlsoft.org/ * @@ -55,8 +55,8 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define WINSTL_VER_WINSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_MAJOR 4 # define WINSTL_VER_WINSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_MINOR 22 -# define WINSTL_VER_WINSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_REVISION 11 -# define WINSTL_VER_WINSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_EDIT 195 +# define WINSTL_VER_WINSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_REVISION 12 +# define WINSTL_VER_WINSTL_FILESYSTEM_HPP_FILESYSTEM_TRAITS_EDIT 196 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -1011,6 +1011,15 @@ struct filesystem_traits size_type const len = str_len(dir); + /* Empty string: leave unchanged (and avoid the capacity-0 append + * path, which GCC -Warray-bounds may flag when inlined into a + * tiny stack buffer). + */ + if (0 == len) + { + return dir; + } + return ensure_dir_end(dir, len, 0, NULL); } @@ -1082,6 +1091,8 @@ struct filesystem_traits { if (!resizeable_buffer_resize(rb, n + 1)) { + set_last_error(ERROR_OUTOFMEMORY); + return 0; } @@ -1222,20 +1233,30 @@ struct filesystem_traits T_resizeableBuffer& rb ) { - size_type len = rb.size(); + size_type const n = rb.size(); - if (0 == len) + if (0 == n) { return 0; } - else - { - size_type lenToDecrease = len - 1; - remove_dir_end(rb.data(), len - 1, &lenToDecrease); + size_type lenToDecrease = n - 1; + + remove_dir_end(rb.data(), n - 1, &lenToDecrease); - return lenToDecrease; + size_type const cchRequired = lenToDecrease + 1; + + if (rb.size() != cchRequired) + { + if (!resizeable_buffer_resize(rb, cchRequired)) + { + set_last_error(ERROR_OUTOFMEMORY); + + return 0; + } } + + return lenToDecrease; } public: // path classification and analysis @@ -1834,7 +1855,8 @@ struct filesystem_traits if ('\0' != fileName[len]) { - buffer_type_ fileName_(1 + (len - 1)); + // len characters of payload + trailing NUL + buffer_type_ fileName_(1 + len); // May be being compiled absent exception support, so need to check the // file path buffers. (This _could_ be done with a compile-time #ifdef, @@ -1849,10 +1871,11 @@ struct filesystem_traits } else { + STLSOFT_API_EXTERNAL_memfns_memcpy(&fileName_[0], fileName, sizeof(char_type) * len); fileName_[len] = '\0'; return get_full_path_name_impl( - static_cast(STLSOFT_API_EXTERNAL_memfns_memcpy(&fileName_[0], fileName, sizeof(char_type) * len)) + fileName_.data() , len , buffer , cchBuffer @@ -2015,13 +2038,27 @@ struct filesystem_traits , T_resizeableBuffer& rb ) { - size_type const cchRequired = get_full_path_name(fileName, static_cast(NULL), 0); + size_type const cchRequired = get_full_path_name( + fileName + , static_cast(NULL) + , 0 + ); - if (!resizeable_buffer_resize(rb, cchRequired)) + if (0 == cchRequired) { return 0; } + if (rb.size() < cchRequired) + { + if (!resizeable_buffer_resize(rb, cchRequired)) + { + set_last_error(ERROR_OUTOFMEMORY); + + return 0; + } + } + return get_full_path_name(fileName, &rb[0], rb.size()); } @@ -2067,13 +2104,27 @@ struct filesystem_traits , T_resizeableBuffer& rb ) { - size_type const cchRequired = get_short_path_name(fileName, static_cast(NULL), 0); + size_type const cchRequired = get_short_path_name( + fileName + , static_cast(NULL) + , 0 + ); - if (!resizeable_buffer_resize(rb, cchRequired)) + if (0 == cchRequired) { return 0; } + if (rb.size() < cchRequired) + { + if (!resizeable_buffer_resize(rb, cchRequired)) + { + set_last_error(ERROR_OUTOFMEMORY); + + return 0; + } + } + return get_short_path_name(fileName, &rb[0], rb.size()); } @@ -2151,13 +2202,26 @@ struct filesystem_traits T_resizeableBuffer& rb ) { - size_type const cchRequired = get_current_directory(static_cast(NULL), 0); + size_type const cchRequired = get_current_directory( + static_cast(NULL) + , 0 + ); - if (!resizeable_buffer_resize(rb, cchRequired)) + if (0 == cchRequired) { return 0; } + if (rb.size() < cchRequired) + { + if (!resizeable_buffer_resize(rb, cchRequired)) + { + set_last_error(ERROR_OUTOFMEMORY); + + return 0; + } + } + return get_current_directory(&rb[0], rb.size()); } @@ -2628,6 +2692,15 @@ struct filesystem_traits size_type const len = str_len(dir); + /* Empty string: leave unchanged (and avoid the capacity-0 append + * path, which GCC -Warray-bounds may flag when inlined into a + * tiny stack buffer). + */ + if (0 == len) + { + return dir; + } + return ensure_dir_end(dir, len, 0, NULL); } @@ -2699,6 +2772,8 @@ struct filesystem_traits { if (!resizeable_buffer_resize(rb, n + 1)) { + set_last_error(ERROR_OUTOFMEMORY); + return 0; } @@ -2839,20 +2914,30 @@ struct filesystem_traits T_resizeableBuffer& rb ) { - size_type len = rb.size(); + size_type const n = rb.size(); - if (0 == len) + if (0 == n) { return 0; } - else - { - size_type lenToDecrease = len - 1; - remove_dir_end(rb.data(), len - 1, &lenToDecrease); + size_type lenToDecrease = n - 1; + + remove_dir_end(rb.data(), n - 1, &lenToDecrease); - return lenToDecrease; + size_type const cchRequired = lenToDecrease + 1; + + if (rb.size() != cchRequired) + { + if (!resizeable_buffer_resize(rb, cchRequired)) + { + set_last_error(ERROR_OUTOFMEMORY); + + return 0; + } } + + return lenToDecrease; } public: // path classification and analysis @@ -3374,13 +3459,27 @@ struct filesystem_traits , T_resizeableBuffer& rb ) { - size_type const cchRequired = get_full_path_name(fileName, static_cast(NULL), 0); + size_type const cchRequired = get_full_path_name( + fileName + , static_cast(NULL) + , 0 + ); - if (!resizeable_buffer_resize(rb, cchRequired)) + if (0 == cchRequired) { return 0; } + if (rb.size() < cchRequired) + { + if (!resizeable_buffer_resize(rb, cchRequired)) + { + set_last_error(ERROR_OUTOFMEMORY); + + return 0; + } + } + return get_full_path_name(fileName, &rb[0], rb.size()); } @@ -3426,13 +3525,27 @@ struct filesystem_traits , T_resizeableBuffer& rb ) { - size_type const cchRequired = get_short_path_name(fileName, static_cast(NULL), 0); + size_type const cchRequired = get_short_path_name( + fileName + , static_cast(NULL) + , 0 + ); - if (!resizeable_buffer_resize(rb, cchRequired)) + if (0 == cchRequired) { return 0; } + if (rb.size() < cchRequired) + { + if (!resizeable_buffer_resize(rb, cchRequired)) + { + set_last_error(ERROR_OUTOFMEMORY); + + return 0; + } + } + return get_short_path_name(fileName, &rb[0], rb.size()); } @@ -3511,13 +3624,26 @@ struct filesystem_traits T_resizeableBuffer& rb ) { - size_type const cchRequired = get_current_directory(static_cast(NULL), 0); + size_type const cchRequired = get_current_directory( + static_cast(NULL) + , 0 + ); - if (!resizeable_buffer_resize(rb, cchRequired)) + if (0 == cchRequired) { return 0; } + if (rb.size() < cchRequired) + { + if (!resizeable_buffer_resize(rb, cchRequired)) + { + set_last_error(ERROR_OUTOFMEMORY); + + return 0; + } + } + return get_current_directory(&rb[0], rb.size()); } diff --git a/include/winstl/system/system_traits.hpp b/include/winstl/system/system_traits.hpp index a2e4ecf22..bd955ceee 100644 --- a/include/winstl/system/system_traits.hpp +++ b/include/winstl/system/system_traits.hpp @@ -5,7 +5,7 @@ * Unicode specialisations thereof. * * Created: 15th November 2002 - * Updated: 15th October 2026 + * Updated: 19th October 2026 * * Thanks: Austin Ziegler for spotting the defective pre-condition * enforcement of expand_environment_strings(). @@ -401,6 +401,21 @@ struct system_traits , size_type cchBuffer ); + /// Gets the full path name of the directory of the given module + /// + /// \param hModule The module handle. If \c nullptr then obtains the + /// directory path of the executing module + /// \param rb Reference to a resizeable buffer + template< + ss_typename_param_k T_resizeableBuffer + > + static + size_type + get_module_directory( + HMODULE hModule + , T_resizeableBuffer& rb + ); + /// Gets the full path name of the system directory /// /// \param buffer Pointer to array of size \c cchBuffer. Must not be @@ -954,6 +969,40 @@ struct system_traits return cch; } + template< + ss_typename_param_k T_resizeableBuffer + > + static + size_type + get_module_directory( + HMODULE hModule + , T_resizeableBuffer& rb + ) + { + size_type const cchRequired = get_module_directory( + hModule + , static_cast(NULL) + , 0 + ); + + if (0 == cchRequired) + { + return 0; + } + + if (rb.size() < cchRequired) + { + if (!STLSOFT_NS_QUAL(resizeable_buffer_resize)(rb, cchRequired)) + { + set_last_error(ERROR_OUTOFMEMORY); + + return 0; + } + } + + return get_module_directory(hModule, &rb[0], rb.size()); + } + static size_type get_system_directory( @@ -1639,6 +1688,40 @@ struct system_traits return cch; } + template< + ss_typename_param_k T_resizeableBuffer + > + static + size_type + get_module_directory( + HMODULE hModule + , T_resizeableBuffer& rb + ) + { + size_type const cchRequired = get_module_directory( + hModule + , static_cast(NULL) + , 0 + ); + + if (0 == cchRequired) + { + return 0; + } + + if (rb.size() < cchRequired) + { + if (!STLSOFT_NS_QUAL(resizeable_buffer_resize)(rb, cchRequired)) + { + set_last_error(ERROR_OUTOFMEMORY); + + return 0; + } + } + + return get_module_directory(hModule, &rb[0], rb.size()); + } + static size_type get_system_directory( diff --git a/test/component/unixstl/filesystem/filesystem_traits/CMakeLists.txt b/test/component/unixstl/filesystem/filesystem_traits/CMakeLists.txt index 8460ebf33..7c3363b5e 100644 --- a/test/component/unixstl/filesystem/filesystem_traits/CMakeLists.txt +++ b/test/component/unixstl/filesystem/filesystem_traits/CMakeLists.txt @@ -1,2 +1,3 @@ # SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten add_subdirectory(opendir) +add_subdirectory(paths) diff --git a/test/component/unixstl/filesystem/filesystem_traits/paths/CMakeLists.txt b/test/component/unixstl/filesystem/filesystem_traits/paths/CMakeLists.txt new file mode 100644 index 000000000..c9319f9fd --- /dev/null +++ b/test/component/unixstl/filesystem/filesystem_traits/paths/CMakeLists.txt @@ -0,0 +1,2 @@ +# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +define_automated_test_program(test.component.unixstl.filesystem.filesystem_traits.paths entry.cpp) diff --git a/test/component/unixstl/filesystem/filesystem_traits/paths/entry.cpp b/test/component/unixstl/filesystem/filesystem_traits/paths/entry.cpp new file mode 100644 index 000000000..9940643f7 --- /dev/null +++ b/test/component/unixstl/filesystem/filesystem_traits/paths/entry.cpp @@ -0,0 +1,250 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/component/unixstl/filesystem/filesystem_traits/paths/entry.cpp + * + * Purpose: Component-tests for resizeable-buffer overloads of + * `unixstl::filesystem_traits` (`get_current_directory`, + * `get_full_path_name`, `get_short_path_name`). + * + * Created: 15th October 2026 + * Updated: 15th October 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +/* ///////////////////////////////////// + * test component header file include(s) + */ + +#include + +/* ///////////////////////////////////// + * general includes + */ + +/* xTests header files */ +#include +#include + +/* STLSoft header files */ +#include +#include + +/* Standard C++ header files */ +#include + +/* Standard C header files */ +#include +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * forward declarations + */ + +namespace { + + static void TEST_get_current_directory_RESIZEABLE_auto_buffer(); + static void TEST_get_current_directory_RESIZEABLE_vector(); + static void TEST_get_full_path_name_RESIZEABLE_auto_buffer(); + static void TEST_get_full_path_name_RESIZEABLE_vector(); + static void TEST_get_short_path_name_RESIZEABLE_auto_buffer(); + static void TEST_get_short_path_name_RESIZEABLE_vector(); +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * main() + */ + +int main(int argc, char* argv[]) +{ + int retCode = EXIT_SUCCESS; + int verbosity = 2; + + XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); + + if (XTESTS_START_RUNNER("test.component.unixstl.filesystem.filesystem_traits.paths", verbosity)) + { + XTESTS_RUN_CASE(TEST_get_current_directory_RESIZEABLE_auto_buffer); + XTESTS_RUN_CASE(TEST_get_current_directory_RESIZEABLE_vector); + XTESTS_RUN_CASE(TEST_get_full_path_name_RESIZEABLE_auto_buffer); + XTESTS_RUN_CASE(TEST_get_full_path_name_RESIZEABLE_vector); + XTESTS_RUN_CASE(TEST_get_short_path_name_RESIZEABLE_auto_buffer); + XTESTS_RUN_CASE(TEST_get_short_path_name_RESIZEABLE_vector); + + XTESTS_PRINT_RESULTS(); + + XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); + } + + return retCode; +} + + +/* ///////////////////////////////////////////////////////////////////////// + * test function implementations + */ + +namespace { + + typedef unixstl::filesystem_traits traits_m_t; + typedef stlsoft::auto_buffer ab_m_t; + typedef std::vector vec_m_t; + + + static + size_t + capture_fixed( + size_t (*fn)(char*, size_t) + , vec_m_t& expected + ) + { + expected.resize(1 + traits_m_t::path_max()); + + size_t const n = fn(&expected[0], expected.size()); + + if (0 != n && + n < expected.size()) + { + expected.resize(n + 1); + expected[n] = '\0'; + } + + return n; + } + + static + size_t + capture_cwd( + char* buffer + , size_t cchBuffer + ) + { + return traits_m_t::get_current_directory(buffer, cchBuffer); + } + + static + size_t + capture_full_dot( + char* buffer + , size_t cchBuffer + ) + { + return traits_m_t::get_full_path_name(".", buffer, cchBuffer); + } + + static + size_t + capture_short_dot( + char* buffer + , size_t cchBuffer + ) + { + return traits_m_t::get_short_path_name(".", buffer, cchBuffer); + } + + +static void TEST_get_current_directory_RESIZEABLE_auto_buffer() +{ + vec_m_t expected; + size_t const expected_len = capture_fixed(capture_cwd, expected); + + TEST_INT_NE(0, expected_len); + + ab_m_t rb(0); + size_t const n = traits_m_t::get_current_directory(rb); + + TEST_INT_EQ(expected_len, n); + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(&expected[0], rb.data()); +} + +static void TEST_get_current_directory_RESIZEABLE_vector() +{ + vec_m_t expected; + size_t const expected_len = capture_fixed(capture_cwd, expected); + + TEST_INT_NE(0, expected_len); + + vec_m_t rb; + size_t const n = traits_m_t::get_current_directory(rb); + + TEST_INT_EQ(expected_len, n); + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(&expected[0], &rb[0]); +} + +static void TEST_get_full_path_name_RESIZEABLE_auto_buffer() +{ + vec_m_t expected; + size_t const expected_len = capture_fixed(capture_full_dot, expected); + + TEST_INT_NE(0, expected_len); + + ab_m_t rb(0); + size_t const n = traits_m_t::get_full_path_name(".", rb); + + TEST_INT_EQ(expected_len, n); + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(&expected[0], rb.data()); +} + +static void TEST_get_full_path_name_RESIZEABLE_vector() +{ + vec_m_t expected; + size_t const expected_len = capture_fixed(capture_full_dot, expected); + + TEST_INT_NE(0, expected_len); + + vec_m_t rb; + size_t const n = traits_m_t::get_full_path_name(".", rb); + + TEST_INT_EQ(expected_len, n); + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(&expected[0], &rb[0]); +} + +static void TEST_get_short_path_name_RESIZEABLE_auto_buffer() +{ + vec_m_t expected; + size_t const expected_len = capture_fixed(capture_short_dot, expected); + + TEST_INT_NE(0, expected_len); + + ab_m_t rb(0); + size_t const n = traits_m_t::get_short_path_name(".", rb); + + TEST_INT_EQ(expected_len, n); + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(&expected[0], rb.data()); +} + +static void TEST_get_short_path_name_RESIZEABLE_vector() +{ + vec_m_t expected; + size_t const expected_len = capture_fixed(capture_short_dot, expected); + + TEST_INT_NE(0, expected_len); + + vec_m_t rb; + size_t const n = traits_m_t::get_short_path_name(".", rb); + + TEST_INT_EQ(expected_len, n); + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(&expected[0], &rb[0]); +} + +} // anonymous namespace + + +/* ///////////////////////////// end of file //////////////////////////// */ diff --git a/test/component/unixstl/system/system_traits/entry.cpp b/test/component/unixstl/system/system_traits/entry.cpp index afcec26c0..554abe3dd 100644 --- a/test/component/unixstl/system/system_traits/entry.cpp +++ b/test/component/unixstl/system/system_traits/entry.cpp @@ -6,7 +6,7 @@ * `get_environment_variable`) and wide home / environment APIs. * * Created: 15th October 2026 - * Updated: 15th October 2026 + * Updated: 19th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -99,8 +99,17 @@ namespace { typedef stlsoft::auto_buffer ab_w_t; typedef unixstl::system_traits traits_m_t; typedef unixstl::system_traits traits_w_t; + typedef std::vector vec_m_t; typedef std::vector vec_w_t; + static char const s_missing_name[] = + "STLSOFT_TEST_ENV_ABSENT_9F3C2B1A_7E4D_4A6F_9C08_1D2E3F4A5B6C"; + + static char const s_present_name[] = + "STLSOFT_TEST_ENV_PRESENT_9F3C2B1A_7E4D_4A6F_9C08_1D2E3F4A5B6C"; + + static char const s_present_value[] = "resizeable-buffer-value"; + static wchar_t const s_wide_missing_name_w[] = L"UNIXSTL_SYSTEM_TRAITS_W_TEST_MISSING_9F3C2B1A_7E4D_4A6F_9C08_1D2E3F4A5B6C"; @@ -115,19 +124,72 @@ namespace { static wchar_t const s_wide_present_value_w[] = L"wide-env-value"; -/* Shared with resizeable_buffer branch — bodies and ANSI name/value - * constants land at merge (rb size including NUL / probe-then-fill). - */ static void TEST_get_environment_variable_MISSING() { + { + ab_m_t rb(0); + size_t const n = traits_m_t::get_environment_variable(s_missing_name, rb); + + TEST_INT_EQ(0, n); + TEST_INT_EQ(0, rb.size()); + } + + { + vec_m_t rb; + size_t const n = traits_m_t::get_environment_variable(s_missing_name, rb); + + TEST_INT_EQ(0, n); + TEST_INT_EQ(0, rb.size()); + } + + { + ab_m_t rb(3); + rb[0] = 'a'; + rb[1] = 'b'; + rb[2] = 'c'; + + size_t const n = traits_m_t::get_environment_variable(s_missing_name, rb); + + TEST_INT_EQ(0, n); + TEST_INT_EQ(3, rb.size()); + TEST_CHAR_EQ('a', rb[0]); + TEST_CHAR_EQ('b', rb[1]); + TEST_CHAR_EQ('c', rb[2]); + } } static void TEST_get_environment_variable_RESIZEABLE_auto_buffer() { + platformstl::environment_variable_scope const scope( + s_present_name + , s_present_value + ); + + size_t const expected_len = ::strlen(s_present_value); + ab_m_t rb(0); + size_t const n = traits_m_t::get_environment_variable(s_present_name, rb); + + TEST_INT_EQ(expected_len, n); + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(s_present_value, rb.data()); } static void TEST_get_environment_variable_RESIZEABLE_vector() { + platformstl::environment_variable_scope const scope( + s_present_name + , s_present_value + ); + + size_t const expected_len = ::strlen(s_present_value); + vec_m_t rb; + size_t const n = traits_m_t::get_environment_variable(s_present_name, rb); + + TEST_INT_EQ(expected_len, n); + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(s_present_value, &rb[0]); } static void TEST_get_environment_variable_w() @@ -180,10 +242,52 @@ static void TEST_get_environment_variable_w() static void TEST_get_home_directory_RESIZEABLE_auto_buffer() { + char const* const home = ::getenv("HOME"); + + if (NULL == home) + { + ab_m_t rb(0); + size_t const n = traits_m_t::get_home_directory(rb); + + TEST_INT_EQ(0, n); + TEST_INT_EQ(0, rb.size()); + + return; + } + + size_t const expected_len = ::strlen(home); + ab_m_t rb(0); + size_t const n = traits_m_t::get_home_directory(rb); + + TEST_INT_EQ(expected_len, n); + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(home, rb.data()); } static void TEST_get_home_directory_RESIZEABLE_vector() { + char const* const home = ::getenv("HOME"); + + if (NULL == home) + { + vec_m_t rb; + size_t const n = traits_m_t::get_home_directory(rb); + + TEST_INT_EQ(0, n); + TEST_INT_EQ(0, rb.size()); + + return; + } + + size_t const expected_len = ::strlen(home); + vec_m_t rb; + size_t const n = traits_m_t::get_home_directory(rb); + + TEST_INT_EQ(expected_len, n); + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(home, &rb[0]); } static void TEST_get_home_directory_w() @@ -230,3 +334,4 @@ static void TEST_get_home_directory_w() /* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/component/winstl/filesystem/filesystem_traits/entry.cpp b/test/component/winstl/filesystem/filesystem_traits/entry.cpp index add202432..ace529cf8 100644 --- a/test/component/winstl/filesystem/filesystem_traits/entry.cpp +++ b/test/component/winstl/filesystem/filesystem_traits/entry.cpp @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: filesystem_traits/entry.cpp + * File: test/component/winstl/filesystem/filesystem_traits/entry.cpp * * Purpose: Component-tests for `winstl::filesystem_traits`. * * Created: 31st December 2008 - * Updated: 29th September 2026 + * Updated: 15th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -47,6 +47,12 @@ namespace { static void TEST_FIND_FILE_1(); static void TEST_FIND_FILE_2(); + static void TEST_get_current_directory_RESIZEABLE_auto_buffer(); + static void TEST_get_current_directory_RESIZEABLE_vector(); + static void TEST_get_full_path_name_RESIZEABLE_auto_buffer(); + static void TEST_get_full_path_name_RESIZEABLE_vector(); + static void TEST_get_short_path_name_RESIZEABLE_auto_buffer(); + static void TEST_get_short_path_name_RESIZEABLE_vector(); } // anonymous namespace @@ -54,7 +60,7 @@ namespace { * main() */ -int main(int argc, char *argv[]) +int main(int argc, char* argv[]) { int retCode = EXIT_SUCCESS; int verbosity = 2; @@ -65,6 +71,12 @@ int main(int argc, char *argv[]) { XTESTS_RUN_CASE(TEST_FIND_FILE_1); XTESTS_RUN_CASE(TEST_FIND_FILE_2); + XTESTS_RUN_CASE(TEST_get_current_directory_RESIZEABLE_auto_buffer); + XTESTS_RUN_CASE(TEST_get_current_directory_RESIZEABLE_vector); + XTESTS_RUN_CASE(TEST_get_full_path_name_RESIZEABLE_auto_buffer); + XTESTS_RUN_CASE(TEST_get_full_path_name_RESIZEABLE_vector); + XTESTS_RUN_CASE(TEST_get_short_path_name_RESIZEABLE_auto_buffer); + XTESTS_RUN_CASE(TEST_get_short_path_name_RESIZEABLE_vector); XTESTS_PRINT_RESULTS(); @@ -179,6 +191,145 @@ static void TEST_FIND_FILE_2() } } } + + +static void TEST_get_current_directory_RESIZEABLE_auto_buffer() +{ + vec_m_t expected(1 + filesystem_traits_m_t::path_max()); + size_t const expected_len = filesystem_traits_m_t::get_current_directory( + &expected[0] + , expected.size() + ); + + TEST_INT_NE(0, expected_len); + + ab_m_t rb(0); + size_t const n = filesystem_traits_m_t::get_current_directory(rb); + + TEST_INT_EQ(expected_len, n); + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(&expected[0], rb.data()); +} + +static void TEST_get_current_directory_RESIZEABLE_vector() +{ + vec_m_t expected(1 + filesystem_traits_m_t::path_max()); + size_t const expected_len = filesystem_traits_m_t::get_current_directory( + &expected[0] + , expected.size() + ); + + TEST_INT_NE(0, expected_len); + + vec_m_t rb; + size_t const n = filesystem_traits_m_t::get_current_directory(rb); + + TEST_INT_EQ(expected_len, n); + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(&expected[0], &rb[0]); +} + +static void TEST_get_full_path_name_RESIZEABLE_auto_buffer() +{ + char const* const fileName = "."; + + vec_m_t expected(1 + filesystem_traits_m_t::path_max()); + size_t const expected_len = filesystem_traits_m_t::get_full_path_name( + fileName + , &expected[0] + , expected.size() + ); + + TEST_INT_NE(0, expected_len); + + ab_m_t rb(0); + size_t const n = filesystem_traits_m_t::get_full_path_name(fileName, rb); + + TEST_INT_EQ(expected_len, n); + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(&expected[0], rb.data()); +} + +static void TEST_get_full_path_name_RESIZEABLE_vector() +{ + char const* const fileName = "."; + + vec_m_t expected(1 + filesystem_traits_m_t::path_max()); + size_t const expected_len = filesystem_traits_m_t::get_full_path_name( + fileName + , &expected[0] + , expected.size() + ); + + TEST_INT_NE(0, expected_len); + + vec_m_t rb; + size_t const n = filesystem_traits_m_t::get_full_path_name(fileName, rb); + + TEST_INT_EQ(expected_len, n); + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(&expected[0], &rb[0]); +} + +static void TEST_get_short_path_name_RESIZEABLE_auto_buffer() +{ + string_m_t const windir = get_windows_directory(); + + vec_m_t expected(1 + filesystem_traits_m_t::path_max()); + size_t const expected_len = filesystem_traits_m_t::get_short_path_name( + windir.c_str() + , &expected[0] + , expected.size() + ); + + ab_m_t rb(0); + size_t const n = filesystem_traits_m_t::get_short_path_name(windir.c_str(), rb); + + TEST_INT_EQ(expected_len, n); + + if (0 == expected_len) + { + TEST_INT_EQ(0, rb.size()); + } + else + { + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(&expected[0], rb.data()); + } +} + +static void TEST_get_short_path_name_RESIZEABLE_vector() +{ + string_m_t const windir = get_windows_directory(); + + vec_m_t expected(1 + filesystem_traits_m_t::path_max()); + size_t const expected_len = filesystem_traits_m_t::get_short_path_name( + windir.c_str() + , &expected[0] + , expected.size() + ); + + vec_m_t rb; + size_t const n = filesystem_traits_m_t::get_short_path_name(windir.c_str(), rb); + + TEST_INT_EQ(expected_len, n); + + if (0 == expected_len) + { + TEST_INT_EQ(0, rb.size()); + } + else + { + TEST_INT_EQ(expected_len + 1, rb.size()); + TEST_CHAR_EQ('\0', rb[n]); + TEST_MS_EQ(&expected[0], &rb[0]); + } +} } /* anonymous namespace */ diff --git a/test/unit/stlsoft/util/CMakeLists.txt b/test/unit/stlsoft/util/CMakeLists.txt index aff0fe68b..957bcb461 100644 --- a/test/unit/stlsoft/util/CMakeLists.txt +++ b/test/unit/stlsoft/util/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(bit_functions) add_subdirectory(count_decimal_digits) add_subdirectory(count_hexadecimal_digits) +add_subdirectory(resizeable_buffer_helpers) if(X_CMAKE_CXX_FULLSTANDARD GREATER_EQUAL 2011) add_subdirectory(string_insertion) diff --git a/test/unit/stlsoft/util/resizeable_buffer_helpers/CMakeLists.txt b/test/unit/stlsoft/util/resizeable_buffer_helpers/CMakeLists.txt new file mode 100644 index 000000000..cc8b70aa2 --- /dev/null +++ b/test/unit/stlsoft/util/resizeable_buffer_helpers/CMakeLists.txt @@ -0,0 +1,2 @@ +# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +define_automated_test_program(test.unit.stlsoft.util.resizeable_buffer_helpers entry.cpp) diff --git a/test/unit/stlsoft/util/resizeable_buffer_helpers/entry.cpp b/test/unit/stlsoft/util/resizeable_buffer_helpers/entry.cpp new file mode 100644 index 000000000..edb063eed --- /dev/null +++ b/test/unit/stlsoft/util/resizeable_buffer_helpers/entry.cpp @@ -0,0 +1,307 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test/unit/stlsoft/util/resizeable_buffer_helpers/entry.cpp + * + * Purpose: Unit-tests for `stlsoft::resizeable_buffer_resize`. + * + * Created: 15th October 2026 + * Updated: 16th October 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +/* ///////////////////////////////////// + * test component header file include(s) + */ + +#include + +/* ///////////////////////////////////// + * general includes + */ + +/* xTests header files */ +#include +#include + +/* STLSoft header files */ +#include +#include + +/* Standard C++ header files */ +#include + +/* Standard C header files */ +#include +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * forward declarations + */ + +namespace { + + static void TEST_WITH_auto_buffer_GROW(); + static void TEST_WITH_auto_buffer_SHRINK(); + static void TEST_WITH_auto_buffer_TO_ZERO(); + static void TEST_WITH_bool_returning_stub_FAILURE(); + static void TEST_WITH_bool_returning_stub_SUCCESS(); + static void TEST_WITH_vector_GROW(); + static void TEST_WITH_vector_SHRINK(); + static void TEST_WITH_vector_TO_ZERO(); + static void TEST_WITH_void_returning_default_arg_stub_GROW(); + static void TEST_WITH_void_returning_default_arg_stub_SHRINK(); + static void TEST_WITH_void_returning_stub_SUCCESS(); +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * main() + */ + +int main(int argc, char* argv[]) +{ + int retCode = EXIT_SUCCESS; + int verbosity = 2; + + XTESTS_COMMANDLINE_PARSEVERBOSITY(argc, argv, &verbosity); + + if (XTESTS_START_RUNNER("test.unit.stlsoft.util.resizeable_buffer_helpers", verbosity)) + { + XTESTS_RUN_CASE(TEST_WITH_auto_buffer_GROW); + XTESTS_RUN_CASE(TEST_WITH_auto_buffer_SHRINK); + XTESTS_RUN_CASE(TEST_WITH_auto_buffer_TO_ZERO); + XTESTS_RUN_CASE(TEST_WITH_bool_returning_stub_FAILURE); + XTESTS_RUN_CASE(TEST_WITH_bool_returning_stub_SUCCESS); + XTESTS_RUN_CASE(TEST_WITH_vector_GROW); + XTESTS_RUN_CASE(TEST_WITH_vector_SHRINK); + XTESTS_RUN_CASE(TEST_WITH_vector_TO_ZERO); + XTESTS_RUN_CASE(TEST_WITH_void_returning_default_arg_stub_GROW); + XTESTS_RUN_CASE(TEST_WITH_void_returning_default_arg_stub_SHRINK); + XTESTS_RUN_CASE(TEST_WITH_void_returning_stub_SUCCESS); + + XTESTS_PRINT_RESULTS(); + + XTESTS_END_RUNNER_UPDATE_EXITCODE(&retCode); + } + + return retCode; +} + + +/* ///////////////////////////////////////////////////////////////////////// + * test function implementations + */ + +namespace { + +/** Minimal resizeable-buffer stub whose resize() returns + * bool, optionally failing. + */ +class bool_returning_stub +{ +public: // types + typedef char value_type; + typedef size_t size_type; + +public: // construction + bool_returning_stub() + : m_size(0) + , m_fail(false) + {} + +public: // accessors + size_type size() const + { + return m_size; + } + +public: // operations + void set_fail(bool fail) + { + m_fail = fail; + } + + bool resize(size_type newSize) + { + if (m_fail) + { + return false; + } + + m_size = newSize; + + return true; + } + +private: // fields + size_type m_size; + bool m_fail; +}; + +/** Minimal resizeable-buffer stub whose resize() returns + * void. + */ +class void_returning_stub +{ +public: // types + typedef char value_type; + typedef size_t size_type; + +public: // construction + void_returning_stub() + : m_size(0) + {} + +public: // accessors + size_type size() const + { + return m_size; + } + +public: // operations + void resize(size_type newSize) + { + m_size = newSize; + } + +private: // fields + size_type m_size; +}; + +/** Models pre-C++11 std::vector::resize: a single member + * with a defaulted fill argument, so &T::resize has a + * two-parameter type while rb.resize(n) remains valid. + */ +class void_returning_default_arg_stub +{ +public: // types + typedef char value_type; + typedef size_t size_type; + +public: // construction + void_returning_default_arg_stub() + : m_size(0) + {} + +public: // accessors + size_type size() const + { + return m_size; + } + +public: // operations + void + resize( + size_type newSize + , value_type /* fill */ = value_type() + ) + { + m_size = newSize; + } + +private: // fields + size_type m_size; +}; + + +static void TEST_WITH_auto_buffer_GROW() +{ + stlsoft::auto_buffer rb(1); + + TEST_BOOLEAN_TRUE(stlsoft::resizeable_buffer_resize(rb, 32)); + TEST_INT_EQ(32, rb.size()); +} + +static void TEST_WITH_auto_buffer_SHRINK() +{ + stlsoft::auto_buffer rb(64); + + TEST_BOOLEAN_TRUE(stlsoft::resizeable_buffer_resize(rb, 8)); + TEST_INT_EQ(8, rb.size()); +} + +static void TEST_WITH_auto_buffer_TO_ZERO() +{ + stlsoft::auto_buffer rb(16); + + TEST_BOOLEAN_TRUE(stlsoft::resizeable_buffer_resize(rb, 0)); + TEST_INT_EQ(0, rb.size()); +} + +static void TEST_WITH_vector_GROW() +{ + std::vector rb(1, 'x'); + + TEST_BOOLEAN_TRUE(stlsoft::resizeable_buffer_resize(rb, 32)); + TEST_INT_EQ(32, rb.size()); +} + +static void TEST_WITH_vector_SHRINK() +{ + std::vector rb(64, 'x'); + + TEST_BOOLEAN_TRUE(stlsoft::resizeable_buffer_resize(rb, 8)); + TEST_INT_EQ(8, rb.size()); +} + +static void TEST_WITH_vector_TO_ZERO() +{ + std::vector rb(16, 'x'); + + TEST_BOOLEAN_TRUE(stlsoft::resizeable_buffer_resize(rb, 0)); + TEST_INT_EQ(0, rb.size()); +} + +static void TEST_WITH_bool_returning_stub_SUCCESS() +{ + bool_returning_stub rb; + + TEST_BOOLEAN_TRUE(stlsoft::resizeable_buffer_resize(rb, 10)); + TEST_INT_EQ(10, rb.size()); +} + +static void TEST_WITH_bool_returning_stub_FAILURE() +{ + bool_returning_stub rb; + + rb.set_fail(true); + + TEST_BOOLEAN_FALSE(stlsoft::resizeable_buffer_resize(rb, 10)); + TEST_INT_EQ(0, rb.size()); +} + +static void TEST_WITH_void_returning_stub_SUCCESS() +{ + void_returning_stub rb; + + TEST_BOOLEAN_TRUE(stlsoft::resizeable_buffer_resize(rb, 10)); + TEST_INT_EQ(10, rb.size()); +} + +static void TEST_WITH_void_returning_default_arg_stub_GROW() +{ + void_returning_default_arg_stub rb; + + TEST_BOOLEAN_TRUE(stlsoft::resizeable_buffer_resize(rb, 32)); + TEST_INT_EQ(32, rb.size()); +} + +static void TEST_WITH_void_returning_default_arg_stub_SHRINK() +{ + void_returning_default_arg_stub rb; + + TEST_BOOLEAN_TRUE(stlsoft::resizeable_buffer_resize(rb, 8)); + TEST_BOOLEAN_TRUE(stlsoft::resizeable_buffer_resize(rb, 3)); + TEST_INT_EQ(3, rb.size()); +} + +} // anonymous namespace + + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/test/unit/unixstl/filesystem/filesystem_traits/entry.cpp b/test/unit/unixstl/filesystem/filesystem_traits/entry.cpp index 375f83c7d..9d1eca885 100644 --- a/test/unit/unixstl/filesystem/filesystem_traits/entry.cpp +++ b/test/unit/unixstl/filesystem/filesystem_traits/entry.cpp @@ -3,11 +3,11 @@ * * Purpose: Unit-tests for `unixstl::filesystem_traits` resizeable-buffer * `ensure_dir_end` / `remove_dir_end` (C2 empty-`""` behaviour; - * buffer size after remove); and wide path classification / - * separator finders. + * buffer size after remove; G3 strip-all trailing separators); + * and wide path classification / separator finders. * * Created: 15th October 2026 - * Updated: 15th October 2026 + * Updated: 19th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -31,9 +31,14 @@ #include /* STLSoft header files */ +#include #include +/* Standard C++ header files */ +#include + /* Standard C header files */ +#include #include #include #include @@ -47,7 +52,9 @@ namespace { static void TEST_ensure_dir_end_EMPTY_stays_empty(); static void TEST_ensure_dir_end_APPENDS_separator(); + static void TEST_ensure_dir_end_RESIZE_FAILURE_sets_ENOMEM(); static void TEST_remove_dir_end_SHRINKS_buffer(); + static void TEST_remove_dir_end_STRIPS_ALL_trailing_separators(); static void TEST_remove_then_ensure_round_trip(); static void TEST_find_path_name_separators_w(); @@ -71,7 +78,9 @@ int main(int argc, char* argv[]) { XTESTS_RUN_CASE(TEST_ensure_dir_end_EMPTY_stays_empty); XTESTS_RUN_CASE(TEST_ensure_dir_end_APPENDS_separator); + XTESTS_RUN_CASE(TEST_ensure_dir_end_RESIZE_FAILURE_sets_ENOMEM); XTESTS_RUN_CASE(TEST_remove_dir_end_SHRINKS_buffer); + XTESTS_RUN_CASE(TEST_remove_dir_end_STRIPS_ALL_trailing_separators); XTESTS_RUN_CASE(TEST_remove_then_ensure_round_trip); XTESTS_RUN_CASE(TEST_find_path_name_separators_w); @@ -95,25 +104,243 @@ namespace { typedef unixstl::filesystem_traits traits_m_t; typedef unixstl::filesystem_traits traits_w_t; + typedef stlsoft::auto_buffer ab_m_t; + typedef std::vector vec_m_t; + + /* Resizeable-buffer model whose resize() always fails — used to lock + * C5 OOM reporting without exhausting the process heap. + */ + struct fail_resize_buffer + { + typedef char value_type; + typedef size_t size_type; + + std::vector storage; + + size_type + size() const + { + return storage.size(); + } + + bool + empty() const + { + return storage.empty(); + } + + char& + operator [](size_type i) + { + return storage[i]; + } + + char const& + operator [](size_type i) const + { + return storage[i]; + } + + bool + resize(size_type /* n */) + { + return false; + } + }; -/* Shared with resizeable_buffer branch — bodies land at merge (C2/C3). */ static void TEST_ensure_dir_end_EMPTY_stays_empty() { + /* C-string empty: exercise the sized overload with an explicit + * capacity. Calling the one-arg form on a tiny stack array such as + * `char dirname[] = "\0"` makes GCC -Warray-bounds / -Wstringop- + * overflow treat the capacity-0 append path as writing past the + * object under -Werror, even when an empty early-out is present. + */ + { + char dirname[8] = { 0 }; + size_t n = 0; + char* r = traits_m_t::ensure_dir_end( + dirname + , 0 + , STLSOFT_NUM_ELEMENTS(dirname) + , &n + ); + + TEST_MS_EQ("", dirname); + TEST_INT_EQ(0, n); + TEST_PTR_EQ(dirname + 0, r); + } + +#if __cplusplus >= 201103L + + { + ab_m_t buff = { '\0' }; + size_t const r = traits_m_t::ensure_dir_end(buff); + + TEST_MS_EQ("", buff.data()); + TEST_INT_EQ(0, r); + TEST_INT_EQ(1, buff.size()); + } +#endif + + { + vec_m_t buff(0); + size_t const r = traits_m_t::ensure_dir_end(buff); + + TEST_MS_EQ("", buff.data()); + TEST_INT_EQ(0, r); + TEST_INT_EQ(0, buff.size()); + } + + { + vec_m_t buff(1, '\0'); + size_t const r = traits_m_t::ensure_dir_end(buff); + + TEST_MS_EQ("", buff.data()); + TEST_INT_EQ(0, r); + TEST_INT_EQ(1, buff.size()); + } } static void TEST_ensure_dir_end_APPENDS_separator() { +#if __cplusplus >= 201103L + + { + ab_m_t buff = { 'a', 'b', 'c', '\0' }; + size_t const r = traits_m_t::ensure_dir_end(buff); + + TEST_MS_EQ("abc/", buff.data()); + TEST_INT_EQ(4, r); + TEST_INT_EQ(5, buff.size()); + } +#endif + + { + vec_m_t buff; + buff.push_back('a'); + buff.push_back('b'); + buff.push_back('c'); + buff.push_back('\0'); + + size_t const r = traits_m_t::ensure_dir_end(buff); + + TEST_MS_EQ("abc/", buff.data()); + TEST_INT_EQ(4, r); + TEST_INT_EQ(5, buff.size()); + } +} + +static void TEST_ensure_dir_end_RESIZE_FAILURE_sets_ENOMEM() +{ + fail_resize_buffer rb; + + rb.storage.push_back('a'); + rb.storage.push_back('b'); + rb.storage.push_back('c'); + rb.storage.push_back('\0'); + + traits_m_t::set_last_error(0); + + size_t const n = traits_m_t::ensure_dir_end(rb); + + TEST_INT_EQ(0, n); + TEST_INT_EQ(4, rb.size()); + TEST_INT_EQ(ENOMEM, traits_m_t::get_last_error()); } static void TEST_remove_dir_end_SHRINKS_buffer() { +#if __cplusplus >= 201103L + + { + ab_m_t buff = { 'a', 'b', 'c', '/', '\0' }; + size_t const r = traits_m_t::remove_dir_end(buff); + + TEST_MS_EQ("abc", buff.data()); + TEST_INT_EQ(3, r); + TEST_INT_EQ(4, buff.size()); + } +#endif + + { + vec_m_t buff; + buff.push_back('a'); + buff.push_back('b'); + buff.push_back('c'); + buff.push_back('/'); + buff.push_back('\0'); + + size_t const r = traits_m_t::remove_dir_end(buff); + + TEST_MS_EQ("abc", buff.data()); + TEST_INT_EQ(3, r); + TEST_INT_EQ(4, buff.size()); + } } -static void TEST_remove_then_ensure_round_trip() +static void TEST_remove_dir_end_STRIPS_ALL_trailing_separators() { + { + char dirname[] = "abc///"; + char* r = traits_m_t::remove_dir_end(dirname); + + TEST_MS_EQ("abc", dirname); + TEST_PTR_EQ(dirname + 0, r); + } + +#if __cplusplus >= 201103L + + { + ab_m_t buff = { 'a', 'b', 'c', '/', '/', '/', '\0' }; + size_t const r = traits_m_t::remove_dir_end(buff); + + TEST_MS_EQ("abc", buff.data()); + TEST_INT_EQ(3, r); + TEST_INT_EQ(4, buff.size()); + } +#endif + + { + vec_m_t buff; + buff.push_back('a'); + buff.push_back('b'); + buff.push_back('c'); + buff.push_back('/'); + buff.push_back('/'); + buff.push_back('/'); + buff.push_back('\0'); + + size_t const r = traits_m_t::remove_dir_end(buff); + + TEST_MS_EQ("abc", buff.data()); + TEST_INT_EQ(3, r); + TEST_INT_EQ(4, buff.size()); + } } +static void TEST_remove_then_ensure_round_trip() +{ + vec_m_t buff; + buff.push_back('a'); + buff.push_back('b'); + buff.push_back('c'); + buff.push_back('/'); + buff.push_back('\0'); + + size_t const n_remove = traits_m_t::remove_dir_end(buff); + + TEST_INT_EQ(3, n_remove); + TEST_INT_EQ(4, buff.size()); + TEST_MS_EQ("abc", buff.data()); + + size_t const n_ensure = traits_m_t::ensure_dir_end(buff); + + TEST_INT_EQ(4, n_ensure); + TEST_INT_EQ(5, buff.size()); + TEST_MS_EQ("abc/", buff.data()); +} static void TEST_find_path_name_separators_w() { diff --git a/test/unit/versions/entry.cpp b/test/unit/versions/entry.cpp index 4c5c1638c..b000a3088 100644 --- a/test/unit/versions/entry.cpp +++ b/test/unit/versions/entry.cpp @@ -133,7 +133,7 @@ namespace { static void TEST__STLSOFT_VER() { - TEST_INT_EQ(_STLSOFT_VER_1_11_1_RC9, _STLSOFT_VER); + TEST_INT_EQ(_STLSOFT_VER_1_11_1_RC10, _STLSOFT_VER); } #ifdef STLSOFT_HAS_ACE diff --git a/test/unit/winstl/filesystem/filesystem_traits/entry.cpp b/test/unit/winstl/filesystem/filesystem_traits/entry.cpp index 0a9e3e6e1..7441049ee 100644 --- a/test/unit/winstl/filesystem/filesystem_traits/entry.cpp +++ b/test/unit/winstl/filesystem/filesystem_traits/entry.cpp @@ -1,10 +1,10 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: filesystem_traits/entry.cpp + * File: test/unit/winstl/filesystem/filesystem_traits/entry.cpp * * Purpose: Unit-tests for `winstl::filesystem_traits`. * * Created: 31st December 2008 - * Updated: 29th September 2026 + * Updated: 19th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -47,10 +47,12 @@ namespace { static void TEST_starts_with_dots(); static void TEST_ensure_dir_end_1(); // TODO: rename to `dirname_ensure_pns()` + static void TEST_ensure_dir_end_RESIZE_FAILURE_sets_ERROR_OUTOFMEMORY(); static void TEST_has_dir_end_1(); static void TEST_remove_dir_end_1(); + static void TEST_remove_then_ensure_round_trip(); static void TEST_find_last_path_name_separator(); static void TEST_find_next_path_name_separator(); @@ -94,10 +96,12 @@ int main(int argc, char *argv[]) XTESTS_RUN_CASE(TEST_starts_with_dots); XTESTS_RUN_CASE(TEST_ensure_dir_end_1); + XTESTS_RUN_CASE(TEST_ensure_dir_end_RESIZE_FAILURE_sets_ERROR_OUTOFMEMORY); XTESTS_RUN_CASE(TEST_has_dir_end_1); XTESTS_RUN_CASE(TEST_remove_dir_end_1); + XTESTS_RUN_CASE(TEST_remove_then_ensure_round_trip); XTESTS_RUN_CASE(TEST_path_is_absolute_1); XTESTS_RUN_CASE(TEST_path_is_absolute_2); @@ -146,6 +150,47 @@ namespace { typedef winstl::filesystem_traits filesystem_traits_m_t; typedef winstl::filesystem_traits filesystem_traits_w_t; + /* Resizeable-buffer model whose resize() always fails — used to lock + * C5 OOM reporting without exhausting the process heap. + */ + struct fail_resize_buffer + { + typedef char value_type; + typedef size_t size_type; + + std::vector storage; + + size_type + size() const + { + return storage.size(); + } + + bool + empty() const + { + return storage.empty(); + } + + char& + operator [](size_type i) + { + return storage[i]; + } + + char const& + operator [](size_type i) const + { + return storage[i]; + } + + bool + resize(size_type /* n */) + { + return false; + } + }; + static void TEST_basename_is_dots() { @@ -225,21 +270,42 @@ static void TEST_ensure_dir_end_1() { // empty string { + /* C-string empty: exercise the sized overload with an explicit + * capacity. Calling the one-arg form on a tiny stack array such as + * `char dirname[] = "\0"` makes GCC -Warray-bounds / -Wstringop- + * overflow treat the capacity-0 append path as writing past the + * object under -Werror, even when an empty early-out is present. + */ + // passing character array (char) { - char dirname[] = "\0"; - char* r = filesystem_traits_m_t::ensure_dir_end(dirname); + char dirname[8] = { 0 }; + ss_size_t n = 0; + char* r = filesystem_traits_m_t::ensure_dir_end( + dirname + , 0 + , STLSOFT_NUM_ELEMENTS(dirname) + , &n + ); TEST_MS_EQ("", dirname); + TEST_INT_EQ(0, n); TEST_PTR_EQ(dirname + 0, r); } // passing character array (wchar_t) { - wchar_t dirname[] = L"\0"; - wchar_t* r = filesystem_traits_w_t::ensure_dir_end(dirname); + wchar_t dirname[8] = { 0 }; + ss_size_t n = 0; + wchar_t* r = filesystem_traits_w_t::ensure_dir_end( + dirname + , 0 + , STLSOFT_NUM_ELEMENTS(dirname) + , &n + ); TEST_WS_EQ(L"", dirname); + TEST_INT_EQ(0, n); TEST_PTR_EQ(dirname + 0, r); } @@ -484,6 +550,24 @@ static void TEST_ensure_dir_end_1() } } +static void TEST_ensure_dir_end_RESIZE_FAILURE_sets_ERROR_OUTOFMEMORY() +{ + fail_resize_buffer rb; + + rb.storage.push_back('a'); + rb.storage.push_back('b'); + rb.storage.push_back('c'); + rb.storage.push_back('\0'); + + filesystem_traits_m_t::set_last_error(ERROR_SUCCESS); + + ss_size_t const n = filesystem_traits_m_t::ensure_dir_end(rb); + + TEST_INT_EQ(0, n); + TEST_INT_EQ(4, rb.size()); + TEST_INT_EQ(ERROR_OUTOFMEMORY, filesystem_traits_m_t::get_last_error()); +} + static void TEST_has_dir_end_1() { // empty string @@ -866,6 +950,7 @@ static void TEST_remove_dir_end_1() TEST_MS_EQ("abc", buff.data()); TEST_INT_EQ(3, r); + TEST_INT_EQ(4, buff.size()); } // passing resizeable buffer (auto_buffer) @@ -875,6 +960,7 @@ static void TEST_remove_dir_end_1() TEST_WS_EQ(L"abc", buff.data()); TEST_INT_EQ(3, r); + TEST_INT_EQ(4, buff.size()); } // passing resizeable buffer (vector) @@ -884,6 +970,7 @@ static void TEST_remove_dir_end_1() TEST_MS_EQ("abc", buff.data()); TEST_INT_EQ(3, r); + TEST_INT_EQ(4, buff.size()); } // passing resizeable buffer (vector) @@ -893,6 +980,7 @@ static void TEST_remove_dir_end_1() TEST_WS_EQ(L"abc", buff.data()); TEST_INT_EQ(3, r); + TEST_INT_EQ(4, buff.size()); } #endif } @@ -926,6 +1014,7 @@ static void TEST_remove_dir_end_1() TEST_MS_EQ("abc", buff.data()); TEST_INT_EQ(3, r); + TEST_INT_EQ(4, buff.size()); } // passing resizeable buffer (auto_buffer) @@ -935,6 +1024,7 @@ static void TEST_remove_dir_end_1() TEST_WS_EQ(L"abc", buff.data()); TEST_INT_EQ(3, r); + TEST_INT_EQ(4, buff.size()); } // passing resizeable buffer (vector) @@ -944,6 +1034,7 @@ static void TEST_remove_dir_end_1() TEST_MS_EQ("abc", buff.data()); TEST_INT_EQ(3, r); + TEST_INT_EQ(4, buff.size()); } // passing resizeable buffer (vector) @@ -953,11 +1044,34 @@ static void TEST_remove_dir_end_1() TEST_WS_EQ(L"abc", buff.data()); TEST_INT_EQ(3, r); + TEST_INT_EQ(4, buff.size()); } #endif } } +static void TEST_remove_then_ensure_round_trip() +{ + vec_m_t buff; + buff.push_back('a'); + buff.push_back('b'); + buff.push_back('c'); + buff.push_back('\\'); + buff.push_back('\0'); + + ss_size_t const n_remove = filesystem_traits_m_t::remove_dir_end(buff); + + TEST_INT_EQ(3, n_remove); + TEST_INT_EQ(4, buff.size()); + TEST_MS_EQ("abc", buff.data()); + + ss_size_t const n_ensure = filesystem_traits_m_t::ensure_dir_end(buff); + + TEST_INT_EQ(4, n_ensure); + TEST_INT_EQ(5, buff.size()); + TEST_MS_EQ("abc\\", buff.data()); +} + static void TEST_path_is_absolute_1() { { diff --git a/test/unit/winstl/system/system_traits/entry.cpp b/test/unit/winstl/system/system_traits/entry.cpp index 75f2a3175..5170723cb 100644 --- a/test/unit/winstl/system/system_traits/entry.cpp +++ b/test/unit/winstl/system/system_traits/entry.cpp @@ -4,7 +4,7 @@ * Purpose: Unit-tests for `winstl::system_traits`. * * Created: 22nd May 2025 - * Updated: 29th September 2026 + * Updated: 19th October 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -842,6 +842,28 @@ static void TEST_get_module_directory() TEST_CHAR_EQ(L'~', buff[n]); TEST_CHAR_EQ(L'~', buff[STLSOFT_NUM_ELEMENTS(buff) - 1]); } + + // passing resizeable buffer (auto_buffer) + { + ::SetLastError(ERROR_SUCCESS); + + ab_m_t buff(1); + ss_size_t const n = system_traits_m_t::get_module_directory(hmInvalid, buff); + + TEST_INT_EQ(ERROR_MOD_NOT_FOUND, ::GetLastError()); + TEST_INT_EQ(0, n); + } + + // passing resizeable buffer (vector) + { + ::SetLastError(ERROR_SUCCESS); + + vec_w_t buff(0); + ss_size_t const n = system_traits_w_t::get_module_directory(hmInvalid, buff); + + TEST_INT_EQ(ERROR_MOD_NOT_FOUND, ::GetLastError()); + TEST_INT_EQ(0, n); + } } HMODULE hmKernel = ::LoadLibraryA("kernel32"); @@ -872,6 +894,34 @@ static void TEST_get_module_directory() TEST_INT_GE(4, n); TEST_CHAR_EQ(L'\0', buff[0]); } + + // passing resizeable buffer (auto_buffer) — grows then fills + { + ::SetLastError(ERROR_SUCCESS); + + ab_m_t buff(1); + ss_size_t const n = system_traits_m_t::get_module_directory(hmKernel, buff); + + TEST_INT_EQ(ERROR_SUCCESS, ::GetLastError()); + TEST_INT_GE(13, n); + TEST_INT_GT(n, buff.size()); + TEST_CHAR_EQ('\0', buff[n]); + TEST_CHAR_NE('\0', buff[n - 1]); + } + + // passing resizeable buffer (vector) — grows then fills + { + ::SetLastError(ERROR_SUCCESS); + + vec_w_t buff(0); + ss_size_t const n = system_traits_w_t::get_module_directory(hmKernel, buff); + + TEST_INT_EQ(ERROR_SUCCESS, ::GetLastError()); + TEST_INT_GE(13, n); + TEST_INT_GT(n, buff.size()); + TEST_CHAR_EQ(L'\0', buff[n]); + TEST_CHAR_NE(L'\0', buff[n - 1]); + } } // sufficient memory @@ -901,6 +951,34 @@ static void TEST_get_module_directory() TEST_CHAR_EQ(L'\0', buff[n]); TEST_CHAR_NE(L'\0', buff[n - 1]); } + + // passing resizeable buffer (auto_buffer) + { + ::SetLastError(ERROR_SUCCESS); + + ab_w_t buff(1001); + ss_size_t const n = system_traits_w_t::get_module_directory(hmKernel, buff); + + TEST_INT_EQ(ERROR_SUCCESS, ::GetLastError()); + TEST_INT_GE(13, n); + TEST_INT_GT(n, buff.size()); + TEST_CHAR_EQ(L'\0', buff[n]); + TEST_CHAR_NE(L'\0', buff[n - 1]); + } + + // passing resizeable buffer (vector) + { + ::SetLastError(ERROR_SUCCESS); + + vec_m_t buff(1001, '\0'); + ss_size_t const n = system_traits_m_t::get_module_directory(hmKernel, buff); + + TEST_INT_EQ(ERROR_SUCCESS, ::GetLastError()); + TEST_INT_GE(13, n); + TEST_INT_GT(n, buff.size()); + TEST_CHAR_EQ('\0', buff[n]); + TEST_CHAR_NE('\0', buff[n - 1]); + } } }