Repository navigation
Expand file tree
/
Copy pathconcurrent-queue_20260909.html
More file actions
868 lines (805 loc) · 83.9 KB
/
Copy pathconcurrent-queue_20260909.html
File metadata and controls
868 lines (805 loc) · 83.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="dark">
<title>Code Audit — Interactive</title>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{color-scheme:dark}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0f172a;color:#e5e7eb;font-size:14px;line-height:1.5}
.app{max-width:1100px;margin:0 auto;padding:1.5rem 1rem}
.top{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;margin-bottom:1rem;flex-wrap:wrap}
h1{font-size:1.5rem;font-weight:700;color:#f1f5f9}
.meta{font-size:.8rem;color:#6b7280;margin-top:.2rem}
.top-right{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap}
.project-info{margin-bottom:1rem;padding:.75rem .9rem;background:#1e293b;border:1px solid #334155;border-radius:8px}
.project-info .pi-name{font-size:.95rem;font-weight:700;color:#f1f5f9}
.project-info .pi-desc{font-size:.82rem;color:#d1d5db;margin-top:.25rem}
.project-info .pi-row{display:flex;flex-wrap:wrap;gap:.4rem .9rem;margin-top:.45rem;font-size:.78rem;color:#9ca3af}
.project-info .pi-row .pi-k{color:#6b7280}
.project-info a{color:#60a5fa;text-decoration:none}
.project-info a:hover{text-decoration:underline}
.project-info code{font-family:"JetBrains Mono","Fira Code",Consolas,monospace;font-size:.74rem;color:#93c5fd}
.pi-repo-btn{display:inline-flex;align-items:center;gap:.3rem;padding:.18rem .5rem;background:#0f172a;border:1px solid #3b82f6;border-radius:6px;color:#60a5fa !important;font-weight:600}
.pi-repo-btn:hover{background:#1d283a;text-decoration:none !important}
#search{background:#1e293b;border:1px solid #334155;color:#e5e7eb;padding:.4rem .75rem;border-radius:6px;font-size:.875rem;width:220px;outline:none}
#search:focus{border-color:#3b82f6}
.group-label{font-size:.8rem;color:#9ca3af;display:flex;align-items:center;gap:.4rem}
select{background:#1e293b;border:1px solid #334155;color:#e5e7eb;padding:.35rem .6rem;border-radius:6px;font-size:.8rem;outline:none;cursor:pointer}
select:focus{border-color:#3b82f6}
.filterbar{display:flex;flex-direction:column;gap:.4rem;margin-bottom:.6rem;padding:.6rem .75rem;background:#1e293b;border:1px solid #334155;border-radius:8px}
.filter-row{display:flex;flex-wrap:wrap;gap:.3rem;align-items:center}
.filter-lbl{font-size:.65rem;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#4b5563;white-space:nowrap;margin-right:.25rem}
.filter-toggle{font-size:.65rem;font-weight:600;padding:.18rem .5rem;border-radius:4px;border:1px solid #334155;background:#0f172a;color:#9ca3af;cursor:pointer;margin-left:auto;white-space:nowrap;letter-spacing:.04em}
.filter-toggle:hover{background:#1e293b;color:#e5e7eb;border-color:#4b5563}
.badge{font-size:.68rem;font-weight:700;padding:.18rem .5rem;border-radius:4px;border:1px solid transparent;cursor:pointer;letter-spacing:.03em;transition:opacity .12s,filter .12s;user-select:none}
.badge.off{opacity:.28;filter:grayscale(.5)}
.sev-critical{background:#450a0a;color:#fca5a5;border-color:#991b1b}
.sev-high {background:#431407;color:#fdba74;border-color:#9a3412}
.sev-medium {background:#422006;color:#fcd34d;border-color:#854d0e}
.sev-low {background:#0c1a3a;color:#93c5fd;border-color:#1d4ed8}
.sev-negligible{background:#1e293b;color:#6b7280;border-color:#334155}
.type-badge{background:#0c1e33;color:#7dd3fc;border-color:#0369a1}
.diff-very-easy{background:#052e16;color:#86efac;border-color:#16a34a}
.diff-easy{background:#064e3b;color:#6ee7b7;border-color:#059669}
.diff-medium{background:#1e293b;color:#9ca3af;border-color:#4b5563}
.diff-hard{background:#2e1065;color:#c4b5fd;border-color:#7c3aed}
.st-open{background:#1e293b;color:#4b5563;border-color:#334155}
.st-fixed{background:#052e16;color:#4ade80;border-color:#16a34a}
.st-wont{background:#431407;color:#fdba74;border-color:#9a3412}
.st-fp{background:#2e1065;color:#c4b5fd;border-color:#7c3aed}
#progress-wrap{display:flex;align-items:center;gap:.75rem;margin-bottom:.6rem;flex-wrap:wrap}
#progress-bar{flex:1;min-width:120px;height:7px;background:#1e293b;border-radius:4px;overflow:hidden;border:1px solid #334155}
#progress-fill{height:100%;background:#22c55e;border-radius:4px;transition:width .35s ease;width:0}
#progress-text{font-size:.75rem;color:#6b7280;white-space:nowrap}
.action-btn{font-size:.72rem;padding:.25rem .6rem;border-radius:5px;border:1px solid #334155;background:#1e293b;color:#9ca3af;cursor:pointer;white-space:nowrap}
.action-btn:hover{background:#334155;color:#e5e7eb}
.action-btn.active{background:#1d4ed8;color:#fff;border-color:#2563eb}
.action-btn.active:hover{background:#2563eb}
.search-wrap{display:flex;align-items:center;gap:.35rem}
#hidden-bar{margin-bottom:.6rem}
.hidden-inner{display:flex;flex-wrap:wrap;gap:.35rem;align-items:center}
.hidden-lbl{font-size:.72rem;color:#4b5563}
.show-btn{font-size:.7rem;padding:.15rem .45rem;border-radius:4px;background:#1e293b;color:#9ca3af;border:1px solid #334155;cursor:pointer}
.show-btn:hover{background:#334155;color:#e5e7eb}
.show-all-btn{font-size:.7rem;padding:.15rem .45rem;border-radius:4px;background:#2563eb;color:#fff;border:none;cursor:pointer}
.show-all-btn:hover{background:#1d4ed8}
.group-section{margin-bottom:1.5rem}
.group-header{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem;padding-bottom:.35rem;border-bottom:1px solid #334155}
.group-title{font-size:.95rem;font-weight:700;color:#d1d5db}
.group-title.all-done{color:#4ade80}
.group-count{font-size:.72rem;color:#4b5563;background:#1e293b;padding:.1rem .4rem;border-radius:10px;white-space:nowrap}
.icon-btn{background:transparent;border:none;color:#334155;cursor:pointer;font-size:.85rem;padding:.05rem .2rem;line-height:1}
.icon-btn:hover{color:#6b7280}
.hide-btn{margin-left:auto;font-size:.68rem;padding:.12rem .45rem;border-radius:4px;background:transparent;color:#334155;border:1px solid #1e293b;cursor:pointer}
.hide-btn:hover{background:#1e293b;color:#6b7280}
.card{background:#1e293b;border:1px solid #334155;border-radius:8px;margin-bottom:.6rem;overflow:hidden;transition:opacity .15s}
.card.resolved{opacity:.55;border-left:3px solid #16a34a}
.card.resolved:hover{opacity:.85}
.card-header{display:flex;align-items:center;gap:.5rem;padding:.45rem .85rem;background:#162032;border-bottom:1px solid #334155;flex-wrap:wrap;cursor:pointer;user-select:none}
.card-header:hover{background:#1a2840}
.card-arrow{font-size:.65rem;color:#334155;flex-shrink:0}
.card-id{font-size:.75rem;font-weight:700;color:#6b7280;font-family:monospace;letter-spacing:.04em}
.card-loc{font-size:.72rem;color:#6b7280;font-family:"JetBrains Mono","Fira Code",Consolas,monospace;letter-spacing:.02em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
.status-btn{margin-left:auto;font-size:.68rem;font-weight:600;padding:.15rem .5rem;border-radius:4px;border:1px solid transparent;cursor:pointer;white-space:nowrap;transition:filter .1s}
.status-btn:hover{filter:brightness(1.25)}
.copy-btn{font-size:.68rem;font-weight:600;padding:.15rem .5rem;border-radius:4px;border:1px solid #334155;background:#0f172a;color:#9ca3af;cursor:pointer;white-space:nowrap;transition:background .12s,color .12s}
.copy-btn:hover{background:#334155;color:#e5e7eb}
.card-body{padding:.85rem .9rem}
.field-lbl{font-size:.85rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:#7dd3fc;margin:1rem 0 .35rem;padding-bottom:.18rem;border-bottom:1px solid #1e293b}
.field-lbl:first-child{margin-top:0}
.desc,.fix-txt{font-size:.875rem;color:#d1d5db;line-height:1.55}
.desc p,.fix-txt p{margin:.4rem 0}
.desc p:first-child,.fix-txt p:first-child{margin-top:0}
.desc p:last-child,.fix-txt p:last-child{margin-bottom:0}
.loc-block{margin:.3rem 0 .6rem}
.loc-ref{font-size:.8rem;color:#38bdf8;font-family:"JetBrains Mono","Fira Code",Consolas,monospace;font-weight:600;margin-bottom:.22rem}
a.loc-link{color:inherit;text-decoration:none}
a.loc-link:hover{text-decoration:underline}
.loc-lines{color:#4b5563;font-weight:400}
pre{background:#0f172a;color:#cdd6f4;border-radius:5px;padding:.6rem .8rem;overflow-x:auto;white-space:pre;font-size:.8rem;line-height:1.55;font-family:"JetBrains Mono","Fira Code",Consolas,monospace;border:1px solid #1e293b;max-height:320px}
.desc pre.md-code,.fix-txt pre.md-code{margin:.5rem 0;font-size:.82rem;color:#e5e7eb;background:#0b1224;border:1px solid #1e293b;max-height:none}
code.md-inline{font-family:"JetBrains Mono","Fira Code",Consolas,monospace;font-size:.86em;background:#0b1224;color:#fca5a5;border:1px solid #1e293b;border-radius:3px;padding:.04rem .32rem;white-space:pre-wrap;word-break:break-word}
.not-found{font-size:.8rem;color:#334155;font-style:italic}
mark{background:#854d0e;color:#fef9c3;border-radius:2px;padding:0 .1rem}
.no-results{text-align:center;color:#334155;padding:3rem 1rem;font-size:.9rem}
</style>
</head>
<body>
<script type="application/json" id="D">[
{"id":"OVF_1","type":"overflow_underflow","type_label":"Overflow Underflow","severity":"low","difficulty":"easy","description":"`Bounded::new` computes `let mark_bit = (cap + 1).next_power_of_two();` and `let one_lap = mark_bit * 2;` without any upper bound on `cap`, while `ConcurrentQueue::bounded` documents only one panic condition (\"If the capacity is zero, this constructor will panic\").\n\n`ConcurrentQueue::bounded(usize::MAX)` overflows in `cap + 1` (panic with `attempt to add with overflow` in debug, wrap to `0` in release). `next_power_of_two` itself panics in debug when the result overflows, and `mark_bit * 2` overflows for any `cap >= usize::MAX / 4`. In release the wrapped values would give `mark_bit == 1` / `one_lap == 2`, which breaks the entire stamp encoding.\n\nIn practice `Vec::with_capacity(cap)` panics with `capacity overflow` before a wrapped `one_lap` can be used, so this is a panic-message/contract problem rather than a memory-safety one: the user gets an arithmetic-overflow panic from an undocumented path instead of a clear error, and the release-vs-debug behaviour differs.","fix":"Add an explicit upper-bound assertion next to the existing one and document it:\n\n```rust\npub fn new(cap: usize) -> Bounded<T> {\n assert!(cap > 0, \"capacity must be positive\");\n // `mark_bit` is the first power of two greater than `cap`, and `one_lap` is\n // twice that, so the stamp encoding needs two spare high bits.\n assert!(\n cap <= usize::MAX / 4,\n \"capacity is too large for the stamp encoding\"\n );\n```\n\nand extend the `# Panics` section of `ConcurrentQueue::bounded` in `src/lib.rs` to mention the capacity upper bound.","locations":[{"ref":"src/bounded.rs:53-73","code":" 53 | pub fn new(cap: usize) -> Bounded<T> {\n 54 | assert!(cap > 0, \"capacity must be positive\");\n 55 | \n 56 | // Head is initialized to `{ lap: 0, mark: 0, index: 0 }`.\n 57 | let head = 0;\n 58 | // Tail is initialized to `{ lap: 0, mark: 0, index: 0 }`.\n 59 | let tail = 0;\n 60 | \n 61 | // Allocate a buffer of `cap` slots initialized with stamps.\n 62 | let mut buffer = Vec::with_capacity(cap);\n 63 | for i in 0..cap {\n 64 | // Set the stamp to `{ lap: 0, mark: 0, index: i }`.\n 65 | buffer.push(Slot {\n 66 | stamp: AtomicUsize::new(i),\n 67 | value: UnsafeCell::new(MaybeUninit::uninit()),\n 68 | });\n 69 | }\n 70 | \n 71 | // Compute constants `mark_bit` and `one_lap`.\n 72 | let mark_bit = (cap + 1).next_power_of_two();\n 73 | let one_lap = mark_bit * 2;","lines":21,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/bounded.rs#L53-L73"}]},
{"id":"LOGIC_1","type":"logic_error","type_label":"Logic Error","severity":"high","difficulty":"medium","description":"`Bounded::force_push` unconditionally overwrites the tail with `self.tail.store(new_tail, Ordering::SeqCst)`, which silently erases the `mark_bit` (the \"queue is closed\" flag) if `close()` runs concurrently.\n\nThe failure closure is entered with a `tail` value that was loaded in `push_or_else` and verified to have `mark_bit` clear. `new_tail` is derived from that value, so it also has `mark_bit` clear. If another thread calls `close()` (which does `self.tail.fetch_or(self.mark_bit, SeqCst)`) after that load but before the blind `store`, the mark bit is wiped and **the queue silently becomes open again**.\n\nThe head `compare_exchange_weak` cannot protect against this: it only guarantees that `head == tail.wrapping_sub(one_lap)`, i.e. that no other pusher/popper moved past `tail`. The mark bit is the one part of the tail that `close()` can change without changing the index/lap part, so it is exactly the bit that is lost.\n\nObservable consequences after the race: `is_closed()` returns `false` on a queue that was closed, a subsequent `close()` returns `true` a second time (violating the documented \"returns `false` if it was already closed\" contract), `push()` starts succeeding on a closed queue, and consumers that terminate on `PopError::Closed` never see it and may block forever. `force_push` also returns `Ok(Some(old))` instead of the correct `Err(ForcePushError(value))`.\n\nNote that `Unbounded::push` handles the same situation correctly - it uses `compare_exchange_weak` on `tail.index` and a `fetch_add` for the block-boundary bump, both of which fail or preserve `MARK_BIT` rather than overwriting it.","fix":"Never blindly `store` the tail. Since the head CAS already proved that the index/lap part of the tail is still `tail`, the only other possible value is `tail | mark_bit`, so a single `compare_exchange` plus a mark-preserving fallback is enough:\n\n```rust\n// Try to move the head.\nif self\n .head\n .compare_exchange_weak(head, new_head, Ordering::SeqCst, Ordering::Relaxed)\n .is_ok()\n{\n // Move the tail. The only way this can fail is a concurrent `close()`\n // setting `mark_bit`, which must be preserved.\n if self\n .tail\n .compare_exchange(tail, new_tail, Ordering::SeqCst, Ordering::Relaxed)\n .is_err()\n {\n self.tail.store(new_tail | self.mark_bit, Ordering::SeqCst);\n }\n // ... rest unchanged\n```\n\nAlternatively, keep the blind `store` but re-check the close flag afterwards and re-apply it with `fetch_or(self.mark_bit, ...)`. A loom test that runs `force_push` concurrently with `close()` and asserts `is_closed()` afterwards should be added to `tests/loom.rs` to lock the behaviour down.","locations":[{"ref":"src/bounded.rs:100-136","code":" 100 | pub fn force_push(&self, value: T) -> Result<Option<T>, ForcePushError<T>> {\n 101 | let result = self.push_or_else(value, |value, tail, new_tail, slot| {\n 102 | let head = tail.wrapping_sub(self.one_lap);\n 103 | let new_head = new_tail.wrapping_sub(self.one_lap);\n 104 | \n 105 | // Try to move the head.\n 106 | if self\n 107 | .head\n 108 | .compare_exchange_weak(head, new_head, Ordering::SeqCst, Ordering::Relaxed)\n 109 | .is_ok()\n 110 | {\n 111 | // Move the tail.\n 112 | self.tail.store(new_tail, Ordering::SeqCst);\n 113 | \n 114 | // Swap out the old value.\n 115 | // SAFETY: We know this is initialized, since it's covered by the current queue.\n 116 | let old = unsafe {\n 117 | slot.value\n 118 | .with_mut(|slot| slot.replace(MaybeUninit::new(value)).assume_init())\n 119 | };\n 120 | \n 121 | // Update the stamp.\n 122 | slot.stamp.store(tail + 1, Ordering::Release);\n 123 | \n 124 | // Return a PushError.\n 125 | Err(PushError::Full(old))\n 126 | } else {\n 127 | Ok(value)\n 128 | }\n 129 | });\n 130 | \n 131 | match result {\n 132 | Ok(()) => Ok(None),\n 133 | Err(PushError::Full(old_value)) => Ok(Some(old_value)),\n 134 | Err(PushError::Closed(value)) => Err(ForcePushError(value)),\n 135 | }\n 136 | }","lines":37,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/bounded.rs#L100-L136"},{"ref":"src/bounded.rs:350-353","code":" 350 | pub fn close(&self) -> bool {\n 351 | let tail = self.tail.fetch_or(self.mark_bit, Ordering::SeqCst);\n 352 | tail & self.mark_bit == 0\n 353 | }","lines":4,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/bounded.rs#L350-L353"}]},
{"id":"LOGIC_2","type":"logic_error","type_label":"Logic Error","severity":"medium","difficulty":"easy","description":"`Single::push` performs a single `compare_exchange` from state `0` and gives up immediately, so it reports `PushError::Full` when the slot is merely **transiently locked by a concurrent `pop`**, not full.\n\n`Single::pop` acquires the slot by CAS-ing `state` from `PUSHED` to `(state | LOCKED) & !PUSHED`. During the window between that CAS and the `fetch_and(!LOCKED, ...)` unlock, `state == LOCKED` - `PUSHED` has already been cleared, so the item has *already been logically removed*. A `push` racing into that window sees `state != 0` and `state & CLOSED == 0`, and falls into the final `else` branch returning `Err(PushError::Full(value))`.\n\nThis produces a directly observable contradiction: `q.push(x)` returns `PushError::Full` while `q.is_empty()` returns `true` and `q.len()` returns `0` (both derived from the `PUSHED` bit, which is already clear). It is also inconsistent with every sibling implementation - `Single::force_push`, `Single::pop`, `Bounded::push_or_else` and `Unbounded::push` all busy-wait and retry when they observe a transient lock instead of reporting a bogus full/empty result.\n\nImpact for callers: code built on `ConcurrentQueue::bounded(1)` (a very common single-slot channel/notification cell) can get a spurious `Full` and park a producer even though the slot was free, which in a notify-on-pop design is a lost wakeup rather than a retry.","fix":"Retry on the transient-lock case, the way `force_push` and `pop` already do:\n\n```rust\npub fn push(&self, value: T) -> Result<(), PushError<T>> {\n loop {\n // Lock and fill the slot.\n let state = self\n .state\n .compare_exchange(0, LOCKED | PUSHED, Ordering::SeqCst, Ordering::SeqCst)\n .unwrap_or_else(|x| x);\n\n if state == 0 {\n // Write the value and unlock.\n self.slot.with_mut(|slot| unsafe {\n slot.write(MaybeUninit::new(value));\n });\n self.state.fetch_and(!LOCKED, Ordering::Release);\n return Ok(());\n } else if state & CLOSED != 0 {\n return Err(PushError::Closed(value));\n } else if state & PUSHED != 0 {\n return Err(PushError::Full(value));\n }\n\n // `state == LOCKED`: a concurrent `pop` has already taken the item but\n // has not unlocked yet, so the queue is not full - wait and retry.\n busy_wait();\n }\n}\n```","locations":[{"ref":"src/single.rs:30-48","code":" 30 | pub fn push(&self, value: T) -> Result<(), PushError<T>> {\n 31 | // Lock and fill the slot.\n 32 | let state = self\n 33 | .state\n 34 | .compare_exchange(0, LOCKED | PUSHED, Ordering::SeqCst, Ordering::SeqCst)\n 35 | .unwrap_or_else(|x| x);\n 36 | \n 37 | if state == 0 {\n 38 | // Write the value and unlock.\n 39 | self.slot.with_mut(|slot| unsafe {\n 40 | slot.write(MaybeUninit::new(value));\n 41 | });\n 42 | self.state.fetch_and(!LOCKED, Ordering::Release);\n 43 | Ok(())\n 44 | } else if state & CLOSED != 0 {\n 45 | Err(PushError::Closed(value))\n 46 | } else {\n 47 | Err(PushError::Full(value))\n 48 | }","lines":19,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/single.rs#L30-L48"},{"ref":"src/single.rs:105-126","code":" 105 | pub fn pop(&self) -> Result<T, PopError> {\n 106 | let mut state = PUSHED;\n 107 | loop {\n 108 | // Lock and empty the slot.\n 109 | let prev = self\n 110 | .state\n 111 | .compare_exchange(\n 112 | state,\n 113 | (state | LOCKED) & !PUSHED,\n 114 | Ordering::SeqCst,\n 115 | Ordering::SeqCst,\n 116 | )\n 117 | .unwrap_or_else(|x| x);\n 118 | \n 119 | if prev == state {\n 120 | // Read the value and unlock.\n 121 | let value = self\n 122 | .slot\n 123 | .with_mut(|slot| unsafe { slot.read().assume_init() });\n 124 | self.state.fetch_and(!LOCKED, Ordering::Release);\n 125 | return Ok(value);\n 126 | }","lines":22,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/single.rs#L105-L126"}]},
{"id":"PERF_1","type":"performance","type_label":"Performance","severity":"medium","difficulty":"easy","description":"Both `Bounded::push_or_else` and `Bounded::pop` contain a retry path that spins with **no yield and no spin hint at all** on non-`loom` builds. In `push_or_else` the `busy_wait()` call after the failure closure is gated behind `#[cfg(loom)]`; the same is true for the empty-check branch in `pop`. Only the third (`else`) branch of each loop actually calls `busy_wait()`.\n\nThe `push_or_else` path is reached when the slot's stamp lags one lap behind the tail but the queue is not actually full - i.e. a popper has already advanced `self.head` but has not yet published `slot.stamp`. In `pop`, the corresponding path is reached when `stamp == head` but `tail != head` - a pusher has CAS'd the tail but has not yet written the value and stamp. In both cases the thread loops until the other thread finishes its handful of instructions, and each iteration executes `crate::full_fence()` - which is a `lock`-prefixed instruction on x86, a genuine full memory barrier - plus a `SeqCst`-strength stamp load and a tail/head reload.\n\nSo instead of a cheap `pause`/`yield`, the spinning thread hammers the `stamp`, `head` and `tail` cache lines with serializing instructions, stealing bus bandwidth from exactly the thread it is waiting on. If the other thread is preempted mid-operation (oversubscribed thread pool, single core, VM), this burns a whole scheduling quantum at full speed.\n\nThe `#[cfg(loom)]` gate makes the omission look accidental: the comment says loom \"complains if there isn't an explicit busy wait here\", implying the author added the call only to satisfy loom rather than noticing the production path needs it too.","fix":"Make `busy_wait()` unconditional in both retry paths - it is already a no-op-cheap `spin_loop()` on `no_std` and `yield_now()` with `std`:\n\n```rust\n} else if stamp.wrapping_add(self.one_lap) == tail + 1 {\n crate::full_fence();\n\n // We've failed to push; run our failure closure.\n value = fail(value, tail, new_tail, slot)?;\n\n busy_wait();\n tail = self.tail.load(Ordering::Relaxed);\n}\n```\n\nand in `pop`:\n\n```rust\n // ... after the empty/closed checks\n busy_wait();\n head = self.head.load(Ordering::Relaxed);\n}\n```\n\nThe `#[cfg(loom)]` attributes and their comments can then be deleted. Ideally combine this with the spin-then-yield backoff described in the `busy_wait` finding so the first few iterations use `spin_loop()` and only later iterations yield.","locations":[{"ref":"src/bounded.rs:202-217","code":" 202 | } else if stamp.wrapping_add(self.one_lap) == tail + 1 {\n 203 | crate::full_fence();\n 204 | \n 205 | // We've failed to push; run our failure closure.\n 206 | value = fail(value, tail, new_tail, slot)?;\n 207 | \n 208 | // Loom complains if there isn't an explicit busy wait here.\n 209 | #[cfg(loom)]\n 210 | busy_wait();\n 211 | \n 212 | tail = self.tail.load(Ordering::Relaxed);\n 213 | } else {\n 214 | // Yield because we need to wait for the stamp to get updated.\n 215 | busy_wait();\n 216 | tail = self.tail.load(Ordering::Relaxed);\n 217 | }","lines":16,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/bounded.rs#L202-L217"},{"ref":"src/bounded.rs:266-289","code":" 266 | } else if stamp == head {\n 267 | crate::full_fence();\n 268 | let tail = self.tail.load(Ordering::Relaxed);\n 269 | \n 270 | // If the tail equals the head, that means the queue is empty.\n 271 | if (tail & !self.mark_bit) == head {\n 272 | // Check if the queue is closed.\n 273 | if tail & self.mark_bit != 0 {\n 274 | return Err(PopError::Closed);\n 275 | } else {\n 276 | return Err(PopError::Empty);\n 277 | }\n 278 | }\n 279 | \n 280 | // Loom complains if there isn't a busy-wait here.\n 281 | #[cfg(loom)]\n 282 | busy_wait();\n 283 | \n 284 | head = self.head.load(Ordering::Relaxed);\n 285 | } else {\n 286 | // Yield because we need to wait for the stamp to get updated.\n 287 | busy_wait();\n 288 | head = self.head.load(Ordering::Relaxed);\n 289 | }","lines":24,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/bounded.rs#L266-L289"}]},
{"id":"PERF_2","type":"performance","type_label":"Performance","severity":"low","difficulty":"medium","description":"With the default `std` feature, `busy_wait()` unconditionally calls `std::thread::yield_now()`, i.e. it issues a `sched_yield` syscall on the *very first* contention, with no cheap spin phase and no backoff.\n\nEvery wait site in the crate goes through this function, including the tightest ones: `Slot::wait_write` (waits for a pusher to flip the `WRITE` bit - typically two or three instructions away), `Block::wait_next` (waits for `next` to be published, likewise a couple of instructions away), and the block-boundary retry loops in `Unbounded::push`/`Unbounded::pop`. A `sched_yield` costs on the order of a microsecond and can hand the core to an unrelated thread, whereas the awaited store usually lands within tens of nanoseconds - a plain `core::hint::spin_loop()` would be two orders of magnitude cheaper.\n\nThe `no_std` path has the mirror-image problem: it *only* ever calls `spin_loop()` and never yields, so a waiter that is descheduled-blocked (the thread it waits on has been preempted) spins at full speed indefinitely.","fix":"Use a spin-then-yield backoff instead of a single unconditional strategy. A minimal version that keeps the existing call sites unchanged is to give the hot waiters a local counter:\n\n```rust\n/// Notify the CPU that we are currently busy-waiting.\n///\n/// Spins cheaply for the first few iterations, then falls back to yielding so\n/// a preempted peer thread can make progress.\n#[inline]\npub(crate) fn busy_wait_backoff(iters: &mut u32) {\n const SPIN_LIMIT: u32 = 6;\n\n if *iters < SPIN_LIMIT {\n for _ in 0..(1u32 << *iters) {\n spin_loop();\n }\n *iters += 1;\n } else {\n busy_wait();\n }\n}\n```\n\nand use it in `Slot::wait_write` / `Block::wait_next`:\n\n```rust\nfn wait_write(&self) {\n let mut iters = 0;\n while self.state.load(Ordering::Acquire) & WRITE == 0 {\n busy_wait_backoff(&mut iters);\n }\n}\n```\n\nNote that `spin_loop` is currently only imported under `#[cfg(not(feature = \"std\"))]` in all three `sync_impl` modules; that gate has to be dropped so both primitives are available together.","locations":[{"ref":"src/sync.rs:47-55","code":" 47 | /// Notify the CPU that we are currently busy-waiting.\n 48 | #[inline]\n 49 | pub(crate) fn busy_wait() {\n 50 | #[cfg(feature = \"std\")]\n 51 | yield_now();\n 52 | \n 53 | #[cfg(not(feature = \"std\"))]\n 54 | spin_loop();\n 55 | }","lines":9,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/sync.rs#L47-L55"},{"ref":"src/unbounded.rs:74-79","code":" 74 | /// Waits until a value is written into the slot.\n 75 | fn wait_write(&self) {\n 76 | while self.state.load(Ordering::Acquire) & WRITE == 0 {\n 77 | busy_wait();\n 78 | }\n 79 | }","lines":6,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/unbounded.rs#L74-L79"},{"ref":"src/unbounded.rs:102-111","code":" 102 | /// Waits until the next pointer is set.\n 103 | fn wait_next(&self) -> *mut Block<T> {\n 104 | loop {\n 105 | let next = self.next.load(Ordering::Acquire);\n 106 | if !next.is_null() {\n 107 | return next;\n 108 | }\n 109 | busy_wait();\n 110 | }\n 111 | }","lines":10,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/unbounded.rs#L102-L111"}]},
{"id":"CMT_1","type":"invalid_comment","type_label":"Invalid Comment","severity":"low","difficulty":"very_easy","description":"The doc comment on `Bounded::push_or_else` states that if `fail` returns `Ok(val)`, the function will \"try pushing `val` to the **head** of the queue\". Items are never pushed to the head - the head is where `pop` takes items from. The code simply reloads `self.tail` and retries the push at the tail on the next loop iteration.\n\nA reader trusting this comment would conclude that `push_or_else` has a second, head-side insertion path that does not exist, which is actively misleading for the trickiest function in the file.","fix":"Change\n\n```rust\n/// If `fail` returns `Ok(val)`, we will try pushing `val` to the head of the queue. Otherwise,\n/// this function will return the error.\n```\n\nto\n\n```rust\n/// If `fail` returns `Ok(val)`, we will retry pushing `val` at the tail of the queue. Otherwise,\n/// this function will return the error.\n```","locations":[{"ref":"src/bounded.rs:148-149","code":" 148 | /// If `fail` returns `Ok(val)`, we will try pushing `val` to the head of the queue. Otherwise,\n 149 | /// this function will return the error.","lines":2,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/bounded.rs#L148-L149"}]},
{"id":"CMT_2","type":"invalid_comment","type_label":"Invalid Comment","severity":"low","difficulty":"very_easy","description":"The doc comment on `ForcePushError` says the error occurs when force-pushing into a **full** queue, but `force_push` never fails on a full queue - that is its entire purpose (it displaces an element instead). `ForcePushError` is returned only when the queue is **closed**: `Single::force_push` returns it on `prev & CLOSED != 0`, `Bounded::force_push` maps only `PushError::Closed` to it, and the `Unbounded` arm in `ConcurrentQueue::force_push` maps only `PushError::Closed` to it while treating `PushError::Full` as `unreachable!()`.\n\nThe rest of the code agrees that the error means \"closed\" - `impl Display for ForcePushError` prints the string `\"Closed\"`, and the doc example on `ConcurrentQueue::force_push` demonstrates it only after `q.close()`. So the type-level doc is the single wrong statement, and it is the one a user reads first in the rustdoc index.","fix":"Change\n\n```rust\n/// Error that occurs when force-pushing into a full queue.\n```\n\nto\n\n```rust\n/// Error that occurs when force-pushing into a closed queue.\n```","locations":[{"ref":"src/lib.rs:604-606","code":" 604 | /// Error that occurs when force-pushing into a full queue.\n 605 | #[derive(Clone, Copy, PartialEq, Eq)]\n 606 | pub struct ForcePushError<T>(pub T);","lines":3,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/lib.rs#L604-L606"},{"ref":"src/lib.rs:621-625","code":" 621 | impl<T> fmt::Display for ForcePushError<T> {\n 622 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n 623 | write!(f, \"Closed\")\n 624 | }\n 625 | }","lines":5,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/lib.rs#L621-L625"}]},
{"id":"CMT_3","type":"invalid_comment","type_label":"Invalid Comment","severity":"negligible","difficulty":"very_easy","description":"In `Single::force_push`, the comment `// If the value was pushed, swap out the value.` sits directly above `let prev_value = if prev & PUSHED == 0 {`, i.e. above the branch taken when the value was **not** pushed (which writes a fresh value and returns `None`). The swap it describes happens in the `else` branch. As written, the comment reads as documentation of the inverted condition.","fix":"Move the comment onto the branch it describes, or restate it to cover both:\n\n```rust\n// If the slot already holds a value, swap it out; otherwise just fill it.\nlet prev_value = if prev & PUSHED == 0 {\n```","locations":[{"ref":"src/single.rs:67-84","code":" 67 | if prev == state {\n 68 | // If the value was pushed, swap out the value.\n 69 | let prev_value = if prev & PUSHED == 0 {\n 70 | // SAFETY: write is safe because we have locked the state.\n 71 | self.slot.with_mut(|slot| unsafe {\n 72 | slot.write(MaybeUninit::new(value));\n 73 | });\n 74 | None\n 75 | } else {\n 76 | // SAFETY: replace is safe because we have locked the state, and\n 77 | // assume_init is safe because we have checked that the value was pushed.\n 78 | let prev_value = unsafe {\n 79 | self.slot.with_mut(move |slot| {\n 80 | ptr::replace(slot, MaybeUninit::new(value)).assume_init()\n 81 | })\n 82 | };\n 83 | Some(prev_value)\n 84 | };","lines":18,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/single.rs#L67-L84"}]},
{"id":"ARCH_1","type":"architecture","type_label":"Architecture","severity":"low","difficulty":"medium","description":"`Bounded::force_push` communicates its **success** case by returning an error: the failure closure returns `Err(PushError::Full(old))` to signal \"an element was successfully displaced\", and the caller then translates `Err(PushError::Full(old_value))` into `Ok(Some(old_value))`. So the meaning of `PushError::Full` depends on which layer observes it - a genuine \"queue is full, item rejected\" in `Bounded::push`, and \"item accepted, here is the evicted one\" in `Bounded::force_push`.\n\nCombined with the `push_or_else` control flow - a closure that receives four positional `usize`/`&Slot<T>` parameters, may write to the slot itself, and whose `Ok`/`Err` return decides whether the outer loop retries - this makes the most safety-critical code in the crate (it drives raw `MaybeUninit` writes and stamp publication) read backwards. The blind `self.tail.store` bug reported separately lives in exactly this closure and is easy to miss precisely because the success path looks like error handling.","fix":"Give the closure its own three-state result type instead of reusing the public `PushError`:\n\n```rust\nenum SlotFull<T> {\n /// Retry the push at the tail.\n Retry(T),\n /// An element was displaced; hand it back to the caller.\n Displaced(T),\n /// Give up with this error.\n Failed(PushError<T>),\n}\n```\n\n`push_or_else` then returns `Result<Option<T>, PushError<T>>` (`Ok(None)` = pushed, `Ok(Some(old))` = displaced), `Bounded::push` maps `SlotFull::Failed(PushError::Full(v))` for the full case, and `Bounded::force_push` becomes a direct pass-through with no error-to-success translation.","locations":[{"ref":"src/bounded.rs:100-136","code":" 100 | pub fn force_push(&self, value: T) -> Result<Option<T>, ForcePushError<T>> {\n 101 | let result = self.push_or_else(value, |value, tail, new_tail, slot| {\n 102 | let head = tail.wrapping_sub(self.one_lap);\n 103 | let new_head = new_tail.wrapping_sub(self.one_lap);\n 104 | \n 105 | // Try to move the head.\n 106 | if self\n 107 | .head\n 108 | .compare_exchange_weak(head, new_head, Ordering::SeqCst, Ordering::Relaxed)\n 109 | .is_ok()\n 110 | {\n 111 | // Move the tail.\n 112 | self.tail.store(new_tail, Ordering::SeqCst);\n 113 | \n 114 | // Swap out the old value.\n 115 | // SAFETY: We know this is initialized, since it's covered by the current queue.\n 116 | let old = unsafe {\n 117 | slot.value\n 118 | .with_mut(|slot| slot.replace(MaybeUninit::new(value)).assume_init())\n 119 | };\n 120 | \n 121 | // Update the stamp.\n 122 | slot.stamp.store(tail + 1, Ordering::Release);\n 123 | \n 124 | // Return a PushError.\n 125 | Err(PushError::Full(old))\n 126 | } else {\n 127 | Ok(value)\n 128 | }\n 129 | });\n 130 | \n 131 | match result {\n 132 | Ok(()) => Ok(None),\n 133 | Err(PushError::Full(old_value)) => Ok(Some(old_value)),\n 134 | Err(PushError::Closed(value)) => Err(ForcePushError(value)),\n 135 | }\n 136 | }","lines":37,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/bounded.rs#L100-L136"},{"ref":"src/bounded.rs:138-219","code":" 138 | /// Attempts to push an item into the queue, running a closure on failure.\n 139 | ///\n 140 | /// `fail` is run when there is no more room left in the tail of the queue. The parameters of\n 141 | /// this function are as follows:\n 142 | ///\n 143 | /// - The item that failed to push.\n 144 | /// - The value of `self.tail` before the new value would be inserted.\n 145 | /// - The value of `self.tail` after the new value would be inserted.\n 146 | /// - The slot that we attempted to push into.\n 147 | ///\n 148 | /// If `fail` returns `Ok(val)`, we will try pushing `val` to the head of the queue. Otherwise,\n 149 | /// this function will return the error.\n 150 | fn push_or_else<F>(&self, mut value: T, mut fail: F) -> Result<(), PushError<T>>\n 151 | where\n 152 | F: FnMut(T, usize, usize, &Slot<T>) -> Result<T, PushError<T>>,\n 153 | {\n 154 | let mut tail = self.tail.load(Ordering::Relaxed);\n 155 | \n 156 | loop {\n 157 | // Check if the queue is closed.\n 158 | if tail & self.mark_bit != 0 {\n 159 | return Err(PushError::Closed(value));\n 160 | }\n 161 | \n 162 | // Deconstruct the tail.\n 163 | let index = tail & (self.mark_bit - 1);\n 164 | let lap = tail & !(self.one_lap - 1);\n 165 | \n 166 | // Calculate the new location of the tail.\n 167 | let new_tail = if index + 1 < self.buffer.len() {\n 168 | // Same lap, incremented index.\n 169 | // Set to `{ lap: lap, mark: 0, index: index + 1 }`.\n 170 | tail + 1\n 171 | } else {\n 172 | // One lap forward, index wraps around to zero.\n 173 | // Set to `{ lap: lap.wrapping_add(1), mark: 0, index: 0 }`.\n 174 | lap.wrapping_add(self.one_lap)\n 175 | };\n 176 | \n 177 | // Inspect the corresponding slot.\n 178 | let slot = &self.buffer[index];\n 179 | let stamp = slot.stamp.load(Ordering::Acquire);\n 180 | \n 181 | // If the tail and the stamp match, we may attempt to push.\n 182 | if tail == stamp {\n 183 | // Try moving the tail.\n 184 | match self.tail.compare_exchange_weak(\n 185 | tail,\n 186 | new_tail,\n 187 | Ordering::SeqCst,\n 188 | Ordering::Relaxed,\n 189 | ) {\n 190 | Ok(_) => {\n 191 | // Write the value into the slot and update the stamp.\n 192 | slot.value.with_mut(|slot| unsafe {\n 193 | slot.write(MaybeUninit::new(value));\n 194 | });\n 195 | slot.stamp.store(tail + 1, Ordering::Release);\n 196 | return Ok(());\n 197 | }\n 198 | Err(t) => {\n 199 | tail = t;\n 200 | }\n 201 | }\n 202 | } else if stamp.wrapping_add(self.one_lap) == tail + 1 {\n 203 | crate::full_fence();\n 204 | \n 205 | // We've failed to push; run our failure closure.\n 206 | value = fail(value, tail, new_tail, slot)?;\n 207 | \n 208 | // Loom complains if there isn't an explicit busy wait here.\n 209 | #[cfg(loom)]\n 210 | busy_wait();\n 211 | \n 212 | tail = self.tail.load(Ordering::Relaxed);\n 213 | } else {\n 214 | // Yield because we need to wait for the stamp to get updated.\n 215 | busy_wait();\n 216 | tail = self.tail.load(Ordering::Relaxed);\n 217 | }\n 218 | }\n 219 | }","lines":82,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/bounded.rs#L138-L219"}]},
{"id":"ARCH_2","type":"architecture","type_label":"Architecture","severity":"low","difficulty":"easy","description":"`src/sync.rs` exists specifically as the \"synchronization facade to choose between `core` primitives and `loom` primitives\", yet `full_fence` - the crate's most platform-specific synchronization primitive, containing raw `core::arch::asm!` with per-architecture and per-pointer-width `cfg` gates plus `not(miri)`/`not(loom)` guards - lives at the bottom of `src/lib.rs`, the public API file.\n\nThe consequence is split ownership of the same concern: `bounded.rs` and `unbounded.rs` get atomics, cells and `busy_wait` from `crate::sync`, but reach for `crate::full_fence()` instead, and `lib.rs` needs `use sync::atomic::{self, Ordering};` purely for that one fallback. Anyone adding a new architecture case, or reviewing the loom/Miri exclusions as a set, has to know to look in two files. `lib.rs` is also the only place in the crate with `unsafe` inline assembly, which is easy to miss when it is 600 lines below the module docs.","fix":"Move `full_fence` into `src/sync.rs` next to `busy_wait` (both are \"how this crate talks to the CPU/scheduler\"), export it as `pub(crate) fn full_fence()`, and have `bounded.rs`/`unbounded.rs` call `crate::sync::full_fence()` instead of `crate::full_fence()`. The `use sync::atomic::{self, Ordering};` import in `src/lib.rs` then becomes unused and should be deleted.\n\nThis also puts all the `cfg(loom)` / `cfg(miri)` special-casing in a single file, so the loom story can be reviewed in one place.","locations":[{"ref":"src/lib.rs:630-660","code":" 630 | /// Equivalent to `atomic::fence(Ordering::SeqCst)`, but in some cases faster.\n 631 | #[inline]\n 632 | fn full_fence() {\n 633 | #[cfg(all(any(target_arch = \"x86\", target_arch = \"x86_64\"), not(miri), not(loom)))]\n 634 | {\n 635 | use core::{arch::asm, cell::UnsafeCell};\n 636 | // HACK(stjepang): On x86 architectures there are two different ways of executing\n 637 | // a `SeqCst` fence.\n 638 | //\n 639 | // 1. `atomic::fence(SeqCst)`, which compiles into a `mfence` instruction.\n 640 | // 2. A `lock <op>` instruction.\n 641 | //\n 642 | // Both instructions have the effect of a full barrier, but empirical benchmarks have shown\n 643 | // that the second one is sometimes a bit faster.\n 644 | let a = UnsafeCell::new(0_usize);\n 645 | // It is common to use `lock or` here, but when using a local variable, `lock not`, which\n 646 | // does not change the flag, should be slightly more efficient.\n 647 | // Refs: https://www.felixcloutier.com/x86/not\n 648 | unsafe {\n 649 | #[cfg(target_pointer_width = \"64\")]\n 650 | asm!(\"lock not qword ptr [{0}]\", in(reg) a.get(), options(nostack, preserves_flags));\n 651 | #[cfg(target_pointer_width = \"32\")]\n 652 | asm!(\"lock not dword ptr [{0:e}]\", in(reg) a.get(), options(nostack, preserves_flags));\n 653 | }\n 654 | return;\n 655 | }\n 656 | #[allow(unreachable_code)]\n 657 | {\n 658 | atomic::fence(Ordering::SeqCst);\n 659 | }\n 660 | }","lines":31,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/lib.rs#L630-L660"},{"ref":"src/sync.rs:1","code":" 1 | //! Synchronization facade to choose between `core` primitives and `loom` primitives.","lines":1,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/sync.rs#L1"}]},
{"id":"OTHER_1","type":"other","type_label":"Other","severity":"low","difficulty":"very_easy","description":"`Cargo.toml` declares `rust-version = \"1.60\"` but uses the `[lints]` table, which Cargo only understands from **1.74** onwards. On any toolchain in the declared support range 1.60-1.73, Cargo emits `warning: unused manifest key: lints` and silently drops the configuration, so the `check-cfg = ['cfg(loom)']` setting that the crate relies on to keep `cfg(loom)` from being flagged as unexpected is not applied at all. (The `unexpected_cfgs` lint it configures is itself only available from 1.80.)\n\nThe same manifest also pins `criterion = \"0.8\"` as a dev-dependency, whose own MSRV is far above 1.60, so `cargo test` / `cargo bench` cannot even resolve on the declared minimum toolchain. The net effect is that the advertised MSRV is neither honoured by the manifest nor exercisable, which is exactly the situation an MSRV declaration is supposed to prevent.","fix":"Either raise the declared MSRV to the value the manifest actually requires:\n\n```toml\nrust-version = \"1.74\"\n```\n\nor keep `rust-version = \"1.60\"` and drop the `[lints]` table, passing the lint config through `build.rs` / `RUSTFLAGS` instead:\n\n```rust\n// build.rs\nfn main() {\n println!(\"cargo:rustc-check-cfg=cfg(loom)\");\n}\n```\n\nIn either case, pin the dev-dependencies that gate `cargo test` (notably `criterion`) behind a version that resolves on the declared minimum, or document that dev-dependencies require a newer toolchain than the library itself.","locations":[{"ref":"Cargo.toml:12-13","code":" 12 | edition = \"2021\"\n 13 | rust-version = \"1.60\"","lines":2,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/Cargo.toml#L12-L13"},{"ref":"Cargo.toml:33-46","code":" 33 | [[bench]]\n 34 | name = \"bench\"\n 35 | harness = false\n 36 | \n 37 | [dev-dependencies]\n 38 | criterion = { version = \"0.8\", default-features = false, features = [\"cargo_bench_support\"] }\n 39 | easy-parallel = \"3.1.0\"\n 40 | fastrand = \"2.0.0\"\n 41 | \n 42 | [target.'cfg(target_family = \"wasm\")'.dev-dependencies]\n 43 | wasm-bindgen-test = \"0.3\"\n 44 | \n 45 | [lints.rust]\n 46 | unexpected_cfgs = { level = \"warn\", check-cfg = ['cfg(loom)'] }","lines":14,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/Cargo.toml#L33-L46"}]},
{"id":"OTHER_2","type":"other","type_label":"Other","severity":"low","difficulty":"easy","description":"The queue-length formula is written out twice, verbatim, in `Bounded::len` and in `Bounded::drop`: the same four-way `hix < tix` / `hix > tix` / `(tail & !mark_bit) == head` / `buffer.len()` cascade. `drop` relies on that length to decide how many slots to `drop_in_place`, so if the two copies ever diverge the result is either leaked values or a double drop of uninitialised memory - a silent memory-safety bug rather than a visible logic error.\n\nThe duplication is short (about nine lines each) but load-bearing, and nothing links the two sites: neither has a comment pointing at the other, and `drop` has to re-derive `hix`/`tix` from `mark_bit` because the shared helper does not exist.","fix":"Extract the formula into one private helper that both call, so `drop` and `len` cannot drift apart:\n\n```rust\nimpl<T> Bounded<T> {\n /// Number of live items given a `head`/`tail` stamp pair.\n fn len_from(&self, head: usize, tail: usize) -> usize {\n Self::len_impl(head, tail, self.mark_bit, self.buffer.len())\n }\n\n fn len_impl(head: usize, tail: usize, mark_bit: usize, cap: usize) -> usize {\n let hix = head & (mark_bit - 1);\n let tix = tail & (mark_bit - 1);\n\n if hix < tix {\n tix - hix\n } else if hix > tix {\n cap - hix + tix\n } else if (tail & !mark_bit) == head {\n 0\n } else {\n cap\n }\n }\n}\n```\n\n`len` then becomes `return self.len_from(head, tail);` and `drop` calls `Self::len_impl(head, tail, mark_bit, buffer.len())` (the free-function form is needed there because `self` is already destructured).","locations":[{"ref":"src/bounded.rs:302-313","code":" 302 | let hix = head & (self.mark_bit - 1);\n 303 | let tix = tail & (self.mark_bit - 1);\n 304 | \n 305 | return if hix < tix {\n 306 | tix - hix\n 307 | } else if hix > tix {\n 308 | self.buffer.len() - hix + tix\n 309 | } else if (tail & !self.mark_bit) == head {\n 310 | 0\n 311 | } else {\n 312 | self.buffer.len()\n 313 | };","lines":12,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/bounded.rs#L302-L313"},{"ref":"src/bounded.rs:376-387","code":" 376 | let hix = head & (mark_bit - 1);\n 377 | let tix = tail & (mark_bit - 1);\n 378 | \n 379 | let len = if hix < tix {\n 380 | tix - hix\n 381 | } else if hix > tix {\n 382 | buffer.len() - hix + tix\n 383 | } else if (tail & !mark_bit) == head {\n 384 | 0\n 385 | } else {\n 386 | buffer.len()\n 387 | };","lines":12,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/bounded.rs#L376-L387"}]},
{"id":"OTHER_3","type":"other","type_label":"Other","severity":"low","difficulty":"very_easy","description":"`impl fmt::Debug for TryIter` builds the output with `f.debug_tuple(\"Iter\")`, so the rendered debug string is `Iter(ConcurrentQueue { .. })` for a type that is publicly named `TryIter`. This is a leftover from when the type was called `Iter`; the name no longer exists in the public API, so `{:?}` output cannot be matched back to any type a user can name.","fix":"Change\n\n```rust\nf.debug_tuple(\"Iter\").field(&self.queue).finish()\n```\n\nto\n\n```rust\nf.debug_tuple(\"TryIter\").field(&self.queue).finish()\n```","locations":[{"ref":"src/lib.rs:484-488","code":" 484 | impl<T> fmt::Debug for TryIter<'_, T> {\n 485 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n 486 | f.debug_tuple(\"Iter\").field(&self.queue).finish()\n 487 | }\n 488 | }","lines":5,"link":"https://github.com/smol-rs/concurrent-queue/blob/f41c20d8b9424eff6c227dead7b062e3b87142bc/src/lib.rs#L484-L488"}]}
]</script>
<script type="application/json" id="PI">{"name":"concurrent-queue","git_url":"https://github.com/smol-rs/concurrent-queue.git","repo_url":"https://github.com/smol-rs/concurrent-queue","commit":"f41c20d8b9424eff6c227dead7b062e3b87142bc","commit_short":"f41c20d8b9","commit_url":"https://github.com/smol-rs/concurrent-queue/commit/f41c20d8b9424eff6c227dead7b062e3b87142bc","branch":"master","generated":"2026-09-09","description":"Concurrent multi-producer multi-consumer queue"}</script>
<div class="app">
<div class="top">
<div>
<h1>Code Audit Report — concurrent-queue</h1>
<div class="meta">Generated 2026-09-09 — <span id="stats"></span></div>
</div>
<div class="top-right">
<div class="search-wrap">
<input type="search" id="search" placeholder="Search findings…" autocomplete="off">
<button id="search-scope" class="action-btn" type="button" title="Toggle: search everywhere vs. only in file/folder paths">📁 Paths only</button>
</div>
<label class="group-label">Group by
<select id="groupby">
<option value="type">Type</option>
<option value="severity">Severity</option>
<option value="file">File</option>
<option value="none">None (flat)</option>
</select>
</label>
</div>
</div>
<div class="project-info"><div class="pi-name">concurrent-queue</div><div class="pi-desc">Concurrent multi-producer multi-consumer queue</div><div class="pi-row"><a class="pi-repo-btn" href="https://github.com/smol-rs/concurrent-queue" target="_blank" rel="noopener">🔗 https://github.com/smol-rs/concurrent-queue</a><span><span class="pi-k">branch</span> <code>master</code></span><span><span class="pi-k">commit</span> <a href="https://github.com/smol-rs/concurrent-queue/commit/f41c20d8b9424eff6c227dead7b062e3b87142bc" target="_blank" rel="noopener"><code>f41c20d8b9</code></a></span></div></div>
<div class="filterbar" id="filterbar"></div>
<div id="progress-wrap">
<div id="progress-bar"><div id="progress-fill"></div></div>
<span id="progress-text"></span>
<button class="action-btn" data-action="collapse-all">⊟ Collapse all</button>
<button class="action-btn" data-action="expand-all">⊞ Expand all</button>
<button class="action-btn" data-action="download-md">⬇ MD</button>
<button class="action-btn" data-action="download-md-short">⬇ MD Short</button>
<button class="action-btn" data-action="download-toml">⬇ TOML</button>
</div>
<div id="hidden-bar"></div>
<div id="content"></div>
</div>
<script>
(function(){
'use strict';
// Data
const FINDINGS = JSON.parse(document.getElementById('D').textContent);
const PROJECT_INFO = JSON.parse(document.getElementById('PI').textContent);
const SEVS = ['critical','high','medium','low','negligible'];
const STATUSES = ['open','fixed','wont-fix','false-positive'];
const ALL_TYPES = [...new Set(FINDINGS.map(f => f.type))].sort();
// Build lookup map for O(1) access
const FINDING_BY_ID = new Map(FINDINGS.map(f => [f.id, f]));
// Only severities actually present in the report — the filter bar hides the rest.
const PRESENT_SEVS = SEVS.filter(s => FINDINGS.some(f => (f.severity || 'medium') === s));
// Whether any finding is flagged easy — the "Quick wins" filter row is hidden otherwise.
const DIFFICULTY_ORDER = ['very_easy','easy','medium','hard'];
const DIFFICULTY_LBL = {very_easy:'⚡ Very Easy', easy:'Easy', medium:'Medium', hard:'Hard'};
const DIFFICULTY_CLS = {very_easy:'diff-very-easy', easy:'diff-easy', medium:'diff-medium', hard:'diff-hard'};
const PRESENT_DIFFS = DIFFICULTY_ORDER.filter(d => FINDINGS.some(f => f.difficulty === d));
const HAS_DIFFICULTY = PRESENT_DIFFS.length > 0;
const STORE_KEY = 'audit-status:' + location.href;
const STATUS_CLS = {open:'st-open', fixed:'st-fixed', 'wont-fix':'st-wont', 'false-positive':'st-fp'};
const STATUS_LBL = {open:'● open', fixed:'✓ fixed', 'wont-fix':"— won't fix", 'false-positive':'✗ false pos.'};
const STATUS_NEXT = {open:'fixed', fixed:'wont-fix', 'wont-fix':'false-positive', 'false-positive':'open'};
// State
const S = {
search: '', searchScope: 'all', groupBy: 'type',
hiddenSev: new Set(), hiddenType: new Set(),
hiddenStatus: new Set(), hiddenDiff: new Set(), hiddenGroup: new Set(),
collapsed: new Set(),
};
const statuses = {};
// Persistence
function loadStatuses() {
try { Object.assign(statuses, JSON.parse(localStorage.getItem(STORE_KEY) || '{}')); } catch {}
}
function saveStatuses() {
try { localStorage.setItem(STORE_KEY, JSON.stringify(statuses)); } catch {}
}
function statusOf(id) { return statuses[id] || 'open'; }
// URL hash — debounced so rapid filter changes don't thrash history
let _hashTimer = 0;
function syncHash() {
clearTimeout(_hashTimer);
_hashTimer = setTimeout(() => {
const p = new URLSearchParams();
if (S.groupBy !== 'type') p.set('g', S.groupBy);
if (S.hiddenSev.size) p.set('hs', [...S.hiddenSev].join(','));
if (S.hiddenType.size) p.set('ht', [...S.hiddenType].join(','));
if (S.hiddenStatus.size) p.set('hst', [...S.hiddenStatus].join(','));
if (S.hiddenDiff.size) p.set('hd', [...S.hiddenDiff].join(','));
if (S.search) p.set('q', S.search);
if (S.searchScope !== 'all') p.set('qs', S.searchScope);
const str = p.toString();
history.replaceState(null, '', str ? '#' + str : location.pathname + location.search);
}, 300);
}
function loadHash() {
const p = new URLSearchParams(location.hash.slice(1));
if (p.has('g')) { S.groupBy = p.get('g'); document.getElementById('groupby').value = S.groupBy; }
if (p.has('q')) { S.search = p.get('q'); document.getElementById('search').value = S.search; }
if (p.has('qs')) { S.searchScope = p.get('qs'); }
document.getElementById('search-scope').classList.toggle('active', S.searchScope === 'locations');
if (p.has('hs')) p.get('hs') .split(',').filter(Boolean).forEach(v => S.hiddenSev.add(v));
if (p.has('ht')) p.get('ht') .split(',').filter(Boolean).forEach(v => S.hiddenType.add(v));
if (p.has('hst')) p.get('hst').split(',').filter(Boolean).forEach(v => S.hiddenStatus.add(v));
if (p.has('hd')) p.get('hd') .split(',').filter(Boolean).forEach(v => S.hiddenDiff.add(v));
}
// Helpers
function esc(s) {
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
}
function highlight(text, q) {
if (!q) return esc(text);
const re = new RegExp('(' + q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&') + ')', 'gi');
return String(text).split(re).map((p, i) => i % 2 ? '<mark>' + esc(p) + '</mark>' : esc(p)).join('');
}
// Render text with ```fenced``` code blocks and `inline` backticks,
// also paragraph-splitting on blank lines. Output is HTML-safe.
function renderMd(text, q) {
const src = String(text == null ? '' : text);
const FENCE = /```[ \t]*([A-Za-z0-9_+-]*)[ \t]*\r?\n([\s\S]*?)```/g;
const parts = [];
let last = 0, m;
while ((m = FENCE.exec(src)) !== null) {
parts.push({kind: 'text', value: src.slice(last, m.index)});
parts.push({kind: 'code', value: m[2].replace(/\r?\n$/, '')});
last = m.index + m[0].length;
}
parts.push({kind: 'text', value: src.slice(last)});
function renderInline(s) {
const re = /`([^`\n]+)`/g;
let out = '', i = 0, mm;
while ((mm = re.exec(s)) !== null) {
out += highlight(s.slice(i, mm.index), q);
out += '<code class="md-inline">' + highlight(mm[1], q) + '</code>';
i = mm.index + mm[0].length;
}
out += highlight(s.slice(i), q);
return out;
}
function renderTextChunk(s) {
const paragraphs = s.split(/\r?\n[ \t]*\r?\n/);
const out = [];
for (const p of paragraphs) {
const trimmed = p.replace(/^\s+|\s+$/g, '');
if (!trimmed) continue;
out.push('<p>' + renderInline(trimmed).replace(/\r?\n/g, '<br>') + '</p>');
}
return out.join('');
}
let html = '';
for (const p of parts) {
if (p.kind === 'text') html += renderTextChunk(p.value);
else html += '<pre class="md-code">' + highlight(p.value, q) + '</pre>';
}
return html;
}
function sevCls(s) { return SEVS.includes(s) ? 'sev-'+s : 'sev-negligible'; }
function sevLbl(s) { return s ? s[0].toUpperCase()+s.slice(1) : 'Medium'; }
function typeLabel(t) { return t.replace(/_/g,' ').replace(/\b\w/g, c => c.toUpperCase()); }
// First sentence of text (up to and including the first period).
function firstSentence(text) {
const m = String(text).match(/\.(\s|$)/);
return m ? text.slice(0, text.indexOf(m[0]) + 1) : text;
}
// Render a "**Label:** value" line. GitHub/CommonMark only recognizes a
// ``` fence when it starts at the beginning of a line, so if the value
// itself opens with a fence, put it on its own line instead of right
// after the inline label - otherwise the fence marker gets glued onto
// "**Label:** ```" and never renders as a code block.
function mdField(label, value) {
value = value || '';
return /^```/.test(value.trim())
? '**' + label + ':**\n\n' + value
: '**' + label + ':** ' + value;
}
// Render the "**Locations:**" block. When a location has a commit-pinned
// permalink (GitHub/GitLab), just paste the bare URL on its own line -
// GitHub/GitLab auto-expand a line-range blob link into an inline code
// preview, so re-pasting the snippet as a fenced block would duplicate it.
// Forges without a detected permalink shape (or missing commit/repo info)
// fall back to the fenced ref+snippet so the location is still readable.
function mdLocations(locations) {
if (!locations.length) return '';
let out = '**Locations:**\n\n';
for (const l of locations) {
if (l.link) out += l.link + '\n\n';
else if (l.code != null) out += '```\n' + l.ref + '\n' + l.code + '\n```\n\n';
else out += '- `' + l.ref + '` _(file not found)_\n\n';
}
return out;
}
// Build full MD report for in-browser download.
function buildMdFull() {
const SORD = ['critical','high','medium','low','negligible'];
const today = new Date().toISOString().slice(0, 10);
let out = '# Code Audit Report\n\n_Generated ' + today + '_\n\n';
const typeMap = new Map();
for (const f of FINDINGS) {
if (!typeMap.has(f.type)) typeMap.set(f.type, []);
typeMap.get(f.type).push(f);
}
const sortedTypes = [...typeMap.keys()].sort();
out += '## Summary\n\n| Type | Severity | Count |\n|------|----------|-------|\n';
for (const t of sortedTypes) {
const items = typeMap.get(t);
const topSev = items.map(f => f.severity || 'medium').reduce((best, s) => {
const bi = SORD.indexOf(best), si = SORD.indexOf(s);
return (si >= 0 && (bi < 0 || si < bi)) ? s : best;
}, 'negligible');
out += '| ' + typeLabel(t) + ' | ' + topSev + ' | ' + items.length + ' |\n';
}
for (const t of sortedTypes) {
out += '\n## ' + typeLabel(t) + '\n\n';
for (const f of typeMap.get(t)) {
const sevStr = f.severity ? ' `' + f.severity.toUpperCase() + '`' : '';
out += '### ' + f.id + sevStr + '\n\n';
out += mdField('Description', f.description) + '\n\n';
out += mdLocations(f.locations);
out += mdField('Fix', f.fix) + '\n\n---\n\n';
}
}
return out;
}
// Build MD short checklist for in-browser download.
function buildMdShort() {
const lines = [];
const ids = [];
const sorted = [...FINDINGS].sort((a, b) => a.type.localeCompare(b.type));
for (const f of sorted) {
ids.push(f.id);
const desc = firstSentence(f.description || '');
const locs = f.locations.map(l => l.ref);
const locStr = locs.length ? ' - [' + locs.join(', ') + ']' : '';
const sevStr = f.severity ? ' [' + f.severity.toUpperCase() + ']' : '';
lines.push('- [ ] - ' + f.id + sevStr + ' - ' + desc + locStr);
}
lines.push('', '## IDs only', '');
ids.forEach(id => lines.push('- [ ] - ' + id));
return lines.join('\n') + '\n';
}
function downloadText(text, filename, mime) {
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([text], {type: mime || 'text/plain'}));
a.download = filename;
a.click();
setTimeout(() => URL.revokeObjectURL(a.href), 1000);
}
function tomlStr(s) {
return '"' + String(s == null ? '' : s)
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t') + '"';
}
// Build a [project] header block from embedded PROJECT_INFO, so the exported
// findings.toml is self-contained (doesn't depend on a sibling project-info.toml
// that a second, separately-triggered download could silently fail to save).
// '' when there is no project info at all.
function buildProjectToml() {
const ORDER = ['name','description','git_url','repo_url','commit','commit_short','commit_url','branch','generated'];
const pi = PROJECT_INFO || {};
if (!Object.keys(pi).length) return '';
const lines = ['[project]'];
for (const k of ORDER) {
if (k in pi) lines.push(k + ' = ' + tomlStr(pi[k]));
}
return lines.join('\n') + '\n\n';
}
// Build TOML matching the original findings.toml format (re-parseable by toml_to_report.py).
function buildFindingsToml() {
function pad(key) { return (key + ' ').slice(0, 12); }
const chunks = [];
for (const f of FINDINGS) {
const lns = ['[[finding]]'];
lns.push(pad('type') + '= ' + tomlStr(f.type));
lns.push(pad('severity') + '= ' + tomlStr(f.severity || 'medium'));
if (f.difficulty) lns.push(pad('difficulty') + '= ' + tomlStr(f.difficulty));
lns.push(pad('description') + '= ' + tomlStr(f.description || ''));
const refs = f.locations.map(l => tomlStr(l.ref));
lns.push(pad('locations') + '= [' + refs.join(', ') + ']');
lns.push(pad('fix') + '= ' + tomlStr(f.fix || ''));
chunks.push(lns.join('\n'));
}
return buildProjectToml() + chunks.join('\n\n') + '\n';
}
// Build the GitHub-ready Markdown for one finding (mirrors findings.md).
function findingToMd(f) {
let out = '### ' + f.id + (f.severity ? ' `' + f.severity.toUpperCase() + '`' : '') + '\n\n';
out += mdField('Description', f.description) + '\n\n';
out += mdLocations(f.locations);
out += mdField('Fix', f.fix) + '\n';
return out;
}
function flashBtn(btn, msg) {
btn.textContent = msg;
setTimeout(() => { btn.textContent = '⧉ Copy'; }, 1200);
}
function copyText(text, btn) {
const ok = () => flashBtn(btn, '✓ Copied');
const no = () => flashBtn(btn, '✗ Failed');
const fallback = () => {
try {
const ta = document.createElement('textarea');
ta.value = text; ta.style.position = 'fixed'; ta.style.top = '-9999px';
document.body.appendChild(ta); ta.select();
const r = document.execCommand('copy');
document.body.removeChild(ta);
r ? ok() : no();
} catch { no(); }
};
if (navigator.clipboard && navigator.clipboard.writeText)
navigator.clipboard.writeText(text).then(ok, fallback);
else fallback();
}
// Filter logic
function getKey(f) {
if (S.groupBy === 'severity') return f.severity || 'medium';
if (S.groupBy === 'file') return f.locations.length ? f.locations[0].ref.replace(/:.*/,'') : '(no location)';
if (S.groupBy === 'none') return '';
return f.type_label;
}
// Pre-compute search haystacks per finding (cleared on full re-render).
// Two variants: everything (default), and file/folder paths only - for when a
// search term (e.g. a directory name like 'dlls/abc') also happens to appear
// inside description/fix prose and you only want path matches.
const _hay = new Map();
const _hayLoc = new Map();
function hayOf(f) {
if (!_hay.has(f.id)) {
_hay.set(f.id, [f.id, f.type_label, f.severity, f.description, f.fix,
f.difficulty ? (DIFFICULTY_LBL[f.difficulty] || f.difficulty) : '',
...f.locations.map(l => l.ref)].join(' ').toLowerCase());
}
return _hay.get(f.id);
}
function hayLocOf(f) {
if (!_hayLoc.has(f.id)) {
_hayLoc.set(f.id, f.locations.map(l => l.ref).join(' ').toLowerCase());
}
return _hayLoc.get(f.id);
}
function matches(f) {
if (S.hiddenDiff.size > 0 && f.difficulty && S.hiddenDiff.has(f.difficulty)) return false;
if (S.hiddenSev.has(f.severity || 'medium')) return false;
if (S.hiddenType.has(f.type)) return false;
const st = statusOf(f.id);
if (S.hiddenStatus.has(st)) return false;
if (S.search) {
const q = S.search.toLowerCase();
if (S.searchScope === 'locations') {
if (!hayLocOf(f).includes(q)) return false;
} else if (!hayOf(f).includes(q) && !st.includes(q)) {
return false;
}
}
return true;
}
function sortKeys(keys) {
if (S.groupBy === 'severity') {
return [...keys].sort((a, b) => {
const ai = SEVS.indexOf(a), bi = SEVS.indexOf(b);
return (ai < 0 ? 99 : ai) - (bi < 0 ? 99 : bi);
});
}
return [...keys].sort();
}
// Build body HTML for an expanded card (called lazily on first expand).
function renderCardBody(f, q) {
let locs = '';
for (const l of f.locations) {
const lineInfo = l.lines != null
? ' <span class="loc-lines">(' + l.lines + ' line' + (l.lines === 1 ? '' : 's') + ')</span>'
: '';
const refHtml = l.link
? '<a class="loc-link" href="' + esc(l.link) + '" target="_blank" rel="noopener" title="Open at this commit">' + highlight(l.ref, q) + ' ↗</a>'
: highlight(l.ref, q);
locs += '<div class="loc-block"><div class="loc-ref">' + refHtml + lineInfo + '</div>';
locs += l.code != null ? '<pre>' + esc(l.code) + '</pre>' : '<div class="not-found">(file not found)</div>';
locs += '</div>';
}
return (
'<div class="field-lbl">Description</div>' +
'<div class="desc">' + renderMd(f.description, q) + '</div>' +
(locs ? '<div class="field-lbl">Locations</div>' + locs : '') +
'<div class="field-lbl">Fix</div>' +
'<div class="fix-txt">' + renderMd(f.fix, q) + '</div>'
);
}
// Render card shell only — header + empty or populated body.
// When collapsed the body is empty (display:none) — avoids renderMd for 1800+ cards.
function renderCard(f, q) {
const sc = sevCls(f.severity || 'medium');
const sl = sevLbl(f.severity || 'medium');
const st = statusOf(f.id);
const sc2 = STATUS_CLS[st] || 'st-open';
const sl2 = STATUS_LBL[st] || '● open';
const isCollapsed = S.collapsed.has(f.id);
const isResolved = st !== 'open';
const locSummary = f.locations.length
? f.locations[0].ref + (f.locations.length > 1 ? ' \u2026' : '')
: '';
const bodyInner = isCollapsed ? '' : renderCardBody(f, q);
return (
'<div class="card' + (isResolved ? ' resolved' : '') + '" data-card-id="' + esc(f.id) + '">' +
'<div class="card-header" data-action="toggle-card" data-id="' + esc(f.id) + '">' +
'<span class="card-arrow">' + (isCollapsed ? '\u25b6' : '\u25bc') + '</span>' +
'<span class="card-id">' + esc(f.id) + '</span>' +
'<span class="badge ' + sc + '" style="cursor:default">' + esc(sl) + '</span>' +
'<span class="badge type-badge" style="cursor:default">' + esc(f.type_label) + '</span>' +
(f.difficulty ? '<span class="badge ' + (DIFFICULTY_CLS[f.difficulty]||'') + '" style="cursor:default" title="Difficulty: ' + esc(DIFFICULTY_LBL[f.difficulty]||f.difficulty) + '">' + esc(DIFFICULTY_LBL[f.difficulty]||f.difficulty) + '</span>' : '') +
(locSummary ? '<span class="card-loc" title="' + esc(locSummary) + '">' + esc(locSummary) + '</span>' : '') +
'<button class="status-btn ' + sc2 + '" data-action="cycle-status" data-id="' + esc(f.id) + '">' + sl2 + '</button>' +
'<button class="copy-btn" data-action="copy-md" data-id="' + esc(f.id) + '" title="Copy as Markdown for GitHub">\u29c9 Copy</button>' +
'</div>' +
'<div class="card-body" data-body-id="' + esc(f.id) + '"' + (isCollapsed ? ' style="display:none"' : '') + '>' +
bodyInner +
'</div></div>'
);
}
function initFilterBar() {
const fb = document.getElementById('filterbar');
let h = '<div class="filter-row"><span class="filter-lbl">Severity:</span>';
for (const s of PRESENT_SEVS)
h += '<button class="badge ' + sevCls(s) + '" data-sev="' + s + '">' + sevLbl(s) + '</button>';
h += '<button class="filter-toggle" data-toggle-all="sev"></button></div>';
h += '<div class="filter-row"><span class="filter-lbl">Type:</span>';
for (const t of ALL_TYPES)
h += '<button class="badge type-badge" data-type="' + esc(t) + '">' + esc(typeLabel(t)) + '</button>';
h += '<button class="filter-toggle" data-toggle-all="type"></button></div>';
h += '<div class="filter-row"><span class="filter-lbl">Status:</span>';
for (const st of STATUSES)
h += '<button class="badge ' + (STATUS_CLS[st]||'st-open') + '" data-status="' + st + '">' + (STATUS_LBL[st]||st) + '</button>';
h += '<button class="filter-toggle" data-toggle-all="status"></button></div>';
if (HAS_DIFFICULTY) {
h += '<div class="filter-row"><span class="filter-lbl">Difficulty:</span>';
for (const d of PRESENT_DIFFS)
h += '<button class="badge ' + (DIFFICULTY_CLS[d]||'') + '" data-diff="' + d + '">' + esc(DIFFICULTY_LBL[d]||d) + '</button>';
h += '<button class="filter-toggle" data-toggle-all="diff"></button></div>';
}
fb.innerHTML = h;
}
function updateToggles() {
for (const b of document.querySelectorAll('[data-sev]')) b.classList.toggle('off', S.hiddenSev.has(b.dataset.sev));
for (const b of document.querySelectorAll('[data-type]')) b.classList.toggle('off', S.hiddenType.has(b.dataset.type));
for (const b of document.querySelectorAll('[data-status]')) b.classList.toggle('off', S.hiddenStatus.has(b.dataset.status));
for (const b of document.querySelectorAll('[data-diff]')) b.classList.toggle('off', S.hiddenDiff.has(b.dataset.diff));
for (const b of document.querySelectorAll('[data-toggle-all]')) {
const k = b.dataset.toggleAll;
const set = k === 'sev' ? S.hiddenSev : k === 'type' ? S.hiddenType : k === 'status' ? S.hiddenStatus : k === 'diff' ? S.hiddenDiff : null;
if (set) b.textContent = set.size === 0 ? 'Hide all' : 'Show all';
}
}
function updateProgress() {
const total = FINDINGS.length;
const done = FINDINGS.filter(f => statusOf(f.id) !== 'open').length;
const pct = total ? Math.round(done / total * 100) : 0;
document.getElementById('progress-fill').style.width = pct + '%';
document.getElementById('progress-text').textContent = done + ' / ' + total + ' resolved (' + pct + '%)';
}
// Surgical toggle: flip one card without touching the rest of the DOM.
function toggleCard(id) {
const wasCollapsed = S.collapsed.has(id);
if (wasCollapsed) S.collapsed.delete(id); else S.collapsed.add(id);
const body = document.querySelector('[data-body-id="' + id + '"]');
const arrow = document.querySelector('[data-card-id="' + id + '"] .card-arrow');
if (!body) { render(); return; }
if (wasCollapsed) {
if (!body.firstChild) {
const f = FINDING_BY_ID.get(id);
if (f) body.innerHTML = renderCardBody(f, S.search);
}
body.style.display = '';
if (arrow) arrow.textContent = '\u25bc';
} else {
body.style.display = 'none';
if (arrow) arrow.textContent = '\u25b6';
}
}
// Surgical status update: update one card's button + resolved class.
function cycleStatus(id) {
statuses[id] = STATUS_NEXT[statusOf(id)] || 'open';
saveStatuses();
const st = statusOf(id);
const btn = document.querySelector('.status-btn[data-id="' + id + '"]');
if (!btn) { render(); return; }
btn.className = 'status-btn ' + (STATUS_CLS[st] || 'st-open');
btn.textContent = STATUS_LBL[st] || '\u25cf open';
const card = document.querySelector('[data-card-id="' + id + '"]');
if (card) card.classList.toggle('resolved', st !== 'open');
updateProgress();
}
// Surgical collapse/expand for a group: toggle all bodies in the group without full re-render.
function collapseGroup(key, collapse) {
FINDINGS.forEach(f => {
if (getKey(f) !== key) return;
if (collapse) S.collapsed.add(f.id); else S.collapsed.delete(f.id);
const body = document.querySelector('[data-body-id="' + f.id + '"]');
const arrow = document.querySelector('[data-card-id="' + f.id + '"] .card-arrow');
if (!body) return;
if (collapse) {
body.style.display = 'none';
if (arrow) arrow.textContent = '\u25b6';
} else {
if (!body.firstChild) {
const ff = FINDING_BY_ID.get(f.id);
if (ff) body.innerHTML = renderCardBody(ff, S.search);
}
body.style.display = '';
if (arrow) arrow.textContent = '\u25bc';
}
});
}
// Full re-render: used for filter/group-by/search changes.
// Batched via requestAnimationFrame.
let _rafPending = false;
function scheduleRender() {
if (_rafPending) return;
_rafPending = true;
requestAnimationFrame(() => { _rafPending = false; render(); });
}
function render() {
_hay.clear();
_hayLoc.clear();
updateToggles();
updateProgress();
const q = S.search;
const visible = FINDINGS.filter(matches);
document.getElementById('stats').textContent = visible.length + ' / ' + FINDINGS.length + ' findings';
// Build groups
const map = new Map();
for (const f of visible) {
const k = getKey(f);
if (!map.has(k)) map.set(k, []);
map.get(k).push(f);
}
// Hidden-groups bar
const hbar = document.getElementById('hidden-bar');
if (S.hiddenGroup.size > 0) {
let hh = '<div class="hidden-inner"><span class="hidden-lbl">Hidden:</span>';
for (const g of [...S.hiddenGroup].sort())
hh += '<button class="show-btn" data-action="show-group" data-key="' + esc(g) + '">' + esc(g || '(all findings)') + '</button>';
hh += '<button class="show-all-btn" data-action="show-all">Show all</button></div>';
hbar.innerHTML = hh;
} else {
hbar.innerHTML = '';
}
// Content
const content = document.getElementById('content');
if (!visible.length) {
content.innerHTML = '<div class="no-results">No findings match the current filters.</div>';
syncHash();
return;
}
let hh = '';
for (const k of sortKeys([...map.keys()])) {
if (S.hiddenGroup.has(k)) continue;
const items = map.get(k);
const title = k || 'All Findings';
const done = items.filter(f => statusOf(f.id) !== 'open').length;
const allDone = done === items.length;
hh += '<div class="group-section">' +
'<div class="group-header">' +
'<span class="group-title' + (allDone ? ' all-done' : '') + '">' + esc(title) + '</span>' +
'<span class="group-count">' + done + '/' + items.length + '</span>' +
'<button class="icon-btn" data-action="collapse-group" data-key="' + esc(k) + '" title="Collapse all">\u229f</button>' +
'<button class="icon-btn" data-action="expand-group" data-key="' + esc(k) + '" title="Expand all">\u229e</button>' +
'<button class="hide-btn" data-action="hide-group" data-key="' + esc(k) + '">Hide</button>' +
'</div>';
for (const f of items) hh += renderCard(f, q);
hh += '</div>';
}
content.innerHTML = hh;
syncHash();
}
// Long-press solo: hold a filter badge 450 ms to isolate that value (hide all others in the group).
let _soloTimer = 0;
let _soloFired = false;
function _applySolo(b) {
_soloFired = true;
const sev = b.dataset.sev, typ = b.dataset.type, sta = b.dataset.status, dif = b.dataset.diff;
if (sev) { S.hiddenSev.clear(); PRESENT_SEVS.filter(v => v !== sev).forEach(v => S.hiddenSev.add(v)); }
else if (typ) { S.hiddenType.clear(); ALL_TYPES.filter(v => v !== typ).forEach(v => S.hiddenType.add(v)); }
else if (sta) { S.hiddenStatus.clear(); STATUSES.filter(v => v !== sta).forEach(v => S.hiddenStatus.add(v)); }
else if (dif) { S.hiddenDiff.clear(); PRESENT_DIFFS.filter(v => v !== dif).forEach(v => S.hiddenDiff.add(v)); }
scheduleRender();
}
const _fb = document.getElementById('filterbar');
_fb.addEventListener('pointerdown', e => {
const b = e.target.closest('[data-sev],[data-type],[data-status],[data-diff]');
if (!b) return;
_soloFired = false;
clearTimeout(_soloTimer);
_soloTimer = setTimeout(() => _applySolo(b), 450);
}, {passive: true});
_fb.addEventListener('pointerup', () => clearTimeout(_soloTimer), {passive: true});
_fb.addEventListener('pointercancel', () => clearTimeout(_soloTimer), {passive: true});
_fb.addEventListener('pointermove', e => {
if (!_soloTimer) return;
if (!e.target.closest('[data-sev],[data-type],[data-status],[data-diff]')) clearTimeout(_soloTimer);
}, {passive: true});
// Event delegation
_fb.addEventListener('click', e => {
if (_soloFired) { _soloFired = false; return; }
const b = e.target.closest('[data-sev],[data-type],[data-status],[data-toggle-all],[data-diff]');
if (!b) return;
if (b.dataset.toggleAll) {
const k = b.dataset.toggleAll;
const set = k === 'sev' ? S.hiddenSev : k === 'type' ? S.hiddenType : k === 'status' ? S.hiddenStatus : k === 'diff' ? S.hiddenDiff : null;
const full = k === 'sev' ? PRESENT_SEVS : k === 'type' ? ALL_TYPES : k === 'status' ? STATUSES : k === 'diff' ? PRESENT_DIFFS : [];
if (!set) return;
if (set.size === 0) full.forEach(v => set.add(v));
else set.clear();
}
else if (b.dataset.sev) { const s = S.hiddenSev; s.has(b.dataset.sev) ? s.delete(b.dataset.sev) : s.add(b.dataset.sev); }
else if (b.dataset.type) { const s = S.hiddenType; s.has(b.dataset.type) ? s.delete(b.dataset.type) : s.add(b.dataset.type); }
else if (b.dataset.status) { const s = S.hiddenStatus; s.has(b.dataset.status) ? s.delete(b.dataset.status) : s.add(b.dataset.status); }
else if (b.dataset.diff) { const s = S.hiddenDiff; s.has(b.dataset.diff) ? s.delete(b.dataset.diff) : s.add(b.dataset.diff); }
scheduleRender();
});
document.addEventListener('click', e => {
const b = e.target.closest('[data-action]');
if (!b) return;
const { action, key, id } = b.dataset;
if (action === 'hide-group') { S.hiddenGroup.add(key); scheduleRender(); }
else if (action === 'show-group') { S.hiddenGroup.delete(key); scheduleRender(); }
else if (action === 'show-all') { S.hiddenGroup.clear(); scheduleRender(); }
else if (action === 'collapse-all') { FINDINGS.forEach(f => S.collapsed.add(f.id)); scheduleRender(); }
else if (action === 'expand-all') { S.collapsed.clear(); scheduleRender(); }
else if (action === 'copy-md') {
e.stopPropagation();
const f = FINDING_BY_ID.get(id);
if (f) copyText(findingToMd(f), b);
}
else if (action === 'download-md') { downloadText(buildMdFull(), 'findings.md'); }
else if (action === 'download-md-short') { downloadText(buildMdShort(), 'findings-short.md'); }
else if (action === 'download-toml') {
// Single download (was two) - a second auto-triggered download is commonly
// blocked by the browser unless the user explicitly allows it, which silently
// drops project-info.toml. Embedding [project] in findings.toml itself avoids
// that failure mode entirely.
downloadText(buildFindingsToml(), 'findings.toml', 'application/toml');
}
else if (action === 'cycle-status') {
e.stopPropagation();
cycleStatus(id);
}
else if (action === 'toggle-card') {
toggleCard(id);
}
else if (action === 'collapse-group') {
collapseGroup(key, true);
}
else if (action === 'expand-group') {
collapseGroup(key, false);
}
});
// Debounced search — 180 ms after last keystroke
let _searchTimer = 0;
document.getElementById('search').addEventListener('input', e => {
S.search = e.target.value;
clearTimeout(_searchTimer);
_searchTimer = setTimeout(scheduleRender, 180);
});
document.getElementById('groupby').addEventListener('change', e => {
S.groupBy = e.target.value;
S.hiddenGroup.clear();
scheduleRender();
});
document.getElementById('search-scope').addEventListener('click', e => {
S.searchScope = S.searchScope === 'locations' ? 'all' : 'locations';
e.currentTarget.classList.toggle('active', S.searchScope === 'locations');
scheduleRender();
});
// Init
loadStatuses();
initFilterBar();
loadHash();
FINDINGS.forEach(f => S.collapsed.add(f.id));
render();
})();
</script>
</body></html>