github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/kconfig/minimize.go (about) 1 // Copyright 2020 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 kconfig 5 6 import ( 7 "fmt" 8 "sort" 9 10 "github.com/google/syzkaller/pkg/bisect/minimize" 11 "github.com/google/syzkaller/pkg/debugtracer" 12 ) 13 14 // Minimize finds an equivalent with respect to the provided predicate, but smaller config. 15 // It accepts base (small) and full (large) config. It is assumed that the predicate returns true for the full config. 16 // It is also assumed that base and full are not just two completely arbitrary configs, but full it produced from base 17 // mostly by adding more configs. The minimization procedure thus consists of figuring out what set of configs that 18 // are present in full and are not present in base affect the predicate. 19 // If maxPredRuns is non-zero, minimization will stop after the specified number of runs. 20 func (kconf *KConfig) Minimize(base, full *ConfigFile, pred func(*ConfigFile) (bool, error), 21 maxSteps int, dt debugtracer.DebugTracer) (*ConfigFile, error) { 22 diff, other := kconf.missingLeafConfigs(base, full) 23 dt.Log("kconfig minimization: base=%v full=%v leaves diff=%v", len(base.Configs), len(full.Configs), len(diff)) 24 25 diffToConfig := func(part []string) (*ConfigFile, []string) { 26 if len(part) == 0 { 27 // We're testing the baseline config only. 28 return base, nil 29 } 30 suspects := kconf.addDependencies(base, full, part) 31 candidate := base.Clone() 32 // Always move all non-tristate configs from full to base as we don't minimize them. 33 for _, cfg := range other { 34 candidate.Set(cfg.Name, cfg.Value) 35 } 36 for _, cfg := range suspects { 37 candidate.Set(cfg, Yes) 38 } 39 return candidate, suspects 40 } 41 var step int 42 minimizePred := func(diffs []string) (bool, error) { 43 step++ 44 config, _ := diffToConfig(diffs) 45 dt.SaveFile(fmt.Sprintf("step_%d.config", step), config.Serialize()) 46 return pred(config) 47 } 48 result, err := minimize.Slice( 49 minimize.Config[string]{ 50 Pred: minimizePred, 51 MaxSteps: maxSteps, 52 Logf: dt.Log, 53 }, 54 diff, 55 ) 56 if err != nil { 57 return nil, err 58 } 59 config, suspects := diffToConfig(result) 60 if suspects != nil { 61 dt.Log("minimized to %d configs; suspects: %v", len(result), suspects) 62 kconf.writeSuspects(dt, suspects) 63 } 64 return config, nil 65 } 66 67 func (kconf *KConfig) missingConfigs(base, full *ConfigFile) (tristate []string, other []*Config) { 68 for _, cfg := range full.Configs { 69 if cfg.Value == Yes && base.Value(cfg.Name) == No { 70 tristate = append(tristate, cfg.Name) 71 } else if cfg.Value != No && cfg.Value != Yes && cfg.Value != Mod { 72 other = append(other, cfg) 73 } 74 } 75 sort.Strings(tristate) 76 return 77 } 78 79 // missingLeafConfigs returns the set of configs no other config depends upon. 80 func (kconf *KConfig) missingLeafConfigs(base, full *ConfigFile) ([]string, []*Config) { 81 diff, other := kconf.missingConfigs(base, full) 82 needed := map[string]bool{} 83 for _, config := range diff { 84 for _, needs := range kconf.addDependencies(base, full, []string{config}) { 85 if needs != config { 86 needed[needs] = true 87 } 88 } 89 } 90 var leaves []string 91 for _, key := range diff { 92 if !needed[key] { 93 leaves = append(leaves, key) 94 } 95 } 96 return leaves, other 97 } 98 99 func (kconf *KConfig) addDependencies(base, full *ConfigFile, configs []string) []string { 100 closure := make(map[string]bool) 101 for _, cfg := range configs { 102 closure[cfg] = true 103 if m := kconf.Configs[cfg]; m != nil { 104 for dep := range m.DependsOn() { 105 if full.Value(dep) != No && base.Value(dep) == No { 106 closure[dep] = true 107 } 108 } 109 } 110 } 111 var sorted []string 112 for cfg := range closure { 113 sorted = append(sorted, cfg) 114 } 115 sort.Strings(sorted) 116 return sorted 117 } 118 119 const CauseConfigFile = "cause.config" 120 121 func (kconf *KConfig) writeSuspects(dt debugtracer.DebugTracer, suspects []string) { 122 cf := &ConfigFile{ 123 Map: make(map[string]*Config), 124 } 125 for _, cfg := range suspects { 126 cf.Set(cfg, Yes) 127 } 128 dt.SaveFile(CauseConfigFile, cf.Serialize()) 129 }