kubo: CLEAN across all 5 MOADs (520 Go files, hash-based data structures throughout)
This commit is contained in:
parent
bcdca9cb0e
commit
912baaac72
3 changed files with 191 additions and 0 deletions
34
defects/go-libp2p-0001/patch/go-libp2p-0001.patch
Normal file
34
defects/go-libp2p-0001/patch/go-libp2p-0001.patch
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
--- a/p2p/protocol/identify/id.go
|
||||
+++ b/p2p/protocol/identify/id.go
|
||||
@@ -698,22 +698,18 @@ func (ids *idService) getRecordBytes() []byte {
|
||||
|
||||
// diff takes two slices of strings (a and b) and computes which elements were added and removed in b
|
||||
func diff(a, b []protocol.ID) (added, removed []protocol.ID) {
|
||||
- // This is O(n^2), but it's fine because the slices are small.
|
||||
- for _, x := range b {
|
||||
- var found bool
|
||||
- if slices.Contains(a, x) {
|
||||
- found = true
|
||||
- }
|
||||
- if !found {
|
||||
+ aSet := make(map[protocol.ID]struct{}, len(a))
|
||||
+ for _, x := range a {
|
||||
+ aSet[x] = struct{}{}
|
||||
+ }
|
||||
+ bSet := make(map[protocol.ID]struct{}, len(b))
|
||||
+ for _, x := range b {
|
||||
+ bSet[x] = struct{}{}
|
||||
+ if _, ok := aSet[x]; !ok {
|
||||
added = append(added, x)
|
||||
}
|
||||
}
|
||||
for _, x := range a {
|
||||
- var found bool
|
||||
- if slices.Contains(b, x) {
|
||||
- found = true
|
||||
- }
|
||||
- if !found {
|
||||
+ if _, ok := bSet[x]; !ok {
|
||||
removed = append(removed, x)
|
||||
}
|
||||
}
|
||||
128
defects/go-libp2p-0001/test/go_libp2p_0001_test.py
Normal file
128
defects/go-libp2p-0001/test/go_libp2p_0001_test.py
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Unit test for go-libp2p-0001: identify protocol diff() uses
|
||||
slices.Contains inside loop = O(N^2) protocol comparison.
|
||||
Fix: use map[protocol.ID]struct{} for O(1) lookups = O(N) total.
|
||||
|
||||
File: p2p/protocol/identify/id.go
|
||||
Function: diff(a, b []protocol.ID) (added, removed []protocol.ID)
|
||||
"""
|
||||
|
||||
import time
|
||||
import unittest
|
||||
|
||||
|
||||
# --- DEFECTIVE VERSION: O(N^2) ---
|
||||
|
||||
def diff_defective(a, b):
|
||||
"""Original: slices.Contains(a, x) inside for-range b loop."""
|
||||
added = []
|
||||
removed = []
|
||||
for x in b:
|
||||
if x not in a: # linear scan = O(N) per element
|
||||
added.append(x)
|
||||
for x in a:
|
||||
if x not in b: # linear scan = O(N) per element
|
||||
removed.append(x)
|
||||
return added, removed
|
||||
|
||||
|
||||
# --- PATCHED VERSION: O(N) ---
|
||||
|
||||
def diff_patched(a, b):
|
||||
"""Patched: build sets for O(1) lookups."""
|
||||
a_set = set(a)
|
||||
b_set = set(b)
|
||||
added = [x for x in b if x not in a_set]
|
||||
removed = [x for x in a if x not in b_set]
|
||||
return added, removed
|
||||
|
||||
|
||||
class TestDiffCorrectness(unittest.TestCase):
|
||||
"""Verify patched version matches defective version output."""
|
||||
|
||||
def _check(self, a, b, want_add, want_rem):
|
||||
add_d, rem_d = diff_defective(a, b)
|
||||
add_p, rem_p = diff_patched(a, b)
|
||||
self.assertEqual(add_d, add_p, "added mismatch between versions")
|
||||
self.assertEqual(rem_d, rem_p, "removed mismatch between versions")
|
||||
self.assertEqual(add_p, want_add)
|
||||
self.assertEqual(rem_p, want_rem)
|
||||
|
||||
def test_no_change(self):
|
||||
self._check(
|
||||
["/ipfs/id/1.0.0", "/ipfs/ping/1.0.0"],
|
||||
["/ipfs/id/1.0.0", "/ipfs/ping/1.0.0"],
|
||||
[], []
|
||||
)
|
||||
|
||||
def test_one_added(self):
|
||||
self._check(
|
||||
["/ipfs/id/1.0.0"],
|
||||
["/ipfs/id/1.0.0", "/ipfs/ping/1.0.0"],
|
||||
["/ipfs/ping/1.0.0"], []
|
||||
)
|
||||
|
||||
def test_one_removed(self):
|
||||
self._check(
|
||||
["/ipfs/id/1.0.0", "/ipfs/ping/1.0.0"],
|
||||
["/ipfs/id/1.0.0"],
|
||||
[], ["/ipfs/ping/1.0.0"]
|
||||
)
|
||||
|
||||
def test_both_added_and_removed(self):
|
||||
self._check(
|
||||
["/proto/a", "/proto/b"],
|
||||
["/proto/b", "/proto/c"],
|
||||
["/proto/c"], ["/proto/a"]
|
||||
)
|
||||
|
||||
def test_empty_to_many(self):
|
||||
self._check(
|
||||
[],
|
||||
["/p/1", "/p/2", "/p/3"],
|
||||
["/p/1", "/p/2", "/p/3"], []
|
||||
)
|
||||
|
||||
def test_many_to_empty(self):
|
||||
self._check(
|
||||
["/p/1", "/p/2", "/p/3"],
|
||||
[],
|
||||
[], ["/p/1", "/p/2", "/p/3"]
|
||||
)
|
||||
|
||||
|
||||
class TestDiffPerformance(unittest.TestCase):
|
||||
"""Benchmark: patched should be significantly faster at scale."""
|
||||
|
||||
def test_performance_scaling(self):
|
||||
sizes = [10, 100, 500, 1000]
|
||||
for n in sizes:
|
||||
# Build two protocol lists with ~50% overlap
|
||||
a = [f"/proto/{i}" for i in range(n)]
|
||||
b = [f"/proto/{i}" for i in range(n // 2, n + n // 2)]
|
||||
|
||||
iters = 200
|
||||
|
||||
# Benchmark defective (list-based O(N^2))
|
||||
start = time.perf_counter()
|
||||
for _ in range(iters):
|
||||
diff_defective(a, b)
|
||||
defective_time = time.perf_counter() - start
|
||||
|
||||
# Benchmark patched (set-based O(N))
|
||||
start = time.perf_counter()
|
||||
for _ in range(iters):
|
||||
diff_patched(a, b)
|
||||
patched_time = time.perf_counter() - start
|
||||
|
||||
ratio = defective_time / patched_time if patched_time > 0 else float('inf')
|
||||
print(f" N={n:4d} defective={defective_time:.4f}s patched={patched_time:.4f}s ratio={ratio:.1f}x")
|
||||
|
||||
if n >= 100:
|
||||
self.assertGreater(ratio, 2.0,
|
||||
f"N={n}: expected patched to be at least 2x faster, got {ratio:.1f}x")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
29
defects/kubo/CLEAN
Normal file
29
defects/kubo/CLEAN
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
kubo (Go IPFS implementation) — CLEAN across all 5 MOADs
|
||||
|
||||
Scanned: 2026-03-31
|
||||
Source: ~/git/kubo (depth=1 clone from https://github.com/ipfs/kubo)
|
||||
Files: 520 Go source files
|
||||
|
||||
MOAD-0001 (CWE-407): CLEAN
|
||||
- cid.Set (hash-based) used for GC mark set, pin dedup, refs visited
|
||||
- map[string]struct{} used for bootstrap dedup, address filter dedup, announce dedup
|
||||
- map[string]int used for refs seen tracking
|
||||
- slices.Contains only on fixed-size config lists (5 method names, 4 container inits)
|
||||
- Nested loop in filtersRemove (swarm.go) is one-shot CLI config command, trivially small N
|
||||
|
||||
MOAD-0002 (Intertangle): No patchable defect
|
||||
- IpfsNode is a large god object (30+ fields) but architectural, uses fx DI
|
||||
|
||||
MOAD-0003 (Leaked Context): CLEAN
|
||||
- No context.WithValue abuse found
|
||||
- Uses fx dependency injection for component wiring
|
||||
|
||||
MOAD-0004 (Logged Secret): CLEAN
|
||||
- Swarm key fingerprint (hash) logged, not key itself
|
||||
- API keys passed but never logged
|
||||
- HTTPHeaders logged contain only CORS config
|
||||
|
||||
MOAD-0005 (Thundering Herd): CLEAN
|
||||
- sync.Once for autoconf client singleton
|
||||
- fx dependency injection for initialization
|
||||
- No unsynchronized cache patterns
|
||||
Loading…
Add table
Add a link
Reference in a new issue