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  }