github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/tools/syz-imagegen/combinations.go (about) 1 // Copyright 2025 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package main 5 6 import "sort" 7 8 // CoveringArray returns an array of representative parameter value combinations. 9 // The method considers parameters from the first to the last and first tries to 10 // generate all possible combinations of their values. 11 // If N != 0, the method eventually switches to ensuring that all value pairs 12 // are represented, once all pairs are covered - all triples (aka Covering Array). 13 func CoveringArray(params [][]string, n int) [][]string { 14 var ret [][]int 15 for paramID, param := range params { 16 if len(ret) == 0 { 17 ret = append(ret, []int{}) 18 } 19 // If we can explore all combinations, do it. 20 if len(ret)*len(param) <= n || n == 0 { 21 var newRet [][]int 22 for value := range param { 23 for _, row := range ret { 24 newRet = append(newRet, extendRow(row, value)) 25 } 26 } 27 ret = newRet 28 continue 29 } 30 cover := &pairCoverage{cover: map[pairCombo]struct{}{}} 31 32 // First, select a value for each row. 33 var newRet [][]int 34 for _, row := range ret { 35 bestValue, bestCount := 0, coverDelta{} 36 for valueID := range param { 37 newCount := cover.wouldCover(row, paramID, valueID) 38 if newCount.betterThan(bestCount) { 39 bestValue = valueID 40 bestCount = newCount 41 } 42 } 43 newRet = append(newRet, extendRow(row, bestValue)) 44 cover.record(row, paramID, bestValue) 45 } 46 47 // Now that all previous combinations are preserved, we can (as long as 48 // we don't exceed N) duplicate some of the rows to cover more. 49 for len(newRet) < n { 50 var bestRow []int 51 bestValue, bestCount := 0, coverDelta{} 52 for _, row := range ret { 53 for valueID := range param { 54 newCount := cover.wouldCover(row, paramID, valueID) 55 if newCount.betterThan(bestCount) { 56 bestRow = row 57 bestValue = valueID 58 bestCount = newCount 59 } 60 } 61 } 62 if !bestCount.betterThan(coverDelta{}) { 63 break 64 } 65 newRet = append(newRet, extendRow(bestRow, bestValue)) 66 cover.record(bestRow, paramID, bestValue) 67 } 68 ret = newRet 69 } 70 sort.Slice(ret, func(i, j int) bool { 71 rowA, rowB := ret[i], ret[j] 72 for k := 0; k < len(rowA); k++ { 73 if rowA[k] != rowB[k] { 74 return rowA[k] < rowB[k] 75 } 76 } 77 return false 78 }) 79 var retStrings [][]string 80 for _, row := range ret { 81 var stringRow []string 82 for paramID, valueID := range row { 83 stringRow = append(stringRow, params[paramID][valueID]) 84 } 85 retStrings = append(retStrings, stringRow) 86 } 87 return retStrings 88 } 89 90 type pairCoverage struct { 91 cover map[pairCombo]struct{} 92 } 93 94 type coverDelta struct { 95 pairs int 96 triples int 97 } 98 99 func (c coverDelta) betterThan(other coverDelta) bool { 100 if c.pairs != other.pairs { 101 return c.pairs > other.pairs 102 } 103 return c.triples > other.triples 104 } 105 106 // By how much the coverage would increase if we append newVal to the row. 107 // The first integer is the number of newly covered pairs of values, 108 // the second integer is the number of newly covered triples of values. 109 func (pc *pairCoverage) wouldCover(row []int, newID, newVal int) coverDelta { 110 var pairs, triples int 111 for _, item := range rowToPairCombos(row, false, newID, newVal) { 112 if _, ok := pc.cover[item]; !ok { 113 pairs++ 114 } 115 } 116 for _, item := range rowToPairCombos(row, true, newID, newVal) { 117 if _, ok := pc.cover[item]; !ok { 118 triples++ 119 } 120 } 121 return coverDelta{pairs, triples} 122 } 123 124 func (pc *pairCoverage) record(row []int, newID, newVal int) { 125 for _, item := range append( 126 rowToPairCombos(row, false, newID, newVal), 127 rowToPairCombos(row, true, newID, newVal)...) { 128 pc.cover[item] = struct{}{} 129 } 130 } 131 132 type pair struct { 133 pos int 134 value int 135 } 136 137 type pairCombo struct { 138 first pair 139 second pair 140 third pair 141 } 142 143 func rowToPairCombos(row []int, triples bool, newID, newVal int) []pairCombo { 144 var ret []pairCombo 145 // All things being equal, we want to also favor more different values. 146 ret = append(ret, pairCombo{third: pair{newID + 1, newVal}}) 147 for i := 0; i+1 < len(row); i++ { 148 if !triples { 149 ret = append(ret, pairCombo{ 150 first: pair{i + 1, row[i]}, 151 third: pair{newID + 1, newVal}, 152 }) 153 continue 154 } 155 for j := i + 1; j < len(row); j++ { 156 ret = append(ret, pairCombo{ 157 first: pair{i + 1, row[i]}, 158 second: pair{j + 1, row[j]}, 159 third: pair{newID + 1, newVal}, 160 }) 161 } 162 } 163 return ret 164 } 165 166 func extendRow(row []int, newVal int) []int { 167 return append(append([]int{}, row...), newVal) 168 }