Changes
- BC BREAK: a negative integer offset now stores that key instead of appending.
Integer keys are unsigned machine words, so $j[-1] = $v addresses the maximum
index and reads back as -1. Previously every key in [PHP_INT_MIN, -1] was
discarded and the value appended at the next free index, so isset($j[-1]) was
false immediately after the write. See MIGRATION_2.5.0.md.
- BC BREAK: $j[] = $v now throws when the maximum index is occupied, instead of
wrapping onto index 0 and overwriting it.
- FIX: $j[] = $v no longer loses a value after a negative-offset write left the
append watermark stale
- FIX: map()/filter() preserve negative keys instead of relocating them
- SECURITY: fix use-after-free write on *_TO_MIXED overwrite/unset when a stored
value's destructor re-enters and mutates the same array (write-before-dtor /
delete-before-free)
- SECURITY: fix type confusion (UB) in getAll()/next()/rewind() for adaptive
types, which queried a JudyHS operation against the JudyL (SSO) store
- FIX: add get_gc handler so reference cycles through MIXED values are collectable
(previously leaked until request shutdown)
- FIX: STRING_TO_*_ADAPTIVE counter no longer double-counts when the value 0 is
re-stored (size/count/equals/averageValues were affected)
- FIX: $j[] = append after clone/fromArray/putAll no longer overwrites index 0
- FIX: first()/last()/searchNext()/prev() now work on adaptive types
- FIX: fromArray()/putAll() reject non-integer keys on integer-keyed types instead
of inserting at the string's hash
- FIX: __unserialize() on a populated object frees prior contents (no leak)
- FIX: forEach()/filter()/map() callbacks may re-enter without corrupting iteration
- FIX: equals() on INT_TO_PACKED no longer risks an infinite loop
- FIX: allocation failure (JERR) during write/unset is reported as failure, not success
- FIX: bulk operations stop on the first thrown key instead of continuing with a
pending exception; clone/slice no longer leak zvals or diverge on OOM paths
- FEATURE: keys(), values() and toArray() take an inclusive [$start, $end] key
range, where null leaves that side unbounded. All key types; string-keyed types
require string bounds and compare them lexicographically. A bounded read is one
traversal writing straight into the PHP array — prefer it to slice($lo,
$hi)->keys(), which copies a whole sub-array first.
- FEATURE: size($start, $end) counts that same range, including on the six
string-keyed types, without materialising anything. Previously it accepted
string bounds, ignored them, and returned the whole-array count. Its parameters
were renamed $index_start/$index_end -> $start/$end to match the other range
methods, which breaks named-argument callers only; its defaults moved from
(0, -1) to (null, null). populationCount() is unchanged and stays
integer-keyed-only — it answers from libJudy's O(1) population cache, which the
string-keyed stores lack. See MIGRATION_2.5.0.md.
- FEATURE: new Judy($type, optimizeIteration: true) mirrors payloads into the key
index for 24-47% faster ordered reads, at a write-path and memory cost. Opt-in,
per-instance, off by default, and honoured only by STRING_TO_INT_HASH and
STRING_TO_INT_ADAPTIVE; isIterationOptimized() reports what took effect.
- FEATURE: Judy instances are now legible to debuggers — var_dump()/print_r()
show type, count, memory usage, first/last key and a bounded element preview
(judy.debug_preview_size). Ships lldb/gdb pretty-printers for the extension's
own structs under scripts/.
- FEATURE: set operations (intersect/diff/xor) now supported for STRING_TO_INT_ADAPTIVE
- BUILD: extension compiles warning-free; CI now fails on any new compiler warning
- BUILD: minimum PHP raised to 8.1 (PHP 8.0 is no longer tested in CI)