pycedar 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {pycedar-0.2.0 → pycedar-0.2.2}/CHANGELOG.md +70 -1
  2. {pycedar-0.2.0 → pycedar-0.2.2}/MANIFEST.in +1 -0
  3. {pycedar-0.2.0/pycedar.egg-info → pycedar-0.2.2}/PKG-INFO +25 -3
  4. {pycedar-0.2.0 → pycedar-0.2.2}/README.ja.md +23 -2
  5. {pycedar-0.2.0 → pycedar-0.2.2}/README.md +24 -2
  6. pycedar-0.2.2/pycedar/VERSION +1 -0
  7. pycedar-0.2.2/pycedar/core/cedar/README.md +132 -0
  8. {pycedar-0.2.0 → pycedar-0.2.2}/pycedar/core/cedar/src/cedarpp.h +44 -24
  9. {pycedar-0.2.0 → pycedar-0.2.2}/pycedar/pycedar.cpp +4799 -8955
  10. {pycedar-0.2.0 → pycedar-0.2.2}/pycedar/pycedar.pyx +20 -15
  11. {pycedar-0.2.0 → pycedar-0.2.2/pycedar.egg-info}/PKG-INFO +25 -3
  12. {pycedar-0.2.0 → pycedar-0.2.2}/pycedar.egg-info/SOURCES.txt +1 -0
  13. {pycedar-0.2.0 → pycedar-0.2.2}/tests/test_pycedar.py +27 -0
  14. {pycedar-0.2.0 → pycedar-0.2.2}/tests/test_trie_api.py +69 -0
  15. pycedar-0.2.0/pycedar/VERSION +0 -1
  16. {pycedar-0.2.0 → pycedar-0.2.2}/pycedar/core/cedar/AUTHORS +0 -0
  17. {pycedar-0.2.0 → pycedar-0.2.2}/pycedar/core/cedar/BSD +0 -0
  18. {pycedar-0.2.0 → pycedar-0.2.2}/pycedar/core/cedar/COPYING +0 -0
  19. {pycedar-0.2.0 → pycedar-0.2.2}/pycedar/core/cedar/GPL +0 -0
  20. {pycedar-0.2.0 → pycedar-0.2.2}/pycedar/core/cedar/LGPL +0 -0
  21. {pycedar-0.2.0 → pycedar-0.2.2}/pycedar/core/cedar/THANKS +0 -0
  22. {pycedar-0.2.0 → pycedar-0.2.2}/pycedar/pycedar.pxd +0 -0
  23. {pycedar-0.2.0 → pycedar-0.2.2}/pycedar.egg-info/dependency_links.txt +0 -0
  24. {pycedar-0.2.0 → pycedar-0.2.2}/pycedar.egg-info/top_level.txt +0 -0
  25. {pycedar-0.2.0 → pycedar-0.2.2}/pyproject.toml +0 -0
  26. {pycedar-0.2.0 → pycedar-0.2.2}/setup.cfg +0 -0
  27. {pycedar-0.2.0 → pycedar-0.2.2}/setup.py +0 -0
  28. {pycedar-0.2.0 → pycedar-0.2.2}/tests/test_readme.py +0 -0
@@ -7,6 +7,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.2] - 2026-09-16
11
+
12
+ ### Added
13
+
14
+ - `benchmarks/bench.py`, which times the operations pycedar is used for so that
15
+ a performance change can be measured rather than assumed.
16
+
17
+ ### Changed
18
+
19
+ - `dict.get()`, `set()`, `setdefault()`, `update()` and `get_node()` are roughly
20
+ twice as fast. They were declared with a fused `str`/`bytes` parameter, and
21
+ Cython's runtime dispatch for that cost more than the trie operation itself.
22
+ The parameter is now `object`, which is what `d[key]` already used; the key
23
+ type is still enforced by the trie underneath, so behaviour is unchanged.
24
+ - Sentinel checks on the lookup paths compare against C constants instead of
25
+ building a Python tuple of Python ints on every call, which speeds up
26
+ `d[key]`, `key in d` and the traversals.
27
+
28
+ Measured on 20000 random keys, CPython 3.13, nanoseconds per operation:
29
+
30
+ | operation | 0.2.1 | 0.2.2 |
31
+ | --- | --- | --- |
32
+ | `d.get(key)` | 302 | 135 |
33
+ | `d.set(key, value)` | 242 | 108 |
34
+ | `d.update(key, delta)` | 235 | 112 |
35
+ | `d.setdefault(key)` | 290 | 129 |
36
+ | `d.get_node(key)` | 348 | 177 |
37
+ | `d[key]` | 165 | 132 |
38
+ | `key in d` | 133 | 123 |
39
+
40
+ ### Fixed
41
+
42
+ - `node.traverse()` compared integers with `is`, which only worked because
43
+ CPython interns small integers. It now uses `!=`.
44
+
45
+ ## [0.2.1] - 2026-09-16
46
+
47
+ ### Added
48
+
49
+ - `pycedar/core/cedar/README.md`, recording what was modified in the vendored
50
+ copy of cedar and why, that upstream's `cedarpp.h` has been unchanged since
51
+ 2017 despite the 2022 tarball, and why that tarball was deliberately not
52
+ re-vendored.
53
+
54
+ ### Changed
55
+
56
+ - Allocation failures now raise `MemoryError` instead of `RuntimeError`. The
57
+ vendored cedar throws `std::bad_alloc`, which Cython's `except +` maps to
58
+ `MemoryError`, so every allocation failure reaches Python as the same, and
59
+ the idiomatic, exception. `RuntimeError` is now reserved for the one
60
+ non-allocation case, a zero-length key reaching cedar's `update()`.
61
+ - The vendored `cedarpp.h` adopts upstream's `clear()` layout and its
62
+ `STATIC_ASSERT` pragmas. Behaviour is unchanged; this removes the six
63
+ compiler warnings the file used to emit and reduces the delta against
64
+ upstream.
65
+
66
+ ### Fixed
67
+
68
+ - An allocation failure inside `save()` or `load()` no longer terminates the
69
+ interpreter. The vendored cedar called `std::exit(1)` from `shrink_tail()`
70
+ and `open()`, killing the process and discarding buffered output; both now
71
+ throw. `open()` resets itself to a valid empty trie first, so the instance
72
+ stays usable instead of being handed back half-built.
73
+ - `load()` no longer leaks a file descriptor every time it fails. Upstream's
74
+ `open()` returns `-1` from nine places without closing the file; 200 failed
75
+ loads leaked exactly 200 descriptors, eventually exhausting the table.
76
+
10
77
  ## [0.2.0] - 2026-09-16
11
78
 
12
79
  This release restores compatibility with current Python and Cython toolchains,
@@ -99,7 +166,9 @@ records; the corresponding tags were added retroactively.
99
166
 
100
167
  - Build failure with clang on macOS.
101
168
 
102
- [Unreleased]: https://github.com/akivajp/pycedar/compare/v0.2.0...HEAD
169
+ [Unreleased]: https://github.com/akivajp/pycedar/compare/v0.2.2...HEAD
170
+ [0.2.2]: https://github.com/akivajp/pycedar/compare/v0.2.1...v0.2.2
171
+ [0.2.1]: https://github.com/akivajp/pycedar/compare/v0.2.0...v0.2.1
103
172
  [0.2.0]: https://github.com/akivajp/pycedar/compare/v0.1.3...v0.2.0
104
173
  [0.1.3]: https://github.com/akivajp/pycedar/compare/v0.1.2...v0.1.3
105
174
  [0.1.2]: https://github.com/akivajp/pycedar/compare/v0.1.1...v0.1.2
@@ -4,6 +4,7 @@ include CHANGELOG.md
4
4
  include pycedar/VERSION
5
5
  include pycedar/pycedar.pxd
6
6
  include pycedar/pycedar.pyx
7
+ include pycedar/core/cedar/README.md
7
8
  include pycedar/core/cedar/AUTHORS
8
9
  include pycedar/core/cedar/BSD
9
10
  include pycedar/core/cedar/COPYING
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycedar
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Python binding of cedar (implementation of efficiently-updatable double-array trie) using Cython
5
5
  Home-page: https://github.com/akivajp/pycedar
6
6
  Author: Akiva Miura
@@ -355,6 +355,12 @@ source.
355
355
  ``save()`` / ``load()`` / ``open()`` return ``0`` on success and ``-1`` on
356
356
  failure instead of raising; check the return value.
357
357
 
358
+ Running out of memory is the exception to that rule: ``save()`` and ``load()``
359
+ raise ``MemoryError`` rather than returning ``-1``. A failed ``load()`` leaves
360
+ the trie **empty**, because the previous contents are released before the new
361
+ ones are allocated. The instance stays valid and can be reused. See
362
+ [``pycedar/core/cedar/README.md``](pycedar/core/cedar/README.md).
363
+
358
364
  ## Development
359
365
 
360
366
  ```shell
@@ -372,6 +378,19 @@ $ python setup.py build_ext --inplace
372
378
 
373
379
  ``./clean.sh`` removes build artifacts.
374
380
 
381
+ ### Benchmarks
382
+
383
+ ``benchmarks/bench.py`` times the operations pycedar is used for. Run it against
384
+ two builds to check whether a change actually paid off:
385
+
386
+ ```shell
387
+ $ python benchmarks/bench.py --label before
388
+ $ python benchmarks/bench.py --label after
389
+ ```
390
+
391
+ It uses ``rich`` for the table when that is installed, and plain text
392
+ otherwise. ``--help`` lists the knobs.
393
+
375
394
  ## Releasing
376
395
 
377
396
  The version lives in a single place, [``pycedar/VERSION``](pycedar/VERSION).
@@ -393,7 +412,10 @@ LGPLv2.1 and BSD-2-Clause. See the license files bundled under
393
412
  ## Credits
394
413
 
395
414
  ``cedar`` is written by Naoki Yoshinaga. The copy vendored under
396
- ``pycedar/core/cedar/`` carries two local modifications, documented at the top
397
- of [``cedarpp.h``](pycedar/core/cedar/src/cedarpp.h).
415
+ ``pycedar/core/cedar/`` carries local modifications.
416
+ [``pycedar/core/cedar/README.md``](pycedar/core/cedar/README.md) records what
417
+ was changed and why, why the 2022 upstream tarball was deliberately not
418
+ re-vendored, and what has to be re-applied if anyone syncs with a newer
419
+ release.
398
420
 
399
421
  See [CHANGELOG.md](CHANGELOG.md) for the list of contributors to each release.
@@ -314,6 +314,12 @@ cedar ネイティブの ``.dat`` 形式は、書き出したマシンのポイ
314
314
  ``save()`` / ``load()`` / ``open()`` は例外を送出せず、成功で ``0``、失敗で
315
315
  ``-1`` を返します。必ず戻り値を確認してください。
316
316
 
317
+ 唯一の例外がメモリ不足で、``save()`` と ``load()`` は ``-1`` を返すのではなく
318
+ ``MemoryError`` を送出します。``load()`` が失敗した場合、cedar は新しい配列を
319
+ 確保する前に既存の配列を解放するため、**トライは空になります**。インスタンス
320
+ 自体は有効なまま再利用できます。詳細は
321
+ [``pycedar/core/cedar/README.md``](pycedar/core/cedar/README.md) を参照してください。
322
+
317
323
  ## 開発
318
324
 
319
325
  ```shell
@@ -331,6 +337,19 @@ $ python setup.py build_ext --inplace
331
337
 
332
338
  ``./clean.sh`` でビルド生成物を削除できます。
333
339
 
340
+ ### ベンチマーク
341
+
342
+ ``benchmarks/bench.py`` が主要な操作の実行時間を計測します。変更に効果があった
343
+ かどうかは、2 つのビルドで実行して比較してください。
344
+
345
+ ```shell
346
+ $ python benchmarks/bench.py --label before
347
+ $ python benchmarks/bench.py --label after
348
+ ```
349
+
350
+ ``rich`` が入っていれば表形式で、無ければプレーンテキストで出力します。
351
+ 指定できる項目は ``--help`` を参照してください。
352
+
334
353
  ## リリース手順
335
354
 
336
355
  バージョンは [``pycedar/VERSION``](pycedar/VERSION) の 1 箇所のみで管理します。
@@ -352,7 +371,9 @@ BSD-2-Clause)。ライセンス文書は
352
371
  ## クレジット
353
372
 
354
373
  ``cedar`` は Naoki Yoshinaga 氏によるものです。``pycedar/core/cedar/`` に同梱
355
- しているコピーには 2 点のローカル変更があり、その内容は
356
- [``cedarpp.h``](pycedar/core/cedar/src/cedarpp.h) の冒頭に記載しています。
374
+ しているコピーにはローカル変更があります。変更内容とその理由、2022 年の上流
375
+ tarball をあえて取り込まなかった判断の経緯、より新しい版へ同期する際に再適用
376
+ すべき内容は
377
+ [``pycedar/core/cedar/README.md``](pycedar/core/cedar/README.md) に記録しています。
357
378
 
358
379
  各リリースの貢献者は [CHANGELOG.md](CHANGELOG.md) を参照してください。
@@ -317,6 +317,12 @@ source.
317
317
  ``save()`` / ``load()`` / ``open()`` return ``0`` on success and ``-1`` on
318
318
  failure instead of raising; check the return value.
319
319
 
320
+ Running out of memory is the exception to that rule: ``save()`` and ``load()``
321
+ raise ``MemoryError`` rather than returning ``-1``. A failed ``load()`` leaves
322
+ the trie **empty**, because the previous contents are released before the new
323
+ ones are allocated. The instance stays valid and can be reused. See
324
+ [``pycedar/core/cedar/README.md``](pycedar/core/cedar/README.md).
325
+
320
326
  ## Development
321
327
 
322
328
  ```shell
@@ -334,6 +340,19 @@ $ python setup.py build_ext --inplace
334
340
 
335
341
  ``./clean.sh`` removes build artifacts.
336
342
 
343
+ ### Benchmarks
344
+
345
+ ``benchmarks/bench.py`` times the operations pycedar is used for. Run it against
346
+ two builds to check whether a change actually paid off:
347
+
348
+ ```shell
349
+ $ python benchmarks/bench.py --label before
350
+ $ python benchmarks/bench.py --label after
351
+ ```
352
+
353
+ It uses ``rich`` for the table when that is installed, and plain text
354
+ otherwise. ``--help`` lists the knobs.
355
+
337
356
  ## Releasing
338
357
 
339
358
  The version lives in a single place, [``pycedar/VERSION``](pycedar/VERSION).
@@ -355,7 +374,10 @@ LGPLv2.1 and BSD-2-Clause. See the license files bundled under
355
374
  ## Credits
356
375
 
357
376
  ``cedar`` is written by Naoki Yoshinaga. The copy vendored under
358
- ``pycedar/core/cedar/`` carries two local modifications, documented at the top
359
- of [``cedarpp.h``](pycedar/core/cedar/src/cedarpp.h).
377
+ ``pycedar/core/cedar/`` carries local modifications.
378
+ [``pycedar/core/cedar/README.md``](pycedar/core/cedar/README.md) records what
379
+ was changed and why, why the 2022 upstream tarball was deliberately not
380
+ re-vendored, and what has to be re-applied if anyone syncs with a newer
381
+ release.
360
382
 
361
383
  See [CHANGELOG.md](CHANGELOG.md) for the list of contributors to each release.
@@ -0,0 +1 @@
1
+ 0.2.2
@@ -0,0 +1,132 @@
1
+ # Vendored copy of cedar
2
+
3
+ This directory holds a vendored copy of `cedar`, the double-array trie
4
+ implementation that pycedar wraps.
5
+
6
+ * Upstream: <http://www.tkl.iis.u-tokyo.ac.jp/~ynaga/cedar/>
7
+ * Author: Naoki Yoshinaga
8
+ * License: GPLv2, LGPLv2.1 and BSD-2-Clause (see `BSD`, `COPYING`, `GPL`, `LGPL`)
9
+
10
+ This file records what was changed locally and why, so that a future maintainer
11
+ who finds a newer tarball on the upstream site can decide what to do without
12
+ repeating the investigation.
13
+ (上流サイトに新しい tarball を見つけた人が、調査をやり直さずに判断できるようにするための記録)
14
+
15
+ ## What is actually used
16
+
17
+ Only **one** file is compiled and shipped. Everything else is here because the
18
+ original tarball was vendored wholesale, and is dead weight.
19
+
20
+ | Path | Status |
21
+ | --- | --- |
22
+ | `src/cedarpp.h` | **Compiled and shipped.** The only file `pycedar.pxd` declares (`cdef extern from "cedarpp.h"`) and the only source `setup.py` ships in `package_data`. |
23
+ | `AUTHORS`, `BSD`, `COPYING`, `GPL`, `LGPL`, `THANKS` | Shipped, for license compliance. |
24
+ | `src/cedar.h`, `src/cedar.cc`, `src/bench.cc`, `src/bench_static.cc`, `src/mkcedar.cc`, `src/Makefile.am`, `Makefile.in` | Present in git, **never compiled and never shipped.** They differ substantially from upstream, which is irrelevant. |
25
+
26
+ ## Local modifications to `src/cedarpp.h`
27
+
28
+ Upstream's error handler terminates the process:
29
+
30
+ ```cpp
31
+ static void _err (const char* fn, const int ln, const char* msg)
32
+ { std::fprintf (stderr, "cedar: %s [%d]: %s", fn, ln, msg); std::exit (1); }
33
+ ```
34
+
35
+ For a library loaded into a Python process that is unacceptable, so **four** of
36
+ the five `_err()` call sites were converted to throw instead. The Cython
37
+ declarations in `pycedar.pxd` carry `except +`, which turns the exception into a
38
+ Python one.
39
+
40
+ | Change | Reason |
41
+ | --- | --- |
42
+ | `#include <new>`, `#include <stdexcept>` | Needed by the `throw` sites below. |
43
+ | `update()`: inserting a zero-length key throws `std::runtime_error` instead of calling `_err()` | Surfaces as a Python exception. `pycedar` rejects empty keys with `KeyError` before reaching this, so it is a backstop. |
44
+ | `_realloc_array()`: a failed `realloc` throws `std::bad_alloc` instead of calling `_err()` | Surfaces as `MemoryError` rather than killing the interpreter. |
45
+ | `shrink_tail()`: a failed `malloc` throws `std::bad_alloc` instead of calling `_err()` | Reached through `save()`, whose `shrink` argument defaults to `True`. Nothing has been mutated at that point, so the trie survives intact. |
46
+ | `open()`: a failed `malloc` calls `fclose()`, then `clear(true)`, then throws `std::bad_alloc` | Reached through `load()`. Unlike the site above, `clear(false)` has already dropped the previous contents, so the object has to be reset to a valid empty trie before the exception escapes. |
47
+ | `open()`: every early `return -1` closes the file first | Upstream leaks the `FILE*` on all nine of those paths. A loop of failed loads exhausts the descriptor table: 200 failed loads leaked exactly 200 descriptors before the fix. |
48
+ | `_consult()` synced with upstream revision 1916 (2017-07-12) | Upstream bug fix, applied in [#2](https://github.com/akivajp/pycedar/pull/2). |
49
+ | `clear()` layout and the `STATIC_ASSERT` pragmas taken from upstream | Pure formatting, adopted to remove the compiler warnings this copy used to emit. Reduces the delta against upstream rather than adding to it. |
50
+
51
+ The original lines are kept as comments next to the replacements, so the delta
52
+ against upstream stays readable.
53
+
54
+ ### The one `_err()` call site that is left
55
+
56
+ `dump()` still calls `_err()`, and therefore still calls `std::exit(1)`. That is
57
+ acceptable because `pycedar.pxd` does not declare `dump()`, so it is
58
+ unreachable from Python. Check with:
59
+
60
+ ```shell
61
+ $ grep -n '_err (__FILE__' pycedar/core/cedar/src/cedarpp.h
62
+ ```
63
+
64
+ Every line that comes back should either be commented out or sit inside
65
+ `dump()`. Anything else is a path that can kill the interpreter.
66
+ (上記以外が出てきたら、インタプリタを落としうる経路が増えたということ)
67
+
68
+ ### What an allocation failure looks like from Python
69
+
70
+ Every allocation failure raises `MemoryError`, because Cython's `except +` maps
71
+ `std::bad_alloc` to it. Use `std::bad_alloc` — not `std::runtime_error` — for any
72
+ further allocation site, so that all of them stay on one exception type.
73
+ (確保失敗はすべて `MemoryError`。新たに確保箇所を足すときも `std::bad_alloc` を使うこと)
74
+
75
+ `std::runtime_error` is reserved for the one non-allocation case, the
76
+ zero-length key in `update()`, which reaches Python as `RuntimeError`.
77
+
78
+ `load()` is destructive: recovering from a failed load leaves an **empty** trie,
79
+ not the previous contents. cedar frees the old arrays before allocating the new
80
+ ones, so the old contents are already gone by the time the failure is detected.
81
+ (読み込み失敗後のトライは空になる。cedar は新規確保より前に旧配列を解放するため)
82
+
83
+ ## Upstream status, checked 2026-09-16
84
+
85
+ The download page offers `cedar-latest.tar.gz`, which unpacks to
86
+ `cedar-2022-03-18/`. That date is misleading:
87
+
88
+ ```
89
+ cedar-2022-03-18/src/cedarpp.h
90
+ $Id: cedarpp.h 1916 2017-07-12 07:30:56Z ynaga $
91
+ ```
92
+
93
+ **`cedarpp.h` itself has not changed upstream since 2017-07-12.** The 2022
94
+ tarball is a repackage. The single functional change made in 2017 was the
95
+ `_consult()` fix, and pycedar already carries it.
96
+
97
+ Reproduce the comparison with:
98
+
99
+ ```shell
100
+ $ curl -O http://www.tkl.iis.u-tokyo.ac.jp/~ynaga/cedar/cedar-latest.tar.gz
101
+ $ tar xzf cedar-latest.tar.gz
102
+ $ diff -u pycedar/core/cedar/src/cedarpp.h cedar-*/src/cedarpp.h
103
+ ```
104
+
105
+ ## Decision: the 2022 tarball was not re-vendored
106
+
107
+ Once the `_consult()` fix landed, the remaining delta against upstream is only
108
+ the deliberate local patches listed above, plus comment wording. Everything
109
+ else that upstream had and this copy did not — the `clear()` layout and the
110
+ `STATIC_ASSERT` pragmas — has since been adopted, which silenced the six
111
+ compiler warnings this copy used to emit:
112
+
113
+ ```
114
+ 5 x -Wmisleading-indentation (from the old clear() formatting)
115
+ 1 x -Wunused-local-typedefs (from STATIC_ASSERT)
116
+ ```
117
+
118
+ There is **no functional difference left to gain**. Re-vendoring wholesale would
119
+ mean re-applying the patches by hand, and silently losing them would convert
120
+ recoverable errors back into `std::exit(1)` and reintroduce the descriptor leak
121
+ — the worst possible regressions for a library. That risk buys nothing.
122
+
123
+ ## If you do re-vendor
124
+
125
+ 1. Diff the new `src/cedarpp.h` against this copy first, and confirm that the
126
+ upstream `$Id:` revision actually changed. If it did not, stop.
127
+ 2. Re-apply every row of the modification table above.
128
+ 3. Verify that no reachable `_err()` call site came back:
129
+ `grep -n '_err (__FILE__' pycedar/core/cedar/src/cedarpp.h`
130
+ 4. Update the note at the top of `cedarpp.h` and this file.
131
+ 5. Run `pytest`. The suite includes randomized insert/delete consistency checks
132
+ that exercise the double-array rebalancing paths where upstream bugs live.
@@ -2,13 +2,19 @@
2
2
  // $Id: cedarpp.h 1830 2014-06-16 06:17:42Z ynaga $
3
3
  // Copyright (c) 2009-2014 Naoki Yoshinaga <ynaga@tkl.iis.u-tokyo.ac.jp>
4
4
  //
5
- // NOTE (pycedar): this is a vendored copy of the upstream header with two
6
- // local modifications:
7
- // 1. _err() calls are replaced with `throw std::runtime_error` so that
8
- // failures can be surfaced as Python exceptions (hence <stdexcept>).
9
- // 2. _consult() is synced with cedar-2022-03-18
10
- // ($Id: cedarpp.h 1916 2017-07-12 07:30:56Z ynaga $).
11
- // Apart from these, this file is functionally identical to cedar-2022-03-18.
5
+ // NOTE (pycedar): this is a vendored copy carrying local modifications. Four of
6
+ // the five _err() call sites throw instead of calling std::exit(1) -- allocation
7
+ // failures throw std::bad_alloc, which reaches Python as MemoryError -- and the
8
+ // fifth is in dump(), which pycedar never reaches. open() also closes the file
9
+ // on its early returns, which upstream does not. _consult(), the clear() layout
10
+ // and the STATIC_ASSERT pragmas are synced with upstream revision 1916
11
+ // (2017-07-12). Apart from that, this file is functionally identical to
12
+ // cedar-2022-03-18 -- whose cedarpp.h is itself unchanged since 2017, so there
13
+ // is nothing newer to pick up.
14
+ //
15
+ // Before editing or re-vendoring this file, read ../../README.md in this
16
+ // repository (pycedar/core/cedar/README.md). It records what was changed, why,
17
+ // and what must be re-applied if you ever sync with a newer upstream release.
12
18
  #ifndef CEDAR_H
13
19
  #define CEDAR_H
14
20
 
@@ -17,6 +23,7 @@
17
23
  #include <cstring>
18
24
  #include <climits>
19
25
  #include <cassert>
26
+ #include <new> // std::bad_alloc
20
27
  #include <stdexcept> // std::runtime_error
21
28
 
22
29
  #ifdef HAVE_CONFIG_H
@@ -79,9 +86,11 @@ namespace cedar {
79
86
  block () : prev (0), next (0), num (256), reject (257), trial (0), ehead (0) {}
80
87
  };
81
88
  da () : tracking_node (), _array (0), _tail (0), _tail0 (0), _ninfo (0), _block (0), _bheadF (0), _bheadC (0), _bheadO (0), _capacity (0), _size (0), _quota (0), _quota0 (0), _no_delete (false), _reject () {
89
+ #pragma GCC diagnostic ignored "-Wunused-local-typedefs"
82
90
  STATIC_ASSERT(sizeof (value_type) <= sizeof (int),
83
91
  value_type_is_not_supported___maintain_a_value_array_by_yourself_and_store_its_index_to_trie
84
92
  );
93
+ #pragma GCC diagnostic warning "-Wunused-local-typedefs"
85
94
  _initialize ();
86
95
  }
87
96
  ~da () { clear (false); }
@@ -331,7 +340,10 @@ namespace cedar {
331
340
  = static_cast <size_t> (*_length)
332
341
  - static_cast <size_t> (*_length0) * (1 + sizeof (value_type));
333
342
  t.tail = static_cast <char*> (std::malloc (length_));
334
- if (! t.tail) _err (__FILE__, __LINE__, "memory allocation failed\n");
343
+ if (! t.tail)
344
+ // pycedar: nothing has been mutated yet, so the trie survives intact.
345
+ //_err (__FILE__, __LINE__, "memory allocation failed\n");
346
+ throw std::bad_alloc ();
335
347
  *t.length = static_cast <int> (sizeof (int));
336
348
  for (int to = 0; to < _size; ++to) {
337
349
  node& n = _array[to];
@@ -383,16 +395,16 @@ namespace cedar {
383
395
  if (! fp) return -1;
384
396
  // get size
385
397
  if (! size_) {
386
- if (std::fseek (fp, 0, SEEK_END) != 0) return -1;
398
+ if (std::fseek (fp, 0, SEEK_END) != 0) { std::fclose (fp); return -1; }
387
399
  size_ = static_cast <size_t> (std::ftell (fp));
388
- if (std::fseek (fp, 0, SEEK_SET) != 0) return -1;
400
+ if (std::fseek (fp, 0, SEEK_SET) != 0) { std::fclose (fp); return -1; }
389
401
  }
390
- if (size_ <= offset) return -1;
391
- if (std::fseek (fp, static_cast <long> (offset), SEEK_SET) != 0) return -1;
402
+ if (size_ <= offset) { std::fclose (fp); return -1; }
403
+ if (std::fseek (fp, static_cast <long> (offset), SEEK_SET) != 0) { std::fclose (fp); return -1; }
392
404
  int len = 0;
393
- if (std::fread (&len, sizeof (int), 1, fp) != 1) return -1;
405
+ if (std::fread (&len, sizeof (int), 1, fp) != 1) { std::fclose (fp); return -1; }
394
406
  const size_t length_ = static_cast <size_t> (len);
395
- if (size_ <= offset + length_) return -1;
407
+ if (size_ <= offset + length_) { std::fclose (fp); return -1; }
396
408
  // set array
397
409
  clear (false);
398
410
  size_ = (size_ - offset - length_) / sizeof (node);
@@ -406,11 +418,18 @@ namespace cedar {
406
418
  #else
407
419
  if (! _array || ! _tail || ! _tail0)
408
420
  #endif
409
- _err (__FILE__, __LINE__, "memory allocation failed\n");
410
- if (std::fseek (fp, static_cast <long> (offset), SEEK_SET) != 0) return -1;
421
+ //_err (__FILE__, __LINE__, "memory allocation failed\n");
422
+ { // pycedar: clear (false) above already dropped the previous contents,
423
+ // so reset to a valid empty trie before reporting the failure;
424
+ // otherwise the caller would be handed a half-built object.
425
+ std::fclose (fp);
426
+ clear (true);
427
+ throw std::bad_alloc ();
428
+ }
429
+ if (std::fseek (fp, static_cast <long> (offset), SEEK_SET) != 0) { std::fclose (fp); return -1; }
411
430
  if (length_ != std::fread (_tail, sizeof (char), length_, fp) ||
412
431
  size_ != std::fread (_array, sizeof (node), size_, fp))
413
- return -1;
432
+ { std::fclose (fp); return -1; }
414
433
  std::fclose (fp);
415
434
  _size = static_cast <int> (size_);
416
435
  *_length0 = 0;
@@ -425,7 +444,7 @@ namespace cedar {
425
444
  std::fread (&_bheadO, sizeof (int), 1, fp);
426
445
  if (size_ != std::fread (_ninfo, sizeof (ninfo), size_, fp) ||
427
446
  size_ >> 8 != std::fread (_block, sizeof (block), size_ >> 8, fp))
428
- return -1;
447
+ { std::fclose (fp); return -1; }
429
448
  std::fclose (fp);
430
449
  _capacity = _size;
431
450
  _quota = *_length;
@@ -454,11 +473,12 @@ namespace cedar {
454
473
  const void* array () const { return _array; }
455
474
  void clear (const bool reuse = true) {
456
475
  if (_no_delete) _array = 0, _tail = 0;
457
- if (_array) std::free (_array); _array = 0;
458
- if (_tail) std::free (_tail); _tail = 0;
459
- if (_tail0) std::free (_tail0); _tail0 = 0;
460
- if (_ninfo) std::free (_ninfo); _ninfo = 0;
461
- if (_block) std::free (_block); _block = 0;
476
+ if (_array) std::free (_array);
477
+ if (_tail) std::free (_tail);
478
+ if (_tail0) std::free (_tail0);
479
+ if (_ninfo) std::free (_ninfo);
480
+ if (_block) std::free (_block);
481
+ _array = 0; _tail = 0; _tail0 = 0; _ninfo = 0; _block = 0;
462
482
  _bheadF = _bheadC = _bheadO = _capacity = _size = _quota = _quota0 = 0;
463
483
  if (reuse) _initialize ();
464
484
  _no_delete = false;
@@ -529,7 +549,7 @@ namespace cedar {
529
549
  void* tmp = std::realloc (p, sizeof (T) * static_cast <size_t> (size_n));
530
550
  if (! tmp)
531
551
  //std::free (p), _err (__FILE__, __LINE__, "memory reallocation failed\n");
532
- throw std::runtime_error("memory reallocation failed");
552
+ throw std::bad_alloc ();
533
553
  p = static_cast <T*> (tmp);
534
554
  static const T T0 = T ();
535
555
  for (T* q (p + size_p), * const r (p + size_n); q != r; ++q) *q = T0;