| // Copyright 2026 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| package main | |
| import ( | |
| "bytes" | |
| "context" | |
| "fmt" | |
| "io" | |
| "os" | |
| "path/filepath" | |
| "regexp" | |
| "strings" | |
| "dawn.googlesource.com/dawn/tools/src/glob" | |
| "dawn.googlesource.com/dawn/tools/src/oswrapper" | |
| "dawn.googlesource.com/dawn/tools/src/transform" | |
| ) | |
| // runCorpusGenerator converts a set of input test files into a fuzzer corpus | |
| // The generator will use t.inputs as the source directory. | |
| // The corpus will be written to t.out. | |
| func runCorpusGenerator(t *taskConfig) error { | |
| switch t.fuzzMode { | |
| case FuzzModeWgsl: | |
| return runCorpusGeneratorWgsl(t) | |
| case FuzzModeIr: | |
| return runCorpusGeneratorIr(t) | |
| default: | |
| return fmt.Errorf("unknown fuzzer mode %d", t.fuzzMode) | |
| } | |
| } | |
| // runCorpusGeneratorWgsl converts a set of input test .wgsl files into a WGSL fuzzer corpus. | |
| func runCorpusGeneratorWgsl(t *taskConfig) error { | |
| return gatherWgslFiles(t.inputs, t.out, t.osWrapper) | |
| } | |
| // runCorpusGeneratorIr converts a set of input test .wgsl files into an IR fuzzer corpus. | |
| // It gathers the WGSL files, then forks out to an external binary (t.assembler) to perform the conversion. | |
| func runCorpusGeneratorIr(t *taskConfig) error { | |
| tmp, err := t.osWrapper.MkdirTemp("", "wgsl_corpus_for_ir") | |
| if err != nil { | |
| return fmt.Errorf("failed to create temporary directory for WGSL files: %w", err) | |
| } | |
| defer t.osWrapper.RemoveAll(tmp) | |
| if err := gatherWgslFiles(t.inputs, tmp, t.osWrapper); err != nil { | |
| return fmt.Errorf("failed to gather WGSL files for IR corpus generation: %w", err) | |
| } | |
| ctx, cancel := context.WithCancel(context.Background()) | |
| defer cancel() | |
| args := []string{tmp, t.out} | |
| if t.verbose { | |
| args = append(args, "--verbose") | |
| } | |
| cmdStr := fmt.Sprintf("%s %s", t.assembler, strings.Join(args, " ")) | |
| if t.verbose { | |
| fmt.Println("Using assembler cmd: " + cmdStr) | |
| } | |
| fmt.Println("running assembler") | |
| out := &bytes.Buffer{} | |
| var stdout, stderr io.Writer = out, out | |
| if t.verbose { | |
| stdout = io.MultiWriter(out, os.Stdout) | |
| stderr = io.MultiWriter(out, os.Stderr) | |
| } | |
| cmd := t.execWrapper.CommandContext(ctx, t.assembler, args...).WithStdout(stdout).WithStderr(stderr) | |
| if err := cmd.Run(); err != nil { | |
| return fmt.Errorf("failed to run IR corpus assembler.\n command: %s\n error: %w\n output:\n%s", cmdStr, err, out.String()) | |
| } | |
| fmt.Println("done") | |
| return nil | |
| } | |
| // gatherWgslFiles cleans up and copies all the .wgsl files in a directory structure over to a flat directory | |
| // structure, via replacing the path separators for the origins with underscores in the destination | |
| // file names. It also filters out any '*.expected.*' files. If there are copyright notice blocks in the file contents | |
| // they will be stripped, because they are known to cause issues when decoding fuzzer sidecar data. | |
| func gatherWgslFiles(inputs string, out string, fsReaderWriter oswrapper.FilesystemReaderWriter) error { | |
| fmt.Println("gathering and filtering .wgsl files") | |
| globPattern := filepath.Join(inputs, "**.wgsl") | |
| files, err := glob.Glob(globPattern, fsReaderWriter) | |
| if err != nil { | |
| return fmt.Errorf("failed to find .wgsl files with pattern '%v': %w", globPattern, err) | |
| } | |
| // Remove '*.expected.*' | |
| files = transform.Filter(files, func(s string) bool { return !strings.Contains(s, ".expected.") }) | |
| // Map src file paths to dst filenames where the path separators have been converted to underscores | |
| mapping := make(map[string]string, len(files)) | |
| for _, f := range files { | |
| // paths returned by glob.Glob are absolute, but only want to use the relative path in the dest name | |
| relPath, err := filepath.Rel(inputs, f) | |
| if err != nil { | |
| return fmt.Errorf("failed to calculate relative path for '%v' from base '%v': %w", f, inputs, err) | |
| } | |
| mapping[f] = strings.ReplaceAll(filepath.ToSlash(relPath), "/", "_") | |
| } | |
| reCopyrightNotice := regexp.MustCompile(`(?s)^// Copyright.*?\r?\n// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE\.\r?\n\s*`) | |
| for src, dest := range mapping { | |
| dstPath := filepath.Join(out, dest) | |
| contentBytes, err := fsReaderWriter.ReadFile(src) | |
| if err != nil { | |
| return fmt.Errorf("failed to read '%v': %w", src, err) | |
| } | |
| content := string(contentBytes) | |
| content = reCopyrightNotice.ReplaceAllString(content, "") | |
| if err := fsReaderWriter.MkdirAll(filepath.Dir(dstPath), 0755); err != nil { | |
| return fmt.Errorf("failed to create directory for '%v': %w", dstPath, err) | |
| } | |
| if err := fsReaderWriter.WriteFile(dstPath, []byte(content), 0644); err != nil { | |
| return fmt.Errorf("failed to write '%v': %w", dstPath, err) | |
| } | |
| } | |
| fmt.Println("done") | |
| return nil | |
| } |