File: generate_allowed_imports.py

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (149 lines) | stat: -rw-r--r-- 4,913 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
Generates the list of valid imports for the lowest-supported version of
Windows.

Run from the root directory of the checkout - builds a .inc file that
will be included into delayloads_unittest.cc.

To export Chrome's view of the data for other build systems, run with the --json
flag and specify the --apisets-file.
"""

import argparse
import json
import os
import re
import sys

USE_PYTHON_3 = f'This script will only run under python3.'

# e.g. '  Section contains the following exports for CRYPT32.dll'
RE_NEWMOD = re.compile(
  r'Section contains the following exports for (?P<dll>\w+\.(?i:dll|drv))')
# e.g. '       1020    0 00088A30 CertAddCRLContextToStore'
#                        ^ can be blank
RE_EXPORT = re.compile(r'^\s+\d+\s+[0-9A-F]+\s+[0-9A-F ]{8}\s+(?P<export>\w+)')
# apiset line in apisets.inc (see generate_supported_apisets.py)
RE_APISET = re.compile(r'^{"(?P<apiset>[^"]+)",\s*(?P<version>\d+)},')

def parse_file(f):
  """Naive parser for dumpbin output.

  f: filehandle to file containing dumpbin output."""
  mods = dict()
  curmod = None
  imports = []
  for line in f.readlines():
    # e.g. '  Section contains the following exports for CRYPT32.dll'
    m = re.search(RE_NEWMOD, line)
    if m:
      if curmod:
        mods[curmod] = imports
        imports = []
      curmod = m.group('dll').lower()
      continue
    if curmod is None:
      continue
    # e.g. '       1020    0 00088A30 CertAddCRLContextToStore'
    m = re.search(RE_EXPORT, line)
    if m:
      imports.append(m.group('export'))
  if curmod:
    mods[curmod] = imports
  return mods


def generate_inc(input_file):
  """Reads output of dumpbin /exports *.dll and makes input for .inc C++ file.

  input_file: path to file containing output of `dumpbin /exports *.dll`.

  using DetailedImports = std::map<std::string, std::set<std::string>>;
  """
  # const DetailedImports kVariable = {
  mods = parse_file(open(input_file, 'r', encoding='utf-8'))
  module_entries = [];
  for module, functions in mods.items():
    joined_functions = ',\n'.join([f'  "{fn}"' for fn in functions])
    module_line = f' {{"{module}", {{{joined_functions}}}}}'
    module_entries.append(module_line)
  all_modules = (',\n').join(module_entries)
  return all_modules
  # };


def maybe_read(filename):
  """ Read existing file so that we don't write it again if it hasn't changed"""
  if not os.path.isfile(filename):
    return None;
  try:
    with open(filename, 'r', encoding='utf-8') as f:
      return f.read();
  except Exception:
    return None


def write_imports_inc(input, output):
  existing_content = maybe_read(output)
  new_content = generate_inc(input)
  if existing_content == new_content:
    return
  os.makedirs(os.path.dirname(output), exist_ok=True)
  with open(output, 'w', encoding='utf-8', newline='') as f:
    f.write(new_content)


def parse_apisets(f):
  # Parses output of generate_supported_apisets.py
  apisets = dict()
  for line in f.readlines():
    m = re.search(RE_APISET, line)
    if m:
      apisets[m.group("apiset").lower()] = m.group("version")
  return apisets


def write_json(exports_file, apisets_file, output):
  # This generates a pbtext used in google3 for a similar check.
  exports = parse_file(open(exports_file, 'r', encoding='utf-8'))
  apisets = parse_apisets(open(apisets_file, 'r', encoding='utf-8'))

  result = {
    "exports": exports,
    "apisets": apisets,
  }
  with open(output, 'w', encoding='utf-8', newline='') as f:
      json.dump(result, f, indent=2)


def main():
  parser = argparse.ArgumentParser(
      description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
  parser.add_argument('--exports-file',
                      default="chrome/test/delayload/supported_imports.txt",
                      metavar='FILE_NAME',
                      help='output of dumpbin /exports *.dll')
  parser.add_argument('--apisets-file',
                      default="chrome/test/delayload/apisets.inc",
                      metavar='FILE_NAME',
                      help='[optional] output of generate_supported_apisets.py')
  parser.add_argument('--out-file',
                      default='gen/chrome/test/delayload/supported_imports.inc',
                      metavar='FILE_NAME',
                      help='path to write .inc or .json file, within out-dir')
  parser.add_argument('--json', action='store_true',
                      help='output json instead of .inc')
  args, _extras = parser.parse_known_args()
  if args.json:
    # Used to export Chrome's data for other build systems.
    write_json(args.exports_file, args.apisets_file, args.out_file)
  else:
    # Used in Chrome build.
    write_imports_inc(args.exports_file, args.out_file)


if __name__ == '__main__':
  sys.exit(main())