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 }