Skip to content

Commit 0c6e26c

Browse files
committed
docs(time-zone): #2185's selected repair cannot meet its own acceptance example
Returned to needs_user, with the measurement rather than an argument. No production statement changed. The ticket selects "a vector<shared_ptr<AdjustmentRule>> populated from the year scan #2181 already performs", and names America/New_York versus America/Havana as the pair that must stop comparing equal. MEASUREMENT 1. The #2181 scan samples twelve months at midday on the 15th. For 2025 it produces, for BOTH zones, byte-identical output: same standard offset, same daylight offset, same daylight MONTHS. They differ only in the transition HOUR -- Havana switches at local midnight, New_York at 02:00 -- which month sampling cannot see. Rules derived from that scan would still report the two as same-rule zones, so a test written against the design would pass while the finding stood. MEASUREMENT 2, and the one that matters more. A one-year scan does not generalise. New_York transitions on 1995-04-02, 2007-03-11 and 2025-03-09, because the US rule changed in 2007. .NET returns SEVERAL AdjustmentRule objects with different DateStart/DateEnd ranges precisely for that, and each carries a RECURRENCE taken from tzdata's RULE records. libc exposes only resolved instants, never the recurrence. Consequence: a single rule derived from one year's transitions would be INVENTED CONTENT in a .NET-named API -- GetAdjustmentRules() would return objects that are not adjustment rules -- which SA-5 forbids in terms. The finding is not closable by sampling libc at any granularity; it needs tzdata's own rule structures, which is materially larger than this ticket describes. The layout gate the ticket measured (sizeof 160 -> 184) is real but is now the SMALLER obstacle, and the approval it was blocked on is not what blocks it: the design is. The failure remains one-directional -- this port can only be too permissive, never too strict.
1 parent 36a97ce commit 0c6e26c

2 files changed

Lines changed: 1 addition & 1 deletion

File tree

audit/AUDIT_FINDINGS_INDEX.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ implementation ticket.
239239
| [SR-AUD-225](modules/time-zone/include/System/TimeZoneInfo.hpp.audit.md#sr-aud-225--medium--createcustomtimezone-accepts-empty-ids-and-offsets-outside-the-managed-range) | medium | remediated | `TimeZoneInfo.hpp`, `TimeZoneInfoTests.cpp` | C++ creates empty-ID and +15-hour zones; .NET rejects them with argument diagnostics. |
240240
| [SR-AUD-226](modules/time-zone/include/System/TimeZoneInfo.hpp.audit.md#sr-aud-226--medium--adjustmentrule-accepts-an-effective-end-date-before-its-start-date) | medium | remediated | `TimeZoneInfo.hpp`, `AdjustmentRuleTests.cpp` | C++ accepts reversed 2025 date range while current .NET throws ArgumentException. |
241241
| [SR-AUD-227](modules/time-zone/include/System/TimeZoneInfo.hpp.audit.md#sr-aud-227--medium--zone-equality-is-case-sensitive-while-its-hash-is-case-insensitive-and-net-treats-custom-ids-case-insensitively) | medium | remediated | `TimeZoneInfo.hpp`, `TimeZoneInfoTests.cpp` | C++ Zone/zone equality is false despite lowercased hashes; current .NET custom zones compare true. |
242-
| [SR-AUD-228](modules/time-zone/include/System/TimeZoneInfo.hpp.audit.md#sr-aud-228--medium--hassamerules-reduces-distinct-adjustment-histories-to-current-offset-plus-a-dst-flag) | medium | confirmed (design-complete) | `TimeZoneInfo.hpp`, `TimeZoneInfo.cpp`, `TimeZoneInfoTests.cpp` | C++ says New York and Havana have same rules because only offset/DST bool match; .NET returns false. |
242+
| [SR-AUD-228](modules/time-zone/include/System/TimeZoneInfo.hpp.audit.md#sr-aud-228--medium--hassamerules-reduces-distinct-adjustment-histories-to-current-offset-plus-a-dst-flag) | medium | confirmed (design-complete) | `TimeZoneInfo.hpp`, `TimeZoneInfo.cpp`, `TimeZoneInfoTests.cpp` | C++ says New York and Havana have same rules because only offset/DST bool match; .NET returns false. **THE SELECTED REPAIR WAS MEASURED AND CANNOT WORK (2026-08-18, #2185 returned to `needs_user`).** The design was *"a `vector<shared_ptr<AdjustmentRule>>` populated from the year scan #2181 already performs"*, with America/New_York versus America/Havana as the acceptance example. **Measured, the twelve-month scan produces byte-identical output for both zones** — same standard offset, same daylight offset, same daylight *months*; they differ only in the transition **hour**, which month sampling cannot see. A test written against that design would have passed while the finding stood. **And a one-year scan does not generalise**: New_York transitions on 1995-04-02, 2007-03-11 and 2025-03-09, because the US rule changed in 2007 — which is exactly why .NET returns **several** `AdjustmentRule` objects with different `DateStart`/`DateEnd`, each carrying a *recurrence* taken from tzdata's RULE records. libc exposes only resolved instants, never the recurrence, so a rule derived from one year would be **invented content in a .NET-named API**, which SA-5 forbids. The finding is not closable by sampling libc at any granularity; it needs tzdata's own rule structures, which is a different and larger piece of work. The layout gate the ticket measured (`sizeof(TimeZoneInfo)` 160 → 184) is real but is now the *smaller* obstacle, and the approval it was blocked on is **not** what blocks it — the design is. The failure remains one-directional: this port can only be too permissive, never too strict. |
243243
| [SR-AUD-229](modules/time-zone/include/System/TimeZoneInfo.hpp.audit.md#sr-aud-229--medium--iana-lookup-records-the-current-daylight-offset-as-baseutcoffset-rather-than-the-zones-standard-offset) | medium | remediated | `TimeZoneInfo.hpp`, `TimeZoneInfo.cpp`, `TimeZoneInfoTests.cpp` | C++ records current New York daylight offset -4 as BaseUtcOffset while .NET reports invariant standard -5. |
244244
| [SR-AUD-230](modules/threading-tasks/include/System/Threading/Tasks/TaskCanceledException.hpp.audit.md#sr-aud-230--high--the-public-task-property-retains-a-dangling-raw-pointer-after-its-source-task-is-destroyed) | high | remediated | `TaskCanceledException.hpp`, `TaskCanceledException.cpp`, `TasksTests.cpp` | Task-taking construction stores a raw non-owning Task pointer; ASan reports stack-use-after-scope when an exception exposes it after a local Task dies. **REMEDIATED (#1970, 2026-08-17), at no public-signature cost.** The constructor still takes `const Task*` and the property still returns `const Task*`; what changed is that the exception now OWNS what it points at. A `Task` in this port is a **handle** over a `std::shared_ptr<State>`, so the exception stores a copy of the handle: the state stays alive for as long as the exception is reachable, **and** stays observed, so a status change after construction is visible through `getTaskProperty()`. That second half is what makes it .NET's contract rather than a safe approximation — `TaskCanceledException.Task` there is a GC-tracked reference to the same object, not a snapshot. `std::shared_ptr<const Task>` rather than `std::optional<Task>` because `Task.hpp` includes this header, so a by-value member would be a circular include. **Layout: `sizeof` 192 → 200**, `alignof` 8 unchanged, no vtable or `noexcept` change; landed under `docs/StandingApprovals.md` SA-3 with the figures pinned as a relationship. **One pre-existing test was INVERTED, and the reason matters**: `TaskCtor_StoresTaskPointer` asserted ADDRESS identity, which is precisely the borrowing this finding reports, and is also the wrong analogue of .NET's contract — there `Task` is a reference type, here it is a value-semantics handle, so the counterpart of *the same object* is *the same state*. +4 tests; the mutation that borrows the caller's pointer again through `shared_ptr`'s aliasing constructor is caught. Downstream measured under SA-2: zero `TaskCanceledException` sites in `cna` or `mobile-eggbert`. `docs/Migration-TaskCanceledExceptionOwnsItsTask.md`. **CCF-019 is NOT closed by this** — the async-capture members (#1959, #2066, #2088, #2134) and the owned-tree members remain open. |
245245
| [SR-AUD-231](modules/threading-tasks/include/System/Threading/Tasks/Task.hpp.audit.md#sr-aud-231--medium--task-entry-points-accept-empty-delegates-and-defer-a-managed-argument-error-into-an-asynchronous-bad_function_call) | medium | remediated | `Task.hpp`, `TaskFactory.hpp`, `Parallel.hpp`, `TasksTests.cpp` | Empty native action/function/continuation values return a task and fault later with bad_function_call, where current .NET synchronously rejects null delegates. **Remediated (#1965, 2026-08-03, cause TC-A — CCF-011 in a third module).** Enumerated by measurement as **22 public entries, not the two named** (eleven distinct bodies edited; the rest inherit the check by forwarding): every `Task`/`TaskT`/`TaskFactory`/`Parallel` entry now decides emptiness at the public boundary, before any worker is started or continuation registered, with .NET's own parameter name (`action`, `function`, `continuationAction`, `continuationFunction`, `body`). Four premises corrected: (1) **`Parallel`'s failure was catchable** — it already arrived as `System::AggregateException`, so CCF-011's "wrong type" consequence applies only to the sixteen Task/TaskT/TaskFactory entries; (2) **`Parallel::Invoke` is not an `ArgumentNullException` site** — .NET reports a null *element* with a bare `ArgumentException(SR.Parallel_Invoke_ActionNull)`, the same §17.1-shaped trap that caught #1951; (3) the failure was **data-dependent in two shapes the finding does not name** — a zero-length range/empty source ran no iteration, and an already-cancelled token short-circuited before the action, so both returned normally; (4) **`ContinueWith` did register** the empty continuation and returned a faulted task rather than an error. Zero `bad_function_call` and zero deferred aggregates remain reachable from any public entry; ASan+UBSan+LSan 0→0; +37 tests (171→208); no signature, layout, vtable or edge change. |

plan.sqlite3

4 KB
Binary file not shown.

0 commit comments

Comments
 (0)