You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
CHKNT's null-terminator guard now covers float/double, struct/ union, and wide _BitInt/_Decimalntarray pointees — previously
only integer and pointer pointees were guarded (a deliberate v1
exclusion, #923). Investigating the exclusion for a decision pass turned
up an actual hole: _BitInt(128) passed the old is_integer() gate and
set the guard flag, but its store lowers through codegen's memcpy branch,
which returned before the old CHKNT-only emission site — so the flag
was set and nothing was ever checked. float/double now reuse CHKNT
itself (their value's raw bits are transferred into an integer register
first). A new opcode, CHKNTZ, guards the memcpy-lowered pointees
(struct/union, wide _BitInt, _Decimal) that never pass through a
single value register: it scans the source bytes for any non-zero byte
before the underlying memcpy runs, so the terminator slot is never
actually clobbered when it traps. long double stays unguarded on
purpose — its widened terminator slot is 16 bytes but the actual store is
an 8-byte flat-double FSTR, so no opcode inspects its full stored
representation. CHKNTZ only guards a whole-object store through the
pointer itself; a member-wise write into the same slot (tbl[n].a = 1;)
is a known, separately-tracked gap (#950). See SAFETY.md § Checked Pointers and VM.md § Safety Opcodes (#939)
Fixed
node_has_side_effects() now sees through a ternary's branches — ND_COND (the cond ? then : els ternary) stores its two branches in ->then/->els, separately from ->lhs/->rhs, and the side-effect
check that gates checked-pointer bounds declarations
(resolve_bounds_tokens()) and member object-expression instrumentation
(compute_checked_bounds(), #921/#945/#947) never recursed into them —
so count(c ? i++ : 3) was wrongly accepted, and i++ would have run on
every checked access instead of never. Now rejected at the declaration,
same as count(i++). See SAFETY.md § Checked Pointers (#949)
GNU elvis (a ?: b) no longer forces a compiler temp when the
condition is a plain, cheaply re-readable operand — a ?: b always
desugared to tmp = a, tmp ? tmp : b, whose ND_ASSIGN made a pure
elvis bounds expression like count(n ?: 8) fail the check above even
though it has no side effects. When a is an ND_VAR/ND_NUM and not volatile/_Atomic, the desugar now builds a ? clone(a) : b directly
instead, which reads as side-effect-free; every other condition shape
keeps the original temp-based desugar. See SAFETY.md § Checked Pointers (#949)
Changed
Checked-pointer bounds propagation and assignment-time bounds
implication also evaluate a member object expression once per
assignment — q = arr[k].p; (bounds propagation) and arr[k].p = src;
(assignment-time bounds implication) used to re-evaluate k's indexing
arithmetic 2-3 times while building that one assignment's bounds, the
same duplication #945 already fixed for a direct per-access check. Both
passes now share #945's hoist-into-a-temp treatment via a second temp
allocator, needed because they run after their function's own local-list
snapshot. Pure performance cleanup — same checks, same traps, no
user-visible behavior change. See SAFETY.md § Checked Pointers (#947)
Documented
Why a side-effecting member object expression stays uninstrumented — f()->p[i] is declined by checked-pointer bounds checking (no check
emitted) because the hoist introduced by #945/#947 rewrites the bounds
expressions, not the access itself: f()->p[i]'s own access still calls f() once regardless, so emitting a check would call it again just to
build the hoisted object-expression temp — an extra evaluation of a
side-effecting expression that a --checked-pointers build must never
introduce over a default build. See SAFETY.md § Checked Pointers (#948)