From e049b1241c5cdb02370bbcd9be99d71aeddaaa68 Mon Sep 17 00:00:00 2001 From: Zenco <64717583+notzenco@users.noreply.github.com> Date: Thu, 29 Oct 2026 16:52:50 +0800 Subject: [PATCH] fix(dsa): guard allocation and cache eviction boundaries Reject C allocation sizes that can overflow before construction. Remove an evicted Rust LRU node by identity before freeing it, including when Clone produces a key unequal to the stored key. Track a compatible Rust dependency graph so the declared 1.70 check resolves consistently in CI. --- COMPLEXITY.md | 4 + REMEDIATION.md | 11 +- by-language/c/data-structures/b_tree.c | 8 +- by-language/c/data-structures/b_tree.h | 2 +- by-language/c/data-structures/segment_tree.c | 23 +- by-language/c/data-structures/segment_tree.h | 4 +- by-language/c/tests/test_btree.c | 3 + by-language/c/tests/test_range_trees.c | 12 +- by-language/rust/.gitignore | 1 - by-language/rust/Cargo.lock | 651 ++++++++++++++++++ by-language/rust/README.md | 2 + .../src/caches/lru_cache.rs | 47 +- 12 files changed, 743 insertions(+), 25 deletions(-) create mode 100644 by-language/rust/Cargo.lock diff --git a/COMPLEXITY.md b/COMPLEXITY.md index 0a5639d..3ef739c 100644 --- a/COMPLEXITY.md +++ b/COMPLEXITY.md @@ -493,6 +493,10 @@ False Positive Rate ≈ (1 - e^(-kn/m))^k Implementation: Hash Map + Doubly Linked List ``` +The Rust `LRUCache` uses a `BTreeMap` and linked nodes. Its get, peek, and +remove operations are O(log n); put is O(n) in the worst case because eviction +scans the map to remove the exact node before freeing it. + **When to Use:** - Caching with memory limit diff --git a/REMEDIATION.md b/REMEDIATION.md index bf55b6e..d23d79f 100644 --- a/REMEDIATION.md +++ b/REMEDIATION.md @@ -11,6 +11,9 @@ possible input or deployment has been audited. | Area | Current contract | |------|------------------| | C shortest paths | `shortest_path_reconstruct` takes `path_capacity` in elements and returns `false` for an insufficient buffer, invalid destination, unreachable path, or negative-cycle result. | +| C segment tree | Both constructors return `NULL` when `4 * n` integer slots cannot fit in `size_t`; callers must check the result before querying or updating. | +| C B-tree | `btree_create` returns `NULL` when the minimum degree is below 2 or too large for signed arithmetic or node allocations. | +| Rust LRU cache | Eviction removes the map entry by node identity before freeing the node, including for keys whose clones compare differently. The map is a `BTreeMap`; eviction scans it, so `put` is O(n) in the worst case. | | Rust shortest paths | `reconstruct_path(parent, source, target)` requires the actual source and returns `None` for invalid or cyclic parent chains. | | Fenwick updates | Rust uses one-based indices; Go, Python, and TypeScript use zero-based indices. Invalid update indices are rejected before entering the update loop. | | Python segment tree | Point updates outside `[0, len)` raise `IndexError`. | @@ -28,15 +31,17 @@ possible input or deployment has been audited. ## Regression areas -- C allocation arithmetic, trie enumeration, search byte counters, graph path - buffers, counting/radix sort, hash tombstones, and B-tree root changes. +- C allocation arithmetic, including segment-tree sizes and B-tree degree, + trie enumeration, search byte counters, graph path buffers, counting/radix + sort, hash tombstones, and B-tree root changes. - Graph algorithms: absent destinations, negative vertex labels, self-loops, parallel edges, object-valued vertices, and path reconstruction from a nonzero source. - String algorithms: delimiters occurring in input, Unicode boundaries, and palindrome sentinels. - Data structures: lazy segment-tree logical length, skip-list live length, - Fenwick index validation, LFU frequency buckets, and panic-safe LRU clear. + Fenwick index validation, LFU frequency buckets, panic-safe LRU clear, and + node-identity eviction for non-equivalent Rust key clones. - Sorting: negative values, all-negative input, and integer-type extremes for every sort, including the counting-sort comparison fallback. - Randomized model tests in every language compare sorts, searches, string diff --git a/by-language/c/data-structures/b_tree.c b/by-language/c/data-structures/b_tree.c index 73bea8c..88c3f13 100644 --- a/by-language/c/data-structures/b_tree.c +++ b/by-language/c/data-structures/b_tree.c @@ -6,6 +6,8 @@ #include #include #include +#include +#include /* ============== Node Creation/Destruction ============== */ @@ -530,7 +532,11 @@ static void print_node(BTreeNode *node, int depth) { /* ============== Public Functions ============== */ BTree *btree_create(int min_degree) { - if (min_degree < 2) return NULL; + if (min_degree < 2 || min_degree > INT_MAX / 2) return NULL; + + size_t max_children = (size_t)min_degree * 2; + if (max_children > SIZE_MAX / sizeof(BTreeNode *) || + max_children - 1 > SIZE_MAX / sizeof(int)) return NULL; BTree *tree = malloc(sizeof(BTree)); if (!tree) return NULL; diff --git a/by-language/c/data-structures/b_tree.h b/by-language/c/data-structures/b_tree.h index 4cfd4d3..7290a26 100644 --- a/by-language/c/data-structures/b_tree.h +++ b/by-language/c/data-structures/b_tree.h @@ -71,7 +71,7 @@ typedef struct { /** * Create a new B-Tree with specified minimum degree. - * @param min_degree Minimum degree t (must be >= 2) + * @param min_degree Minimum degree t (must be >= 2 and fit node allocations) * @return New tree or NULL on failure */ BTree *btree_create(int min_degree); diff --git a/by-language/c/data-structures/segment_tree.c b/by-language/c/data-structures/segment_tree.c index 41e0af4..62f7371 100644 --- a/by-language/c/data-structures/segment_tree.c +++ b/by-language/c/data-structures/segment_tree.c @@ -7,6 +7,7 @@ #include #include #include +#include /* ============== Helper Functions ============== */ @@ -62,8 +63,12 @@ static void build(SegmentTree *st, const int *arr, size_t node, /* ============== Creation/Destruction ============== */ +static bool valid_tree_size(size_t n) { + return n > 0 && n <= SIZE_MAX / sizeof(int) / 4; +} + SegmentTree *segtree_create(const int *arr, size_t n, SegmentTreeType type) { - if (arr == NULL || n == 0) return NULL; + if (arr == NULL || !valid_tree_size(n)) return NULL; SegmentTree *st = malloc(sizeof(SegmentTree)); if (st == NULL) return NULL; @@ -73,8 +78,9 @@ SegmentTree *segtree_create(const int *arr, size_t n, SegmentTreeType type) { st->identity = get_identity(type); /* Allocate tree array (4*n is safe upper bound) */ - st->tree = malloc(4 * n * sizeof(int)); - st->lazy = calloc(4 * n, sizeof(int)); + size_t capacity = 4 * n; + st->tree = malloc(capacity * sizeof(int)); + st->lazy = calloc(capacity, sizeof(int)); if (st->tree == NULL || st->lazy == NULL) { free(st->tree); @@ -84,7 +90,7 @@ SegmentTree *segtree_create(const int *arr, size_t n, SegmentTreeType type) { } /* Initialize with identity */ - for (size_t i = 0; i < 4 * n; i++) { + for (size_t i = 0; i < capacity; i++) { st->tree[i] = st->identity; } @@ -93,7 +99,7 @@ SegmentTree *segtree_create(const int *arr, size_t n, SegmentTreeType type) { } SegmentTree *segtree_create_empty(size_t n, SegmentTreeType type) { - if (n == 0) return NULL; + if (!valid_tree_size(n)) return NULL; SegmentTree *st = malloc(sizeof(SegmentTree)); if (st == NULL) return NULL; @@ -102,8 +108,9 @@ SegmentTree *segtree_create_empty(size_t n, SegmentTreeType type) { st->type = type; st->identity = get_identity(type); - st->tree = malloc(4 * n * sizeof(int)); - st->lazy = calloc(4 * n, sizeof(int)); + size_t capacity = 4 * n; + st->tree = malloc(capacity * sizeof(int)); + st->lazy = calloc(capacity, sizeof(int)); if (st->tree == NULL || st->lazy == NULL) { free(st->tree); @@ -112,7 +119,7 @@ SegmentTree *segtree_create_empty(size_t n, SegmentTreeType type) { return NULL; } - for (size_t i = 0; i < 4 * n; i++) { + for (size_t i = 0; i < capacity; i++) { st->tree[i] = st->identity; } diff --git a/by-language/c/data-structures/segment_tree.h b/by-language/c/data-structures/segment_tree.h index eaae624..c8955c9 100644 --- a/by-language/c/data-structures/segment_tree.h +++ b/by-language/c/data-structures/segment_tree.h @@ -72,7 +72,7 @@ typedef struct { * @param arr Input array * @param n Array size * @param type Type of query (SUM, MIN, MAX, GCD) - * @return New segment tree or NULL on failure + * @return New segment tree or NULL if the size is invalid or allocation fails */ SegmentTree *segtree_create(const int *arr, size_t n, SegmentTreeType type); @@ -80,7 +80,7 @@ SegmentTree *segtree_create(const int *arr, size_t n, SegmentTreeType type); * Create an empty segment tree. * @param n Size * @param type Type of query - * @return New segment tree or NULL on failure + * @return New segment tree or NULL if the size is invalid or allocation fails */ SegmentTree *segtree_create_empty(size_t n, SegmentTreeType type); diff --git a/by-language/c/tests/test_btree.c b/by-language/c/tests/test_btree.c index 0a8f079..518c010 100644 --- a/by-language/c/tests/test_btree.c +++ b/by-language/c/tests/test_btree.c @@ -5,6 +5,7 @@ #include "test_framework.h" #include "../data-structures/b_tree.h" #include +#include /* ============== Creation Tests ============== */ @@ -31,6 +32,8 @@ TEST(btree_create_with_degree) { TEST(btree_create_invalid) { ASSERT_TRUE(btree_create(0) == NULL); ASSERT_TRUE(btree_create(1) == NULL); + ASSERT_TRUE(btree_create(INT_MAX / 2 + 1) == NULL); + ASSERT_TRUE(btree_create(INT_MAX) == NULL); } /* ============== Insert Tests ============== */ diff --git a/by-language/c/tests/test_range_trees.c b/by-language/c/tests/test_range_trees.c index 276a7dd..bf81afe 100644 --- a/by-language/c/tests/test_range_trees.c +++ b/by-language/c/tests/test_range_trees.c @@ -6,12 +6,21 @@ #include "../data-structures/segment_tree.h" #include "../data-structures/fenwick_tree.h" #include +#include TEST(fenwick_rejects_overflow_size) { ASSERT_TRUE(fenwick_create_empty(SIZE_MAX) == NULL); } -#include +TEST(segtree_rejects_overflow_size) { + size_t array_length_overflow = SIZE_MAX / 4 + 2; + size_t allocation_bytes_overflow = SIZE_MAX / sizeof(int) / 4 + 1; + int value = 1; + ASSERT_TRUE(segtree_create_empty(array_length_overflow, SEG_SUM) == NULL); + ASSERT_TRUE(segtree_create(&value, array_length_overflow, SEG_SUM) == NULL); + ASSERT_TRUE(segtree_create_empty(allocation_bytes_overflow, SEG_SUM) == NULL); + ASSERT_TRUE(segtree_create(&value, allocation_bytes_overflow, SEG_SUM) == NULL); +} /* ============== Segment Tree Creation Tests ============== */ TEST(segtree_create_sum) { @@ -281,6 +290,7 @@ int main(void) { RUN_TEST(fenwick_rejects_overflow_size); /* Segment Tree Creation */ + RUN_TEST(segtree_rejects_overflow_size); RUN_TEST(segtree_create_sum); RUN_TEST(segtree_create_min); RUN_TEST(segtree_create_max); diff --git a/by-language/rust/.gitignore b/by-language/rust/.gitignore index 52f42d7..1904bc2 100644 --- a/by-language/rust/.gitignore +++ b/by-language/rust/.gitignore @@ -1,6 +1,5 @@ # Generated by Cargo /target/ -Cargo.lock # IDE .idea/ diff --git a/by-language/rust/Cargo.lock b/by-language/rust/Cargo.lock new file mode 100644 index 0000000..3086b05 --- /dev/null +++ b/by-language/rust/Cargo.lock @@ -0,0 +1,651 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "dsa-algorithms" +version = "1.0.0" +dependencies = [ + "dsa-core", + "dsa-data-structures", + "rand", +] + +[[package]] +name = "dsa-benches" +version = "1.0.0" +dependencies = [ + "criterion", + "dsa-algorithms", + "dsa-data-structures", + "rand", +] + +[[package]] +name = "dsa-core" +version = "1.0.0" + +[[package]] +name = "dsa-data-structures" +version = "1.0.0" +dependencies = [ + "dsa-core", + "rand", +] + +[[package]] +name = "either" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zerocopy" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/by-language/rust/README.md b/by-language/rust/README.md index fd4f9ea..154d076 100644 --- a/by-language/rust/README.md +++ b/by-language/rust/README.md @@ -145,6 +145,8 @@ The `tests/models.rs` suites in `dsa-algorithms` and `dsa-data-structures` compare each implementation against standard-library reference models with a fixed seed. CI also checks the library crates on the declared minimum Rust version, 1.70. +The workspace commits `Cargo.lock` so CI uses the same dependency versions; +`web-sys` and `js-sys` are held at 0.3.66 for Cargo 1.70 compatibility. ## Path reconstruction diff --git a/by-language/rust/dsa-data-structures/src/caches/lru_cache.rs b/by-language/rust/dsa-data-structures/src/caches/lru_cache.rs index caabe6b..638dedd 100644 --- a/by-language/rust/dsa-data-structures/src/caches/lru_cache.rs +++ b/by-language/rust/dsa-data-structures/src/caches/lru_cache.rs @@ -26,10 +26,10 @@ //! //! | Operation | Time Complexity | Space Complexity | //! |-----------|-----------------|------------------| -//! | get(key) | O(1) | O(1) | -//! | put(k,v) | O(1) | O(1) | -//! | peek(key) | O(1) | O(1) | -//! | remove(k) | O(1) | O(1) | +//! | get(key) | O(log n) | O(1) | +//! | put(k,v) | O(n) worst case | O(1) | +//! | peek(key) | O(log n) | O(1) | +//! | remove(k) | O(log n) | O(1) | //! | Overall | - | O(capacity) | //! //! ## LeetCode Problems @@ -93,8 +93,9 @@ impl Node { /// An LRU (Least Recently Used) Cache. /// -/// Provides O(1) get and put operations with automatic eviction of the -/// least recently used item when capacity is exceeded. +/// Provides automatic eviction of the least recently used item when capacity +/// is exceeded. Eviction scans the map to keep raw node pointers sound even +/// when a key's clone does not compare equal to the original key. /// /// # Type Parameters /// @@ -556,9 +557,10 @@ where /// Evicts the least recently used item. fn evict_lru(&mut self) -> Option<(K, V)> { if let Some(tail) = self.tail { + // Remove by node identity. Looking up a clone of the node key can + // miss the map entry and leave a dangling pointer after the free. + self.map.retain(|_, node_ptr| *node_ptr != tail); unsafe { - let key = (*tail.as_ptr()).key.clone(); - self.map.remove(&key); self.unlink(tail); let node = Box::from_raw(tail.as_ptr()); Some((node.key, node.value)) @@ -699,6 +701,35 @@ mod tests { mod eviction { use super::*; + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] + struct NonEquivalentCloneKey(u32); + + impl Clone for NonEquivalentCloneKey { + fn clone(&self) -> Self { + Self(self.0 + 1) + } + } + + #[test] + fn test_eviction_removes_entry_by_node_identity() { + let mut cache = LRUCache::new(1); + cache.put(NonEquivalentCloneKey(1), 11); + cache.put(NonEquivalentCloneKey(3), 33); + + assert!(!cache.contains(&NonEquivalentCloneKey(1))); + assert_eq!(cache.peek(&NonEquivalentCloneKey(1)), None); + assert_eq!(cache.peek(&NonEquivalentCloneKey(3)), Some(&33)); + assert_eq!(cache.len(), 1); + + // The tail's cloned key can also match another map entry. + let mut cache = LRUCache::new(1); + cache.put(NonEquivalentCloneKey(1), 11); + cache.put(NonEquivalentCloneKey(2), 22); + assert_eq!(cache.peek(&NonEquivalentCloneKey(1)), None); + assert_eq!(cache.peek(&NonEquivalentCloneKey(2)), Some(&22)); + assert_eq!(cache.len(), 1); + } + #[test] fn test_eviction_on_capacity() { let mut cache = LRUCache::new(2);