blob: 95657a3357aed1236c32e049c189e479e018892b [file] [edit]
# Domato - generator script for WebGPU
# -------------------------------
#
# Written and maintained by Ivan Fratric <ifratric@google.com>
# Modified by Brendon Tiszka to target webgpu <tiszka@google.com>
#
# Copyright 2017 Google Inc. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import glob
import os
import random
import re
import sys
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
sys.path.append(parent_dir)
from grammar import Grammar
_N_MAIN_LINES = 1000
_N_SHADERS = 10
def extract_shader_stages_and_functions(code):
# Pattern to match both single-line and multiline stage attributes
pattern = r'@(?:compute|vertex|fragment)(?:\s+@[^(\n]+(?:\([^)]*\))?)*\s*(?:\n\s*)?fn\s+(\w+)'
matches = re.finditer(pattern, code)
# Extract stage-function pairs
result = []
for match in matches:
# Find the stage attribute within the matched attributes
stage_attr = re.search(r'@(compute|vertex|fragment)', match.group(0)).group(0)
function_name = match.group(1)
result.append((stage_attr, function_name))
return result
def parse_entrypoints(shaders):
entrypoints = []
for shader in shaders:
attr_fn_pairs = extract_shader_stages_and_functions(shader)
for attr, fn in attr_fn_pairs:
entrypoints.append(fn)
result = ""
for entrypoint in entrypoints:
result += "<entrypoint> = \"{}\"\n".format(entrypoint)
return result
def parse_bindings(shaders):
binding_pattern = r'@binding\((\d+)\)'
binding_numbers = []
for shader in shaders:
matches = re.finditer(binding_pattern, shader)
binding_numbers.extend(match.group(1) for match in matches)
return "\n".join(f"<BindInt> = {binding}" for binding in binding_numbers)
def generate_function_body(webgpugrammar, num_lines):
js = ''
js += webgpugrammar._generate_code(num_lines)
return js
def generate_new_sample(template, webgpugrammar):
result = template
while '<webgpufuzz>' in result:
result = result.replace(
'<webgpufuzz>',
generate_function_body(webgpugrammar, _N_MAIN_LINES),
1
)
return result
def generate_samples(template, grammar_dir, outfiles):
extra = ""
shaders_dir = os.path.join(grammar_dir, "wgsl/*.wgsl")
shader_files = glob.glob(shaders_dir)
shaders = []
for i in range(_N_SHADERS):
shader_path = random.choice(shader_files)
with open(shader_path) as fp:
shader_src = fp.read()
shaders.append(shader_src)
with open(os.path.join(grammar_dir, template), "r") as fp:
template_contents = fp.read()
SHADER_CONST = "<shader%s>"
for i, shader in enumerate(shaders):
shader_template = SHADER_CONST % str(i)
template_contents = template_contents.replace(shader_template, shader)
extra += parse_entrypoints(shaders) + "\n"
extra += parse_bindings(shaders) + "\n"
webgpugrammar = Grammar()
err = webgpugrammar.parse_from_file(os.path.join(grammar_dir, os.path.join(grammar_dir, 'webgpu.txt')), extra)
if err > 0:
print('There were errors parsing grammar')
return
for outfile in outfiles:
result = generate_new_sample(template_contents, webgpugrammar)
if result is not None:
print('Writing a sample to ' + outfile)
try:
with open(outfile, 'w') as f:
f.write(result)
except IOError:
print('Error writing to output')
def get_option(option_name):
for i in range(len(sys.argv)):
if (sys.argv[i] == option_name) and ((i + 1) < len(sys.argv)):
return sys.argv[i + 1]
elif sys.argv[i].startswith(option_name + '='):
return sys.argv[i][len(option_name) + 1:]
return None
def main():
fuzzer_dir = os.path.dirname(__file__)
multiple_samples = False
template = os.path.join(fuzzer_dir, "template.html")
for a in sys.argv:
if a.startswith('--output_dir='):
multiple_samples = True
if '--output_dir' in sys.argv:
multiple_samples = True
if multiple_samples:
print('Running on ClusterFuzz')
out_dir = get_option('--output_dir')
nsamples = int(get_option('--no_of_files'))
print('Output directory: ' + out_dir)
print('Number of samples: ' + str(nsamples))
if not os.path.exists(out_dir):
os.mkdir(out_dir)
outfiles = []
for i in range(nsamples):
outfiles.append(os.path.join(out_dir, 'fuzz-' + str(i).zfill(5) + '.html'))
generate_samples(template, fuzzer_dir, outfiles)
else:
print('Arguments missing')
print("Usage:")
print("""--input_dir <directory>. ** not used. **
--output_dir <directory>. This is the output directory which the fuzzer should write to.
--no_of_files <n>. This is the number of testcases which the fuzzer should write to the output directory.
""")
if __name__ == '__main__':
main()