smat.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. //go:build gofuzz
  2. // +build gofuzz
  3. /*
  4. # Instructions for smat testing for roaring
  5. [smat](https://github.com/mschoch/smat) is a framework that provides
  6. state machine assisted fuzz testing.
  7. To run the smat tests for roaring...
  8. ## Prerequisites
  9. $ go get github.com/dvyukov/go-fuzz/go-fuzz
  10. $ go get github.com/dvyukov/go-fuzz/go-fuzz-build
  11. ## Steps
  12. 1. Generate initial smat corpus:
  13. ```
  14. go test -tags=gofuzz -run=TestGenerateSmatCorpus
  15. ```
  16. 2. Build go-fuzz test program with instrumentation:
  17. ```
  18. go-fuzz-build -func FuzzSmat github.com/RoaringBitmap/roaring
  19. ```
  20. 3. Run go-fuzz:
  21. ```
  22. go-fuzz -bin=./roaring-fuzz.zip -workdir=workdir/ -timeout=200
  23. ```
  24. You should see output like...
  25. ```
  26. 2016/09/16 13:58:35 slaves: 8, corpus: 1 (3s ago), crashers: 0, restarts: 1/0, execs: 0 (0/sec), cover: 0, uptime: 3s
  27. 2016/09/16 13:58:38 slaves: 8, corpus: 1 (6s ago), crashers: 0, restarts: 1/0, execs: 0 (0/sec), cover: 0, uptime: 6s
  28. 2016/09/16 13:58:41 slaves: 8, corpus: 1 (9s ago), crashers: 0, restarts: 1/44, execs: 44 (5/sec), cover: 0, uptime: 9s
  29. 2016/09/16 13:58:44 slaves: 8, corpus: 1 (12s ago), crashers: 0, restarts: 1/45, execs: 45 (4/sec), cover: 0, uptime: 12s
  30. 2016/09/16 13:58:47 slaves: 8, corpus: 1 (15s ago), crashers: 0, restarts: 1/46, execs: 46 (3/sec), cover: 0, uptime: 15s
  31. 2016/09/16 13:58:50 slaves: 8, corpus: 1 (18s ago), crashers: 0, restarts: 1/47, execs: 47 (3/sec), cover: 0, uptime: 18s
  32. 2016/09/16 13:58:53 slaves: 8, corpus: 1 (21s ago), crashers: 0, restarts: 1/63, execs: 63 (3/sec), cover: 0, uptime: 21s
  33. 2016/09/16 13:58:56 slaves: 8, corpus: 1 (24s ago), crashers: 0, restarts: 1/65, execs: 65 (3/sec), cover: 0, uptime: 24s
  34. 2016/09/16 13:58:59 slaves: 8, corpus: 1 (27s ago), crashers: 0, restarts: 1/66, execs: 66 (2/sec), cover: 0, uptime: 27s
  35. 2016/09/16 13:59:02 slaves: 8, corpus: 1 (30s ago), crashers: 0, restarts: 1/67, execs: 67 (2/sec), cover: 0, uptime: 30s
  36. 2016/09/16 13:59:05 slaves: 8, corpus: 1 (33s ago), crashers: 0, restarts: 1/83, execs: 83 (3/sec), cover: 0, uptime: 33s
  37. 2016/09/16 13:59:08 slaves: 8, corpus: 1 (36s ago), crashers: 0, restarts: 1/84, execs: 84 (2/sec), cover: 0, uptime: 36s
  38. 2016/09/16 13:59:11 slaves: 8, corpus: 2 (0s ago), crashers: 0, restarts: 1/85, execs: 85 (2/sec), cover: 0, uptime: 39s
  39. 2016/09/16 13:59:14 slaves: 8, corpus: 17 (2s ago), crashers: 0, restarts: 1/86, execs: 86 (2/sec), cover: 480, uptime: 42s
  40. 2016/09/16 13:59:17 slaves: 8, corpus: 17 (5s ago), crashers: 0, restarts: 1/66, execs: 132 (3/sec), cover: 487, uptime: 45s
  41. 2016/09/16 13:59:20 slaves: 8, corpus: 17 (8s ago), crashers: 0, restarts: 1/440, execs: 2645 (55/sec), cover: 487, uptime: 48s
  42. ```
  43. Let it run, and if the # of crashers is > 0, check out the reports in
  44. the workdir where you should be able to find the panic goroutine stack
  45. traces.
  46. */
  47. package roaring
  48. import (
  49. "fmt"
  50. "sort"
  51. "github.com/bits-and-blooms/bitset"
  52. "github.com/mschoch/smat"
  53. )
  54. // fuzz test using state machine driven by byte stream.
  55. func FuzzSmat(data []byte) int {
  56. return smat.Fuzz(&smatContext{}, smat.ActionID('S'), smat.ActionID('T'),
  57. smatActionMap, data)
  58. }
  59. var smatDebug = false
  60. func smatLog(prefix, format string, args ...interface{}) {
  61. if smatDebug {
  62. fmt.Print(prefix)
  63. fmt.Printf(format, args...)
  64. }
  65. }
  66. type smatContext struct {
  67. pairs []*smatPair
  68. // Two registers, x & y.
  69. x int
  70. y int
  71. actions int
  72. }
  73. type smatPair struct {
  74. bm *Bitmap
  75. bs *bitset.BitSet
  76. }
  77. // ------------------------------------------------------------------
  78. var smatActionMap = smat.ActionMap{
  79. smat.ActionID('X'): smatAction("x++", smatWrap(func(c *smatContext) { c.x++ })),
  80. smat.ActionID('x'): smatAction("x--", smatWrap(func(c *smatContext) { c.x-- })),
  81. smat.ActionID('Y'): smatAction("y++", smatWrap(func(c *smatContext) { c.y++ })),
  82. smat.ActionID('y'): smatAction("y--", smatWrap(func(c *smatContext) { c.y-- })),
  83. smat.ActionID('*'): smatAction("x*y", smatWrap(func(c *smatContext) { c.x = c.x * c.y })),
  84. smat.ActionID('<'): smatAction("x<<", smatWrap(func(c *smatContext) { c.x = c.x << 1 })),
  85. smat.ActionID('^'): smatAction("swap", smatWrap(func(c *smatContext) { c.x, c.y = c.y, c.x })),
  86. smat.ActionID('['): smatAction(" pushPair", smatWrap(smatPushPair)),
  87. smat.ActionID(']'): smatAction(" popPair", smatWrap(smatPopPair)),
  88. smat.ActionID('B'): smatAction(" setBit", smatWrap(smatSetBit)),
  89. smat.ActionID('b'): smatAction(" removeBit", smatWrap(smatRemoveBit)),
  90. smat.ActionID('o'): smatAction(" or", smatWrap(smatOr)),
  91. smat.ActionID('a'): smatAction(" and", smatWrap(smatAnd)),
  92. smat.ActionID('#'): smatAction(" cardinality", smatWrap(smatCardinality)),
  93. smat.ActionID('O'): smatAction(" orCardinality", smatWrap(smatOrCardinality)),
  94. smat.ActionID('A'): smatAction(" andCardinality", smatWrap(smatAndCardinality)),
  95. smat.ActionID('c'): smatAction(" clear", smatWrap(smatClear)),
  96. smat.ActionID('r'): smatAction(" runOptimize", smatWrap(smatRunOptimize)),
  97. smat.ActionID('e'): smatAction(" isEmpty", smatWrap(smatIsEmpty)),
  98. smat.ActionID('i'): smatAction(" intersects", smatWrap(smatIntersects)),
  99. smat.ActionID('f'): smatAction(" flip", smatWrap(smatFlip)),
  100. smat.ActionID('-'): smatAction(" difference", smatWrap(smatDifference)),
  101. }
  102. var smatRunningPercentActions []smat.PercentAction
  103. func init() {
  104. var ids []int
  105. for actionId := range smatActionMap {
  106. ids = append(ids, int(actionId))
  107. }
  108. sort.Ints(ids)
  109. pct := 100 / len(smatActionMap)
  110. for _, actionId := range ids {
  111. smatRunningPercentActions = append(smatRunningPercentActions,
  112. smat.PercentAction{pct, smat.ActionID(actionId)})
  113. }
  114. smatActionMap[smat.ActionID('S')] = smatAction("SETUP", smatSetupFunc)
  115. smatActionMap[smat.ActionID('T')] = smatAction("TEARDOWN", smatTeardownFunc)
  116. }
  117. // We only have one smat state: running.
  118. func smatRunning(next byte) smat.ActionID {
  119. return smat.PercentExecute(next, smatRunningPercentActions...)
  120. }
  121. func smatAction(name string, f func(ctx smat.Context) (smat.State, error)) func(smat.Context) (smat.State, error) {
  122. return func(ctx smat.Context) (smat.State, error) {
  123. c := ctx.(*smatContext)
  124. c.actions++
  125. smatLog(" ", "%s\n", name)
  126. return f(ctx)
  127. }
  128. }
  129. // Creates an smat action func based on a simple callback.
  130. func smatWrap(cb func(c *smatContext)) func(smat.Context) (next smat.State, err error) {
  131. return func(ctx smat.Context) (next smat.State, err error) {
  132. c := ctx.(*smatContext)
  133. cb(c)
  134. return smatRunning, nil
  135. }
  136. }
  137. // Invokes a callback function with the input v bounded to len(c.pairs).
  138. func (c *smatContext) withPair(v int, cb func(*smatPair)) {
  139. if len(c.pairs) > 0 {
  140. if v < 0 {
  141. v = -v
  142. }
  143. v = v % len(c.pairs)
  144. cb(c.pairs[v])
  145. }
  146. }
  147. // ------------------------------------------------------------------
  148. func smatSetupFunc(ctx smat.Context) (next smat.State, err error) {
  149. return smatRunning, nil
  150. }
  151. func smatTeardownFunc(ctx smat.Context) (next smat.State, err error) {
  152. return nil, err
  153. }
  154. // ------------------------------------------------------------------
  155. func smatPushPair(c *smatContext) {
  156. c.pairs = append(c.pairs, &smatPair{
  157. bm: NewBitmap(),
  158. bs: bitset.New(100),
  159. })
  160. }
  161. func smatPopPair(c *smatContext) {
  162. if len(c.pairs) > 0 {
  163. c.pairs = c.pairs[0 : len(c.pairs)-1]
  164. }
  165. }
  166. func smatSetBit(c *smatContext) {
  167. c.withPair(c.x, func(p *smatPair) {
  168. y := uint32(c.y)
  169. p.bm.AddInt(int(y))
  170. p.bs.Set(uint(y))
  171. p.checkEquals()
  172. })
  173. }
  174. func smatRemoveBit(c *smatContext) {
  175. c.withPair(c.x, func(p *smatPair) {
  176. y := uint32(c.y)
  177. p.bm.Remove(y)
  178. p.bs.Clear(uint(y))
  179. p.checkEquals()
  180. })
  181. }
  182. func smatAnd(c *smatContext) {
  183. c.withPair(c.x, func(px *smatPair) {
  184. c.withPair(c.y, func(py *smatPair) {
  185. px.bm.And(py.bm)
  186. px.bs = px.bs.Intersection(py.bs)
  187. px.checkEquals()
  188. py.checkEquals()
  189. })
  190. })
  191. }
  192. func smatOr(c *smatContext) {
  193. c.withPair(c.x, func(px *smatPair) {
  194. c.withPair(c.y, func(py *smatPair) {
  195. px.bm.Or(py.bm)
  196. px.bs = px.bs.Union(py.bs)
  197. px.checkEquals()
  198. py.checkEquals()
  199. })
  200. })
  201. }
  202. func smatAndCardinality(c *smatContext) {
  203. c.withPair(c.x, func(px *smatPair) {
  204. c.withPair(c.y, func(py *smatPair) {
  205. c0 := px.bm.AndCardinality(py.bm)
  206. c1 := px.bs.IntersectionCardinality(py.bs)
  207. if c0 != uint64(c1) {
  208. panic("expected same add cardinality")
  209. }
  210. px.checkEquals()
  211. py.checkEquals()
  212. })
  213. })
  214. }
  215. func smatOrCardinality(c *smatContext) {
  216. c.withPair(c.x, func(px *smatPair) {
  217. c.withPair(c.y, func(py *smatPair) {
  218. c0 := px.bm.OrCardinality(py.bm)
  219. c1 := px.bs.UnionCardinality(py.bs)
  220. if c0 != uint64(c1) {
  221. panic("expected same or cardinality")
  222. }
  223. px.checkEquals()
  224. py.checkEquals()
  225. })
  226. })
  227. }
  228. func smatRunOptimize(c *smatContext) {
  229. c.withPair(c.x, func(px *smatPair) {
  230. px.bm.RunOptimize()
  231. px.checkEquals()
  232. })
  233. }
  234. func smatClear(c *smatContext) {
  235. c.withPair(c.x, func(px *smatPair) {
  236. px.bm.Clear()
  237. px.bs = px.bs.ClearAll()
  238. px.checkEquals()
  239. })
  240. }
  241. func smatCardinality(c *smatContext) {
  242. c.withPair(c.x, func(px *smatPair) {
  243. c0 := px.bm.GetCardinality()
  244. c1 := px.bs.Count()
  245. if c0 != uint64(c1) {
  246. panic("expected same cardinality")
  247. }
  248. })
  249. }
  250. func smatIsEmpty(c *smatContext) {
  251. c.withPair(c.x, func(px *smatPair) {
  252. c0 := px.bm.IsEmpty()
  253. c1 := px.bs.None()
  254. if c0 != c1 {
  255. panic("expected same is empty")
  256. }
  257. })
  258. }
  259. func smatIntersects(c *smatContext) {
  260. c.withPair(c.x, func(px *smatPair) {
  261. c.withPair(c.y, func(py *smatPair) {
  262. v0 := px.bm.Intersects(py.bm)
  263. v1 := px.bs.IntersectionCardinality(py.bs) > 0
  264. if v0 != v1 {
  265. panic("intersects not equal")
  266. }
  267. px.checkEquals()
  268. py.checkEquals()
  269. })
  270. })
  271. }
  272. func smatFlip(c *smatContext) {
  273. c.withPair(c.x, func(p *smatPair) {
  274. y := uint32(c.y)
  275. p.bm.Flip(uint64(y), uint64(y)+1)
  276. p.bs = p.bs.Flip(uint(y))
  277. p.checkEquals()
  278. })
  279. }
  280. func smatDifference(c *smatContext) {
  281. c.withPair(c.x, func(px *smatPair) {
  282. c.withPair(c.y, func(py *smatPair) {
  283. px.bm.AndNot(py.bm)
  284. px.bs = px.bs.Difference(py.bs)
  285. px.checkEquals()
  286. py.checkEquals()
  287. })
  288. })
  289. }
  290. func (p *smatPair) checkEquals() {
  291. if !p.equalsBitSet(p.bs, p.bm) {
  292. panic("bitset mismatch")
  293. }
  294. }
  295. func (p *smatPair) equalsBitSet(a *bitset.BitSet, b *Bitmap) bool {
  296. for i, e := a.NextSet(0); e; i, e = a.NextSet(i + 1) {
  297. if !b.ContainsInt(int(i)) {
  298. fmt.Printf("in a bitset, not b bitmap, i: %d\n", i)
  299. fmt.Printf(" a bitset: %s\n b bitmap: %s\n",
  300. a.String(), b.String())
  301. return false
  302. }
  303. }
  304. i := b.Iterator()
  305. for i.HasNext() {
  306. v := i.Next()
  307. if !a.Test(uint(v)) {
  308. fmt.Printf("in b bitmap, not a bitset, v: %d\n", v)
  309. fmt.Printf(" a bitset: %s\n b bitmap: %s\n",
  310. a.String(), b.String())
  311. return false
  312. }
  313. }
  314. return true
  315. }