| Changelog for Judy |
| Release |
What has changed? |
| 2.6.0 |
- FIX: use-after-free during teardown of the MIXED types (INT_TO_MIXED,
STRING_TO_MIXED, STRING_TO_MIXED_HASH, STRING_TO_MIXED_ADAPTIVE), reachable
through both Judy::free() and ordinary object destruction (#162). Freeing a
slot calls zval_ptr_dtor(); where the value is a shared collectable one that
fills the GC root buffer, gc_collect_cycles() then runs synchronously inside
the free loop and re-enters judy_object_get_gc() on the half-destroyed
object. The usual symptom is a "zend_mm_heap corrupted" abort. This defect
PREDATES the bundled libJudy and is present in every previously shipped
release: anyone using a MIXED type should upgrade.
- FIX: libJudy compiled with aggressive loop optimization silently loses
Judy::BITSET keys — jp_1Index is 8 bytes where the code writes up to 15
(#131). Fixed in the bundled tree (patch P1) and guarded by a differential
fuzzer that re-proves itself on every CI run by planting the defect and
failing if the fuzzer does not catch it.
- FIX: further upstream libJudy 1.0.5 defects, fixed in the bundled tree
(#127): the SEARCH_LINEAR/COPYINDEX pair, which was a no-op masking silent
data loss; an off-by-one in InsArray; an out-of-scope read in Cascade; and
hygiene fixes (patches P2-P7). Every patch carries an entry in
libjudy/PATCHES.md and a per-file LGPL section 2(b) change notice.
- BUILD: the bundled, patched libJudy is now the DEFAULT build. ./configure
needs no system library and downloads nothing at build time, and the Windows
build no longer regex-patches library sources in CI. --with-judy=DIR still
links a system libJudy, is CI-tested on every push, and stays supported
indefinitely.
- BUILD: a 32-bit target is now refused with a clear message instead of being
mis-built, and the release matrix is constrained to x64 (#159, #160).
- PERF: integer-keyed paths gain hardware popcount (#149) and a byte-order fix
on the JudyL descend (#150); the string layer loses redundant work (#154).
Figures are in BENCHMARK.md and are deliberately not restated here. Note
that the delivered speedup is NOT one number: with linkage held constant
against an unpatched build of the same tree, about 96.5% of the gain on
integer paths is attributable to these patches but only about 40% of the
gain on string paths, the remainder being static-versus-shared linkage. |
| 2.5.2 |
- PERF: mergeWith() no longer re-descends from the root for a key its own
cursor is already standing on. The slot-to-zval conversion is shared with the
descending read path so the two cannot drift, and the cursor's slot is reused
only where it genuinely holds the value: directly on the integer-keyed and
trie types, and on *_HASH / *_ADAPTIVE only when the payload is mirrored
(an optimizeIteration instance of STRING_TO_INT_HASH, or long-keyed
STRING_TO_INT_ADAPTIVE). Behaviour-preserving: the three new .phpt files pass
against the pre-change build too. **No performance number is claimed** — the
redundant descend is gone, but no benchmark accompanies the change and none
should be inferred pending a run on an idle host. baselines/latest.json and
BENCHMARK.md figures are untouched.
- DOC: API.md now documents that toArray() coerces integer-looking string keys
on string-keyed types ("42" comes back as int 42, while "07" and " 42" stay
strings) and that feeding such a key back as an offset throws. The warning
reached the stub and AGENTS.md in 2.5.1 but not API.md, which is the reference
composer.json advertises.
- DOC: BENCHMARK.md records why php-code-coverage and Infection were examined
and rejected as Judy fits. |
| 2.5.1 |
- FIX: string keys containing an embedded NUL byte are now rejected with an
exception on all six string-keyed types, instead of being silently truncated
at the NUL. Previously STRING_TO_INT and STRING_TO_MIXED truncated the key, so
"ab\0cd" and "ab" collided and one value was destroyed with no signal; the
four *_HASH/*_ADAPTIVE types already rejected such keys on write but still
truncated them on every ordered and range operation (first/last/searchNext/
prev/slice/deleteRange and the bounds of keys/values/toArray/size), because
all six seek through the same JudySL key index. JudySL keys are NUL-terminated
by construction, so rejecting is the only correct behaviour. High-byte keys
(0x80-0xFF) are unaffected and remain binary-safe, including 0xFF prefix-carry
arithmetic. See MIGRATION_2.5.0.md. |
| 2.5.0 |
- 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) |
| 2.4.2 |
- 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: 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) |
| 2.4.1 |
- FIX: PHP 8.6 build -- XtOffsetOf() alias removed in PHP 8.6.0alpha2; use
standard C offsetof() instead (thanks Remi Collet, PR #65) |
| 2.4.0 |
- NEW FEATURE: Fast ZPP (Zend Parameter Parsing) macros across all methods for improved performance
- NEW FEATURE: Native zend_object_handlers (read/write/has/unset_dimension) bypass ArrayAccess overhead
- NEW FEATURE: Native C iterators via get_iterator for zero-overhead foreach loops
- NEW FEATURE: BITSET set operations -- union(), intersect(), diff(), xor() methods
- NEW FEATURE: INT_TO_INT set operations -- union/intersect/diff/xor with left-wins value semantics
- NEW FEATURE: slice($start, $end) for efficient range extraction on all array types
- NEW FEATURE: JsonSerializable interface -- json_encode() works natively on Judy arrays
- NEW FEATURE: __serialize()/__unserialize() for native PHP serialize/unserialize support
- NEW FEATURE: Batch operations -- fromArray(), toArray(), putAll(), getAll(), increment()
- NEW FEATURE: INT_TO_PACKED type (type 6) -- GC-free opaque value storage via php_var_serialize
- NEW FEATURE: STRING_TO_MIXED_HASH type (type 7) -- JudyHS-backed O(1) string-to-mixed map
- NEW FEATURE: STRING_TO_INT_HASH type (type 8) -- JudyHS-backed O(1) string-to-int map
- NEW FEATURE: STRING_TO_MIXED_ADAPTIVE type (type 9) -- SSO short-string optimization via JudyL
- NEW FEATURE: STRING_TO_INT_ADAPTIVE type (type 10) -- SSO for string-to-int
- NEW FEATURE: .stub.php arginfo generation replaces hand-written C arginfo
- NEW FEATURE: keys(), values() -- native C extraction of keys and values (2-3x faster)
- NEW FEATURE: sumValues(), averageValues() -- C-level aggregation for integer-valued types
- NEW FEATURE: populationCount(start, end) -- range counting via Judy internal population cache
- NEW FEATURE: deleteRange(start, end) -- bulk deletion in a single C pass
- NEW FEATURE: equals(Judy other) -- short-circuit identity comparison
- NEW FEATURE: C-level forEach(), filter(), map() -- bypass PHP Iterator protocol
- NEW FEATURE: String set operations -- union/intersect/diff/xor for STRING_TO_INT and STRING_TO_MIXED types
- NEW FEATURE: mergeWith(Judy $other) -- in-place merge for accumulation patterns (avoids union() allocation)
- FIX: JudySL hang on Windows x64 -- cJU_MASKATSTATE 0xffL truncation at States 5-8 (issue #46)
- FIX: Double-traversal elimination in adaptive types -- removes redundant JLG/JHSG before JLI/JHSI
- FIX: hs_array NULL-check in judy_free_array_internal prevents memory leak in adaptive types
- PERF: JLG+JLI elimination -- 50% fewer tree traversals in write paths for STRING types
- PERF: Branch prediction hints (JUDY_LIKELY/JUDY_UNLIKELY) on hot lookup and validation paths
- PERF: LTO and loop unrolling enabled in compiler flags
- PERF: Memory layout repacked for cache-line alignment and reduced padding
- PERF: Stack safety -- 64KB stack buffers replaced with heap-allocated key_scratch buffer (Fiber-safe)
- PERF: foreach iterator overhaul -- valid flag, heap key buffer, zend_string reuse
- PERF: tagged-union packed format for INT_TO_PACKED -- eliminates serialize for scalars
- PERF: O(1) count() for all types -- counter on every insert/delete replaces O(n) scans
- PERF: type-specialized bulk-insert loops in fromArray()/putAll()/__unserialize()
- PERF: ecalloc to emalloc on 9 zval allocs; direct judy_free_array_internal in __destruct
- IMPROVEMENT: 174 tests covering all features |
| 2.3.0 |
- NEW FEATURE: Windows PECL DLL builds now published with each release (PHP 8.1-8.5, x86/x64, TS/NTS)
- NEW FEATURE: PHP 8.5 support added to CI matrix
- FIX: PHP 8.1+ compile fix - ZVAL_DUP replaced with ZVAL_COPY throughout
- FIX: free_obj memory leak resolved (Judy array, zvals, and iterator state now properly freed)
- FIX: BITSET clone bug fixed (was iterating new array instead of source)
- FIX: STRING_TO_INT counter no longer overcounts on value overwrite
- FIX: E_ERROR replaced with exceptions in constructor and write_dimension
- FIX: foreach-by-ref now throws a proper error instead of E_ERROR; fixed emalloc leak
- FIX: long->zend_long throughout; JLF() iterator bug fixed; redundant JLG lookups removed
- FIX: Removed -march=native and -flto from config.m4 for portability
- IMPROVEMENT: CI workflow consolidated; Windows benchmarks added to CI reporting |
| 2.2.0 |
- **BREAKING CHANGE**: Renamed Judy::next() to Judy::searchNext() to resolve Iterator interface conflicts
- **NEW FEATURE**: Implemented proper Iterator interface support (rewind, valid, current, key, next methods)
- **NEW FEATURE**: Added support for SPL iterators (LimitIterator, FilterIterator, etc.)
- **IMPROVEMENT**: Enhanced benchmark examples with accurate memory measurements
- **IMPROVEMENT**: Updated BENCHMARK.md with comprehensive design explanations and performance analysis
- **IMPROVEMENT**: Added comprehensive benchmark suite with statistical analysis
- **IMPROVEMENT**: Fixed iterator performance analysis and methodology
- **FIX**: All tests now pass (100% success rate)
- **FIX**: Improved memory measurement reliability across different environments
- **FIX**: Fixed critical iterator bugs (next, rewind, valid methods)
- **DOCUMENTATION**: Added O(log n) vs O(1) complexity explanations
- **DOCUMENTATION**: Enhanced benchmark methodology with environment details |
| 2.1.0 |
- Performance improvements for string-keyed arrays through strict key typing (Breaking Change).
- Modernized zval handling for better performance and PHP 8+ compatibility.
- Fixed test suite to accomodate for strict key typing. |
| 2.0.0 |
- Added compatibility for PHP 8.0 and newer.
- Dropped support for PHP versions older than 8.0 (Breaking Change).
- Added a comprehensive benchmark suite (`examples/run-benchmarks.php`) to test realistic workloads.
- Added a `BENCHMARK.md` file with detailed performance results and recommendations.
- Added a `Dockerfile` to provide a consistent development and testing environment.
- Updated `README.md` with modern installation instructions and a link to the benchmark results. |
| 1.0.2 |
- Bug fix with count/size, see https://github.com/orieg/php-judy/issues/15
- Add missing test files in package.xml |
| 1.0.1 |
- Fixes for Windows DDL creation
- Support for PHP 5.5
- Add support for Travis CI
- Update minimum PHP version to 5.3 |
| 1.0.0 |
- First stable release based on 0.1.6 beta |
| 0.1.6 |
- Performance improvement from GitHub pull request #10
- Add countable spl interface from GitHub pull request #9 |
| 0.1.5 |
- Multiple bug fixes and improvment from GitHub pull request #8 |
| 0.1.4 |
- Bug fix foreach with usigned/signed int, see https://github.com/orieg/php-judy/issues/3
- Bug fix segfault with iterator, see https://github.com/orieg/php-judy/issues/2
- Add new test case + fix some ZVAL refcount |
| 0.1.3 |
- Add PHP 5.4 support
- Bug fix count() for STRING_TO_MIXED type, cf. https://github.com/orieg/php-judy/issues/1 |
| 0.1.2 |
- Update examples php code
- Fix Mac OS X support
- Fix infinite loop with foreach() on Windows |
| 0.1.1 |
- Implement array access and iterator to provide access to judy objects as arrays
- Add a judy_type() function and Judy::getType() method
- Remove unused methods, refactor and clean-up some code
- Proper Windows support |
| 0.0.1 |
- Initial PECL release |
|