Repository navigation
Reject non-one-based out/tt in sorted-batch itp(out, ts) kernels - #622
ChrisRackauckas-Claude wants to merge 2 commits into
Conversation
The sorted-batch kernels behind `itp(out, ts)` (LinearInterpolation, QuadraticInterpolation, AkimaInterpolation, ConstantInterpolation, QuadraticSpline, CubicSpline, CubicHermiteSpline, QuinticHermiteSpline) index `out` and `tt` assuming 1-based indexing, checking only `length`. An OffsetArray `out` or `ts` reads and writes out of bounds, returning silently wrong values and segfaulting in one probe. Add `Base.require_one_based_indexing(out, tt)` at each entry point so a non-one-based `out`/`ts` errors clearly instead of corrupting memory. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude <noreply@anthropic.com> Agent-Harness: Claude Code Agent-Model: claude-sonnet-5 Agent-Session: https://claude.ai/code/session_01LPHREnnonfLg1VcE1EJovv
The previous fix called Base.require_one_based_indexing(out, tt) unconditionally, which rejected an OffsetArray out/tt even on the unsorted map!(A, out, tt) path -- a path that already handles arbitrary axes correctly on master (map! iterates positionally regardless of indexing). That made previously-working calls regress to an error. Guard only the unsafe @inbounds sorted-batch kernel: when out or tt has non-standard axes, skip the sorted fast path and fall through to map!, which is correct for any axes. One-based, sorted input still takes the fast path; non-one-based input of any order now gives the same correct result it gave on master instead of corrupting memory (previously) or throwing (previous fix). Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude <noreply@anthropic.com> Agent-Harness: Claude Code Agent-Model: claude-sonnet-5 Agent-Session: https://claude.ai/code/session_01LPHREnnonfLg1VcE1EJovv
Independent review (Devin CLI 3000.11.3, model fusion-claude-opus-5-5-high-sidekick-swe-2-medium): MERGE, risk low. Full reviewVERDICT: MERGE Review of #622 (head Blocking findingsNone. Non-blocking findings
What I ranAll runs used
What I did not verify
🤖 Posted by an AI agent — harness: Devin CLI 3000.11.3 (review), Claude Code 2.1.285 (posting) · model: fusion-claude-opus-5-5-high-sidekick-swe-2-medium |
What changed and why
The sorted-batch kernels behind
itp(out, ts)(LinearInterpolation,QuadraticInterpolation,AkimaInterpolation,ConstantInterpolation,QuadraticSpline,CubicSpline,CubicHermiteSpline,QuinticHermiteSpline)only check
length(out) == length(tt)before indexing both arrays as ifthey were 1-based. An
OffsetArrayoutorttis therefore read/writtenout of bounds, returning silently wrong values, and a prior probe
segfaulted the GC when all 10 interpolation types were exercised this way
in one process.
Update: the first version of this PR added
Base.require_one_based_indexing(out, tt)unconditionally right after thelength check. Independent review (Cursor, round 1) correctly flagged that
as a regression: the unsorted path already falls through to
map!(A, out, tt), which handles arbitrary axes correctly on master(confirmed empirically below), so unconditionally throwing broke calls
that previously worked.
Fixed by guarding only the unsafe
@inboundssorted-batch kernel:Non-one-based
out/tt(sorted or not) now always routes tomap!,which is correct for any axes. One-based, sorted input still takes the
fast
@inboundspath. No input that worked on master now errors; theonly behavior change is that the two inputs that were previously
silently wrong (sorted
tt/outwith offset axes) are now correct.Failing before / passing after
Before (unmodified
master, commitb98db80), reproduced in a cleantemp environment with
Pkg.developpointed at the unmodified checkout:LinearInterpolation(sin.(1:10), 1:10)(zeros(2), OffsetArray([1.5, 2.5], 0:1))silently returns
[0.5252, 2.4457]on master where[0.8754, 0.5252]iscorrect — i.e. it's reading/writing through the wrong 0-based offsets.
That input is sorted, so it hits the broken fast path.
Confirmed separately that master's unsorted path already gives the
correct answer via
map!, which is exactly what this PR's fix now alsoguarantees for the sorted case:
After this fix, the sorted case is also correct:
Updated test in
test/interpolation_tests.jl("OffsetArray batch safety")exercises all 8 covered types with offset
out, offsettt, and bothoffset together, for both sorted and unsorted query order, and asserts
the result matches the scalar call in every case — including the cases
that already worked on master (unsorted, offset) and the case that was
silently wrong on master (sorted, offset). This test fails against
unmodified master (wrong values on the sorted/offset case) and passes
with the fix; it also fails against the first version of this PR (throws
on the unsorted/offset case) and passes with the current fix.
What was not verified
types evaluated with offset arrays in one process); the out-of-bounds
write this PR closes is the same code path that produced it.
preJulia channel were notexercised locally.
CoreandQAgroups were run on Julia 1.12 and 1.10 (lts) after thisfix;
preis listed intest_groups.tomlbut was not run.Anything a reviewer should push back on
Base.has_offset_axesis the right public-ish Base check for "does thisneed the
map!fallback", matching the semanticsmap!/copyto!alreadyuse internally, but worth double-checking there isn't a cheaper
AbstractUnitRange-based check the package would prefer for consistencywith
_cumulative_integral's use ofBase.require_one_based_indexingelsewhere (that call site has no working fallback, so throwing there is
still correct and unrelated to this change).
Please ignore until reviewed by @ChrisRackauckas.
Risk assessment
itp(out, ts)methods ininterpolation_methods.jl. One-basedout/tt(the overwhelming commoncase) is unaffected; non-one-based input now always gives a correct
result via the existing
map!fallback instead of either corruptingmemory (master) or erroring (this PR's first version). No public API
signature changes.
regression the first version introduced and this version fixes.
(regression on the unsorted offset path); addressed in this revision.
A fresh independent pass is pending on the current head.
wants an independent pass before merge despite being low-risk.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LPHREnnonfLg1VcE1EJovv
Independent review: Devin Fusion (fusion-claude-opus-5-5-high-sidekick-swe-2-medium) rated it low, verdict MERGE: #622 (comment)