| ... | @@ -137,6 +137,23 @@ pub fn ArrayHashMap( | ... | @@ -137,6 +137,23 @@ pub fn ArrayHashMap( |
| 137 | self.* = undefined; | 137 | self.* = undefined; |
| 138 | } | 138 | } |
| 139 | | 139 | |
| | 140 | /// Puts the hash map into a state where any method call that would |
| | 141 | /// cause an existing key or value pointer to become invalidated will |
| | 142 | /// instead trigger an assertion. |
| | 143 | /// |
| | 144 | /// An additional call to `lockPointers` in such state also triggers an |
| | 145 | /// assertion. |
| | 146 | /// |
| | 147 | /// `unlockPointers` returns the hash map to the previous state. |
| | 148 | pub fn lockPointers(self: *Self) void { |
| | 149 | self.unmanaged.lockPointers(); |
| | 150 | } |
| | 151 | |
| | 152 | /// Undoes a call to `lockPointers`. |
| | 153 | pub fn unlockPointers(self: *Self) void { |
| | 154 | self.unmanaged.unlockPointers(); |
| | 155 | } |
| | 156 | |
| 140 | /// Clears the map but retains the backing allocation for future use. | 157 | /// Clears the map but retains the backing allocation for future use. |
| 141 | pub fn clearRetainingCapacity(self: *Self) void { | 158 | pub fn clearRetainingCapacity(self: *Self) void { |
| 142 | return self.unmanaged.clearRetainingCapacity(); | 159 | return self.unmanaged.clearRetainingCapacity(); |
| ... | @@ -403,6 +420,7 @@ pub fn ArrayHashMap( | ... | @@ -403,6 +420,7 @@ pub fn ArrayHashMap( |
| 403 | /// Set the map to an empty state, making deinitialization a no-op, and | 420 | /// Set the map to an empty state, making deinitialization a no-op, and |
| 404 | /// returning a copy of the original. | 421 | /// returning a copy of the original. |
| 405 | pub fn move(self: *Self) Self { | 422 | pub fn move(self: *Self) Self { |
| | 423 | self.pointer_stability.assertUnlocked(); |
| 406 | const result = self.*; | 424 | const result = self.*; |
| 407 | self.unmanaged = .{}; | 425 | self.unmanaged = .{}; |
| 408 | return result; | 426 | return result; |
| ... | @@ -495,6 +513,9 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -495,6 +513,9 @@ pub fn ArrayHashMapUnmanaged( |
| 495 | /// by how many total indexes there are. | 513 | /// by how many total indexes there are. |
| 496 | index_header: ?*IndexHeader = null, | 514 | index_header: ?*IndexHeader = null, |
| 497 | | 515 | |
| | 516 | /// Used to detect memory safety violations. |
| | 517 | pointer_stability: std.debug.SafetyLock = .{}, |
| | 518 | |
| 498 | comptime { | 519 | comptime { |
| 499 | std.hash_map.verifyContext(Context, K, K, u32, true); | 520 | std.hash_map.verifyContext(Context, K, K, u32, true); |
| 500 | } | 521 | } |
| ... | @@ -589,6 +610,7 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -589,6 +610,7 @@ pub fn ArrayHashMapUnmanaged( |
| 589 | /// Note that this does not free keys or values. You must take care of that | 610 | /// Note that this does not free keys or values. You must take care of that |
| 590 | /// before calling this function, if it is needed. | 611 | /// before calling this function, if it is needed. |
| 591 | pub fn deinit(self: *Self, allocator: Allocator) void { | 612 | pub fn deinit(self: *Self, allocator: Allocator) void { |
| | 613 | self.pointer_stability.assertUnlocked(); |
| 592 | self.entries.deinit(allocator); | 614 | self.entries.deinit(allocator); |
| 593 | if (self.index_header) |header| { | 615 | if (self.index_header) |header| { |
| 594 | header.free(allocator); | 616 | header.free(allocator); |
| ... | @@ -596,8 +618,28 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -596,8 +618,28 @@ pub fn ArrayHashMapUnmanaged( |
| 596 | self.* = undefined; | 618 | self.* = undefined; |
| 597 | } | 619 | } |
| 598 | | 620 | |
| | 621 | /// Puts the hash map into a state where any method call that would |
| | 622 | /// cause an existing key or value pointer to become invalidated will |
| | 623 | /// instead trigger an assertion. |
| | 624 | /// |
| | 625 | /// An additional call to `lockPointers` in such state also triggers an |
| | 626 | /// assertion. |
| | 627 | /// |
| | 628 | /// `unlockPointers` returns the hash map to the previous state. |
| | 629 | pub fn lockPointers(self: *Self) void { |
| | 630 | self.pointer_stability.lock(); |
| | 631 | } |
| | 632 | |
| | 633 | /// Undoes a call to `lockPointers`. |
| | 634 | pub fn unlockPointers(self: *Self) void { |
| | 635 | self.pointer_stability.unlock(); |
| | 636 | } |
| | 637 | |
| 599 | /// Clears the map but retains the backing allocation for future use. | 638 | /// Clears the map but retains the backing allocation for future use. |
| 600 | pub fn clearRetainingCapacity(self: *Self) void { | 639 | pub fn clearRetainingCapacity(self: *Self) void { |
| | 640 | self.pointer_stability.lock(); |
| | 641 | defer self.pointer_stability.unlock(); |
| | 642 | |
| 601 | self.entries.len = 0; | 643 | self.entries.len = 0; |
| 602 | if (self.index_header) |header| { | 644 | if (self.index_header) |header| { |
| 603 | switch (header.capacityIndexType()) { | 645 | switch (header.capacityIndexType()) { |
| ... | @@ -610,6 +652,9 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -610,6 +652,9 @@ pub fn ArrayHashMapUnmanaged( |
| 610 | | 652 | |
| 611 | /// Clears the map and releases the backing allocation | 653 | /// Clears the map and releases the backing allocation |
| 612 | pub fn clearAndFree(self: *Self, allocator: Allocator) void { | 654 | pub fn clearAndFree(self: *Self, allocator: Allocator) void { |
| | 655 | self.pointer_stability.lock(); |
| | 656 | defer self.pointer_stability.unlock(); |
| | 657 | |
| 613 | self.entries.shrinkAndFree(allocator, 0); | 658 | self.entries.shrinkAndFree(allocator, 0); |
| 614 | if (self.index_header) |header| { | 659 | if (self.index_header) |header| { |
| 615 | header.free(allocator); | 660 | header.free(allocator); |
| ... | @@ -795,6 +840,9 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -795,6 +840,9 @@ pub fn ArrayHashMapUnmanaged( |
| 795 | return self.ensureTotalCapacityContext(allocator, new_capacity, undefined); | 840 | return self.ensureTotalCapacityContext(allocator, new_capacity, undefined); |
| 796 | } | 841 | } |
| 797 | pub fn ensureTotalCapacityContext(self: *Self, allocator: Allocator, new_capacity: usize, ctx: Context) !void { | 842 | pub fn ensureTotalCapacityContext(self: *Self, allocator: Allocator, new_capacity: usize, ctx: Context) !void { |
| | 843 | self.pointer_stability.lock(); |
| | 844 | defer self.pointer_stability.unlock(); |
| | 845 | |
| 798 | if (new_capacity <= linear_scan_max) { | 846 | if (new_capacity <= linear_scan_max) { |
| 799 | try self.entries.ensureTotalCapacity(allocator, new_capacity); | 847 | try self.entries.ensureTotalCapacity(allocator, new_capacity); |
| 800 | return; | 848 | return; |
| ... | @@ -1079,6 +1127,9 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1079,6 +1127,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1079 | return self.fetchSwapRemoveContextAdapted(key, ctx, undefined); | 1127 | return self.fetchSwapRemoveContextAdapted(key, ctx, undefined); |
| 1080 | } | 1128 | } |
| 1081 | pub fn fetchSwapRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) ?KV { | 1129 | pub fn fetchSwapRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) ?KV { |
| | 1130 | self.pointer_stability.lock(); |
| | 1131 | defer self.pointer_stability.unlock(); |
| | 1132 | |
| 1082 | return self.fetchRemoveByKey(key, key_ctx, if (store_hash) {} else ctx, .swap); | 1133 | return self.fetchRemoveByKey(key, key_ctx, if (store_hash) {} else ctx, .swap); |
| 1083 | } | 1134 | } |
| 1084 | | 1135 | |
| ... | @@ -1100,6 +1151,9 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1100,6 +1151,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1100 | return self.fetchOrderedRemoveContextAdapted(key, ctx, undefined); | 1151 | return self.fetchOrderedRemoveContextAdapted(key, ctx, undefined); |
| 1101 | } | 1152 | } |
| 1102 | pub fn fetchOrderedRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) ?KV { | 1153 | pub fn fetchOrderedRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) ?KV { |
| | 1154 | self.pointer_stability.lock(); |
| | 1155 | defer self.pointer_stability.unlock(); |
| | 1156 | |
| 1103 | return self.fetchRemoveByKey(key, key_ctx, if (store_hash) {} else ctx, .ordered); | 1157 | return self.fetchRemoveByKey(key, key_ctx, if (store_hash) {} else ctx, .ordered); |
| 1104 | } | 1158 | } |
| 1105 | | 1159 | |
| ... | @@ -1121,6 +1175,9 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1121,6 +1175,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1121 | return self.swapRemoveContextAdapted(key, ctx, undefined); | 1175 | return self.swapRemoveContextAdapted(key, ctx, undefined); |
| 1122 | } | 1176 | } |
| 1123 | pub fn swapRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) bool { | 1177 | pub fn swapRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) bool { |
| | 1178 | self.pointer_stability.lock(); |
| | 1179 | defer self.pointer_stability.unlock(); |
| | 1180 | |
| 1124 | return self.removeByKey(key, key_ctx, if (store_hash) {} else ctx, .swap); | 1181 | return self.removeByKey(key, key_ctx, if (store_hash) {} else ctx, .swap); |
| 1125 | } | 1182 | } |
| 1126 | | 1183 | |
| ... | @@ -1142,6 +1199,9 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1142,6 +1199,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1142 | return self.orderedRemoveContextAdapted(key, ctx, undefined); | 1199 | return self.orderedRemoveContextAdapted(key, ctx, undefined); |
| 1143 | } | 1200 | } |
| 1144 | pub fn orderedRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) bool { | 1201 | pub fn orderedRemoveContextAdapted(self: *Self, key: anytype, key_ctx: anytype, ctx: Context) bool { |
| | 1202 | self.pointer_stability.lock(); |
| | 1203 | defer self.pointer_stability.unlock(); |
| | 1204 | |
| 1145 | return self.removeByKey(key, key_ctx, if (store_hash) {} else ctx, .ordered); | 1205 | return self.removeByKey(key, key_ctx, if (store_hash) {} else ctx, .ordered); |
| 1146 | } | 1206 | } |
| 1147 | | 1207 | |
| ... | @@ -1154,6 +1214,9 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1154,6 +1214,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1154 | return self.swapRemoveAtContext(index, undefined); | 1214 | return self.swapRemoveAtContext(index, undefined); |
| 1155 | } | 1215 | } |
| 1156 | pub fn swapRemoveAtContext(self: *Self, index: usize, ctx: Context) void { | 1216 | pub fn swapRemoveAtContext(self: *Self, index: usize, ctx: Context) void { |
| | 1217 | self.pointer_stability.lock(); |
| | 1218 | defer self.pointer_stability.unlock(); |
| | 1219 | |
| 1157 | self.removeByIndex(index, if (store_hash) {} else ctx, .swap); | 1220 | self.removeByIndex(index, if (store_hash) {} else ctx, .swap); |
| 1158 | } | 1221 | } |
| 1159 | | 1222 | |
| ... | @@ -1167,6 +1230,9 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1167,6 +1230,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1167 | return self.orderedRemoveAtContext(index, undefined); | 1230 | return self.orderedRemoveAtContext(index, undefined); |
| 1168 | } | 1231 | } |
| 1169 | pub fn orderedRemoveAtContext(self: *Self, index: usize, ctx: Context) void { | 1232 | pub fn orderedRemoveAtContext(self: *Self, index: usize, ctx: Context) void { |
| | 1233 | self.pointer_stability.lock(); |
| | 1234 | defer self.pointer_stability.unlock(); |
| | 1235 | |
| 1170 | self.removeByIndex(index, if (store_hash) {} else ctx, .ordered); | 1236 | self.removeByIndex(index, if (store_hash) {} else ctx, .ordered); |
| 1171 | } | 1237 | } |
| 1172 | | 1238 | |
| ... | @@ -1196,6 +1262,7 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1196,6 +1262,7 @@ pub fn ArrayHashMapUnmanaged( |
| 1196 | /// Set the map to an empty state, making deinitialization a no-op, and | 1262 | /// Set the map to an empty state, making deinitialization a no-op, and |
| 1197 | /// returning a copy of the original. | 1263 | /// returning a copy of the original. |
| 1198 | pub fn move(self: *Self) Self { | 1264 | pub fn move(self: *Self) Self { |
| | 1265 | self.pointer_stability.assertUnlocked(); |
| 1199 | const result = self.*; | 1266 | const result = self.*; |
| 1200 | self.* = .{}; | 1267 | self.* = .{}; |
| 1201 | return result; | 1268 | return result; |
| ... | @@ -1271,6 +1338,9 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1271,6 +1338,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1271 | sort_ctx: anytype, | 1338 | sort_ctx: anytype, |
| 1272 | ctx: Context, | 1339 | ctx: Context, |
| 1273 | ) void { | 1340 | ) void { |
| | 1341 | self.pointer_stability.lock(); |
| | 1342 | defer self.pointer_stability.unlock(); |
| | 1343 | |
| 1274 | switch (mode) { | 1344 | switch (mode) { |
| 1275 | .stable => self.entries.sort(sort_ctx), | 1345 | .stable => self.entries.sort(sort_ctx), |
| 1276 | .unstable => self.entries.sortUnstable(sort_ctx), | 1346 | .unstable => self.entries.sortUnstable(sort_ctx), |
| ... | @@ -1288,6 +1358,9 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1288,6 +1358,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1288 | return self.shrinkRetainingCapacityContext(new_len, undefined); | 1358 | return self.shrinkRetainingCapacityContext(new_len, undefined); |
| 1289 | } | 1359 | } |
| 1290 | pub fn shrinkRetainingCapacityContext(self: *Self, new_len: usize, ctx: Context) void { | 1360 | pub fn shrinkRetainingCapacityContext(self: *Self, new_len: usize, ctx: Context) void { |
| | 1361 | self.pointer_stability.lock(); |
| | 1362 | defer self.pointer_stability.unlock(); |
| | 1363 | |
| 1291 | // Remove index entries from the new length onwards. | 1364 | // Remove index entries from the new length onwards. |
| 1292 | // Explicitly choose to ONLY remove index entries and not the underlying array list | 1365 | // Explicitly choose to ONLY remove index entries and not the underlying array list |
| 1293 | // entries as we're going to remove them in the subsequent shrink call. | 1366 | // entries as we're going to remove them in the subsequent shrink call. |
| ... | @@ -1307,6 +1380,9 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1307,6 +1380,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1307 | return self.shrinkAndFreeContext(allocator, new_len, undefined); | 1380 | return self.shrinkAndFreeContext(allocator, new_len, undefined); |
| 1308 | } | 1381 | } |
| 1309 | pub fn shrinkAndFreeContext(self: *Self, allocator: Allocator, new_len: usize, ctx: Context) void { | 1382 | pub fn shrinkAndFreeContext(self: *Self, allocator: Allocator, new_len: usize, ctx: Context) void { |
| | 1383 | self.pointer_stability.lock(); |
| | 1384 | defer self.pointer_stability.unlock(); |
| | 1385 | |
| 1310 | // Remove index entries from the new length onwards. | 1386 | // Remove index entries from the new length onwards. |
| 1311 | // Explicitly choose to ONLY remove index entries and not the underlying array list | 1387 | // Explicitly choose to ONLY remove index entries and not the underlying array list |
| 1312 | // entries as we're going to remove them in the subsequent shrink call. | 1388 | // entries as we're going to remove them in the subsequent shrink call. |
| ... | @@ -1325,6 +1401,9 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1325,6 +1401,9 @@ pub fn ArrayHashMapUnmanaged( |
| 1325 | return self.popContext(undefined); | 1401 | return self.popContext(undefined); |
| 1326 | } | 1402 | } |
| 1327 | pub fn popContext(self: *Self, ctx: Context) KV { | 1403 | pub fn popContext(self: *Self, ctx: Context) KV { |
| | 1404 | self.pointer_stability.lock(); |
| | 1405 | defer self.pointer_stability.unlock(); |
| | 1406 | |
| 1328 | const item = self.entries.get(self.entries.len - 1); | 1407 | const item = self.entries.get(self.entries.len - 1); |
| 1329 | if (self.index_header) |header| | 1408 | if (self.index_header) |header| |
| 1330 | self.removeFromIndexByIndex(self.entries.len - 1, if (store_hash) {} else ctx, header); | 1409 | self.removeFromIndexByIndex(self.entries.len - 1, if (store_hash) {} else ctx, header); |
| ... | @@ -1346,9 +1425,13 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1346,9 +1425,13 @@ pub fn ArrayHashMapUnmanaged( |
| 1346 | return if (self.entries.len == 0) null else self.popContext(ctx); | 1425 | return if (self.entries.len == 0) null else self.popContext(ctx); |
| 1347 | } | 1426 | } |
| 1348 | | 1427 | |
| 1349 | // ------------------ No pub fns below this point ------------------ | 1428 | fn fetchRemoveByKey( |
| 1350 | | 1429 | self: *Self, |
| 1351 | fn fetchRemoveByKey(self: *Self, key: anytype, key_ctx: anytype, ctx: ByIndexContext, comptime removal_type: RemovalType) ?KV { | 1430 | key: anytype, |
| | 1431 | key_ctx: anytype, |
| | 1432 | ctx: ByIndexContext, |
| | 1433 | comptime removal_type: RemovalType, |
| | 1434 | ) ?KV { |
| 1352 | const header = self.index_header orelse { | 1435 | const header = self.index_header orelse { |
| 1353 | // Linear scan. | 1436 | // Linear scan. |
| 1354 | const key_hash = if (store_hash) key_ctx.hash(key) else {}; | 1437 | const key_hash = if (store_hash) key_ctx.hash(key) else {}; |
| ... | @@ -1377,7 +1460,15 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1377,7 +1460,15 @@ pub fn ArrayHashMapUnmanaged( |
| 1377 | .u32 => self.fetchRemoveByKeyGeneric(key, key_ctx, ctx, header, u32, removal_type), | 1460 | .u32 => self.fetchRemoveByKeyGeneric(key, key_ctx, ctx, header, u32, removal_type), |
| 1378 | }; | 1461 | }; |
| 1379 | } | 1462 | } |
| 1380 | fn fetchRemoveByKeyGeneric(self: *Self, key: anytype, key_ctx: anytype, ctx: ByIndexContext, header: *IndexHeader, comptime I: type, comptime removal_type: RemovalType) ?KV { | 1463 | fn fetchRemoveByKeyGeneric( |
| | 1464 | self: *Self, |
| | 1465 | key: anytype, |
| | 1466 | key_ctx: anytype, |
| | 1467 | ctx: ByIndexContext, |
| | 1468 | header: *IndexHeader, |
| | 1469 | comptime I: type, |
| | 1470 | comptime removal_type: RemovalType, |
| | 1471 | ) ?KV { |
| 1381 | const indexes = header.indexes(I); | 1472 | const indexes = header.indexes(I); |
| 1382 | const entry_index = self.removeFromIndexByKey(key, key_ctx, header, I, indexes) orelse return null; | 1473 | const entry_index = self.removeFromIndexByKey(key, key_ctx, header, I, indexes) orelse return null; |
| 1383 | const slice = self.entries.slice(); | 1474 | const slice = self.entries.slice(); |
| ... | @@ -1389,7 +1480,13 @@ pub fn ArrayHashMapUnmanaged( | ... | @@ -1389,7 +1480,13 @@ pub fn ArrayHashMapUnmanaged( |
| 1389 | return removed_entry; | 1480 | return removed_entry; |
| 1390 | } | 1481 | } |
| 1391 | | 1482 | |
| 1392 | fn removeByKey(self: *Self, key: anytype, key_ctx: anytype, ctx: ByIndexContext, comptime removal_type: RemovalType) bool { | 1483 | fn removeByKey( |
| | 1484 | self: *Self, |
| | 1485 | key: anytype, |
| | 1486 | key_ctx: anytype, |
| | 1487 | ctx: ByIndexContext, |
| | 1488 | comptime removal_type: RemovalType, |
| | 1489 | ) bool { |
| 1393 | const header = self.index_header orelse { | 1490 | const header = self.index_header orelse { |
| 1394 | // Linear scan. | 1491 | // Linear scan. |
| 1395 | const key_hash = if (store_hash) key_ctx.hash(key) else {}; | 1492 | const key_hash = if (store_hash) key_ctx.hash(key) else {}; |