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  }