code-intelligence.com/cifuzz@v0.40.0/internal/cmdutils/resolve/resolve.go (about)

     1  package resolve
     2  
     3  import (
     4  	"fmt"
     5  	"io/fs"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"regexp"
    10  	"runtime"
    11  	"strings"
    12  
    13  	"github.com/mattn/go-zglob"
    14  	"github.com/pkg/errors"
    15  
    16  	"code-intelligence.com/cifuzz/internal/cmdutils"
    17  	"code-intelligence.com/cifuzz/internal/config"
    18  	"code-intelligence.com/cifuzz/util/regexutil"
    19  )
    20  
    21  // TODO: use file info of cmake instead of this regex
    22  var cmakeFuzzTestFileNamePattern = regexp.MustCompile(`add_fuzz_test\((?P<fuzzTest>[a-zA-Z0-9_.+=,@~-]+)\s(?P<file>[a-zA-Z0-9_.+=,@~-]+)\)`)
    23  
    24  // resolve determines the corresponding fuzz test name to a given source file.
    25  // The path has to be relative to the project directory.
    26  func resolve(path, buildSystem, projectDir string) (string, error) {
    27  	errNoFuzzTest := errors.New("no fuzz test found")
    28  
    29  	switch buildSystem {
    30  	case config.BuildSystemCMake:
    31  		cmakeLists, err := findAllCMakeLists(projectDir)
    32  		if err != nil {
    33  			return "", err
    34  		}
    35  
    36  		for _, list := range cmakeLists {
    37  			var bs []byte
    38  			bs, err = os.ReadFile(filepath.Join(projectDir, list))
    39  			if err != nil {
    40  				return "", errors.WithStack(err)
    41  			}
    42  
    43  			if !strings.Contains(string(bs), "add_fuzz_test") {
    44  				continue
    45  			}
    46  
    47  			matches, _ := regexutil.FindAllNamedGroupsMatches(cmakeFuzzTestFileNamePattern, string(bs))
    48  			for _, match := range matches {
    49  				if (filepath.IsAbs(path) && filepath.Join(projectDir, filepath.Dir(list), match["file"]) == path) ||
    50  					filepath.Join(filepath.Dir(list), match["file"]) == path {
    51  					return match["fuzzTest"], nil
    52  				}
    53  			}
    54  		}
    55  		return "", errNoFuzzTest
    56  
    57  	case config.BuildSystemBazel:
    58  		var err error
    59  		if filepath.IsAbs(path) {
    60  			path, err = filepath.Rel(projectDir, path)
    61  			if err != nil {
    62  				return "", errors.WithStack(err)
    63  			}
    64  		}
    65  
    66  		if runtime.GOOS == "windows" {
    67  			// bazel doesn't allow backslashes in its query
    68  			// but it would be unusual for windows users to
    69  			// use slashes when writing a path so we allow
    70  			// backslashes and replace them internally
    71  			path = strings.ReplaceAll(path, "\\", "/")
    72  		}
    73  		arg := fmt.Sprintf(`attr(generator_function, cc_fuzz_test, same_pkg_direct_rdeps(%q))`, path)
    74  		cmd := exec.Command("bazel", "query", arg)
    75  		out, err := cmd.Output()
    76  		if err != nil {
    77  			// if a bazel query fails it is because no target could be found but it would
    78  			// only return "exit status 7" as error which is no useful information for
    79  			// the user, so instead we return the custom error
    80  			return "", errNoFuzzTest
    81  		}
    82  
    83  		fuzzTest := strings.TrimSpace(string(out))
    84  		fuzzTest = strings.TrimSuffix(fuzzTest, "_raw_")
    85  
    86  		return fuzzTest, nil
    87  
    88  	case config.BuildSystemMaven, config.BuildSystemGradle:
    89  		testDir := filepath.Join(projectDir, "src", "test")
    90  		matches, err := zglob.Glob(filepath.Join(testDir, "**", "*.{java,kt}"))
    91  		if err != nil {
    92  			return "", errors.WithStack(err)
    93  		}
    94  
    95  		var pathToFile string
    96  		found := false
    97  		for _, match := range matches {
    98  			if (filepath.IsAbs(path) && match == path) ||
    99  				match == filepath.Join(projectDir, path) {
   100  				pathToFile = match
   101  				found = true
   102  			}
   103  
   104  			if !found && runtime.GOOS == "windows" {
   105  				// Try out different slashes under windows to support both formats for user convenience
   106  				// (since zglob.Glob() returns paths with slashes instead of backslashes on windows)
   107  				match = strings.ReplaceAll(match, "/", "\\")
   108  				if (filepath.IsAbs(path) && match == path) ||
   109  					match == filepath.Join(projectDir, path) {
   110  					pathToFile = match
   111  					found = true
   112  				}
   113  			}
   114  		}
   115  		if !found {
   116  			return "", errNoFuzzTest
   117  		}
   118  
   119  		fuzzTest, err := cmdutils.ConstructJVMFuzzTestIdentifier(pathToFile, testDir)
   120  		if err != nil {
   121  			return "", err
   122  		}
   123  		return fuzzTest, nil
   124  
   125  	default:
   126  		return "", errors.New("The flag '--resolve' only supports the following build systems: CMake, Bazel, Maven, Gradle.")
   127  	}
   128  }
   129  
   130  func findAllCMakeLists(projectDir string) ([]string, error) {
   131  	var cmakeLists []string
   132  
   133  	err := filepath.WalkDir(projectDir, func(path string, d fs.DirEntry, err error) error {
   134  		if err != nil {
   135  			return errors.WithStack(err)
   136  		}
   137  
   138  		path, err = filepath.Rel(projectDir, path)
   139  		if err != nil {
   140  			return errors.WithStack(err)
   141  		}
   142  
   143  		baseName := filepath.Base(path)
   144  		if baseName == "CMakeLists.txt" {
   145  			cmakeLists = append(cmakeLists, path)
   146  		}
   147  
   148  		return nil
   149  	})
   150  
   151  	return cmakeLists, errors.WithStack(err)
   152  }
   153  
   154  func FuzzTestArguments(resolveSourceFile bool, args []string, buildSystem, projectDir string) ([]string, error) {
   155  	if resolveSourceFile {
   156  		var fuzzTests []string
   157  		for _, arg := range args {
   158  			fuzzTest, err := resolve(arg, buildSystem, projectDir)
   159  			if err != nil {
   160  				return nil, errors.Wrap(err, fmt.Sprintf("Failed to resolve source file %s", arg))
   161  			}
   162  			fuzzTests = append(fuzzTests, fuzzTest)
   163  		}
   164  		return fuzzTests, nil
   165  	}
   166  
   167  	return args, nil
   168  }