xref: /aosp_15_r20/external/zucchini/fuzzers/create_seed_file_pair.py (revision a03ca8b91e029cd15055c20c78c2e087c84792e4)
1#!/usr/bin/env python
2# Copyright 2018 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Create binary protobuf encoding for fuzzer seeds.
7
8This script is used to generate binary encoded protobuf seeds for fuzzers
9related to Zucchini-gen and -apply, which take pairs of files are arguments. The
10binary protobuf format is faster to parse so it is the preferred method for
11encoding the seeds. For gen related fuzzers this should only need to be run
12once. For any apply related fuzzers this should be rerun whenever the patch
13format is changed.
14"""
15
16import argparse
17import logging
18import os
19import subprocess
20import sys
21
22ABS_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__)))
23PROTO_DEFINITION_FILE = 'file_pair.proto'
24
25def parse_args():
26  """Parse commandline args."""
27  parser = argparse.ArgumentParser()
28  parser.add_argument('protoc_path', help='Path to protoc.')
29  parser.add_argument('old_file', help='Old file to generate/apply patch.')
30  parser.add_argument('new_or_patch_file',
31                      help='New file to generate or patch to apply.')
32  parser.add_argument('output_file',
33                      help='File to write binary protobuf to.')
34  parser.add_argument('--imposed_matches',
35                      help='Equivalence matches to impose when generating '
36                      'the patch.')
37  return parser.parse_args()
38
39
40def read_to_proto_escaped_string(filename):
41  """Reads a file and converts it to hex escape sequences."""
42  with open(filename, 'rb') as f:
43    # Note that unicode-escape escapes all non-ASCII printable characters
44    # excluding ", which needs to be manually escaped.
45    return f.read().decode('latin1').encode('unicode-escape').replace(
46               b'"', b'\\"')
47
48
49def main():
50  args = parse_args()
51  # Create an ASCII string representing a protobuf.
52  content = [b'old_file: "%s"' % read_to_proto_escaped_string(args.old_file),
53             b'new_or_patch_file: "%s"' % read_to_proto_escaped_string(
54                                               args.new_or_patch_file)]
55
56  if args.imposed_matches:
57    content.append(b'imposed_matches: "%s"' %
58                       args.imposed_matches.encode('unicode-escape'))
59
60  # Encode the ASCII protobuf as a binary protobuf.
61  ps = subprocess.Popen([args.protoc_path, '--proto_path=%s' % ABS_PATH,
62                         '--encode=zucchini.fuzzers.FilePair',
63                         os.path.join(ABS_PATH, PROTO_DEFINITION_FILE)],
64                        stdin=subprocess.PIPE,
65                        stdout=subprocess.PIPE)
66  # Write the string to the subprocess. Single line IO is fine as protoc returns
67  # a string.
68  output = ps.communicate(input=b'\n'.join(content))
69  ps.wait()
70  if ps.returncode:
71    logging.error('Binary protobuf encoding failed.')
72    return ps.returncode
73
74  # Write stdout of the subprocess for protoc to the |output_file|.
75  with open(args.output_file, 'wb') as f:
76    f.write(output[0])
77  return 0
78
79
80if __name__ == '__main__':
81  sys.exit(main())
82