#!/usr/bin/env python3 # # Copyright 2024 The Chromium Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import argparse import collections import copy import os import sys import re def _GetDirAbove(dirname: str): """Returns the directory "above" this file containing |dirname| (which must also be "above" this file).""" path = os.path.abspath(__file__) while True: path, tail = os.path.split(path) if not tail: return None if tail == dirname: return path SOURCE_DIR = _GetDirAbove('testing') sys.path.insert(1, os.path.join(SOURCE_DIR, 'third_party')) sys.path.append(os.path.join(SOURCE_DIR, 'build')) import action_helpers import jinja2 _C_STR_TRANS = str.maketrans({ '\n': '\\n', '\r': '\\r', '\t': '\\t', '\"': '\\\"', '\\': '\\\\' }) def c_escape(v: str) -> str: return v.translate(_C_STR_TRANS) def main(): parser = argparse.ArgumentParser( description= 'Generate the necessary files for DomatoLPM to function properly.') parser.add_argument('-p', '--path', required=True, help='The path to the template file.') parser.add_argument('-f', '--file-format', required=True, help='The path (file format) where the generated files' ' should be written to.') parser.add_argument('-n', '--name', required=True, help='The name of the fuzzer.') parser.add_argument('-g', '--grammar', action='append') parser.add_argument('-d', '--generated-dir', required=True, help='The path to the target gen directory.') args = parser.parse_args() template_str = '' with open(args.path, 'r') as f: template_str = f.read() grammars = [{ 'proto_type': repr.split(':')[0], 'proto_name': repr.split(':')[1] } for repr in args.grammar] grammar_types = [f'<{grammar["proto_type"]}>' for grammar in grammars] # This splits the template into fuzzing tags, so that we know where we need # to insert grammar results. splitted_template = re.split('|'.join([f'({g})' for g in grammar_types]), template_str) splitted_template = [a for a in splitted_template if a is not None] grammar_elements = [] counter = collections.defaultdict(int) for elt in splitted_template: if elt in grammar_types: g = next(g for g in grammars if f'<{g["proto_type"]}>' == elt) g = copy.deepcopy(g) g['is_str'] = False counter[elt] += 1 c = counter[elt] g['proto_field_name'] = f'{g["proto_name"]}{c}' grammar_elements.append(g) else: grammar_elements.append({'is_str': True, 'content': c_escape(elt)}) template_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates') environment = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir)) rendering_context = { 'template_path': args.generated_dir, 'template_name': args.name, 'grammars': grammars, 'grammar_elements': grammar_elements, } template = environment.get_template('domatolpm_fuzzer.proto.tmpl') with action_helpers.atomic_output(f'{args.file_format}.proto', mode='w') as f: f.write(template.render(rendering_context)) template = environment.get_template('domatolpm_fuzzer.h.tmpl') with action_helpers.atomic_output(f'{args.file_format}.h', mode='w') as f: f.write(template.render(rendering_context)) template = environment.get_template('domatolpm_fuzzer.cc.tmpl') with action_helpers.atomic_output(f'{args.file_format}.cc', mode='w') as f: f.write(template.render(rendering_context)) if __name__ == '__main__': main()