Skip to content

Commit 625a09e

Browse files
committed
[BoundsSafety] Upstream gen-opt-remarks-check-lines.py script
This script when given a YAML file will emit FileCheck `CHECK` lines to match the YAML file. This is intended to be used for opt-remarks emitted in the YAML format.
1 parent 719214f commit 625a09e

File tree

1 file changed

+186
-0
lines changed

1 file changed

+186
-0
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Generate FileCheck CHECK lines to match opt-remarks
4+
YAML output.
5+
"""
6+
import argparse
7+
import os
8+
import re
9+
import sys
10+
11+
12+
def main():
13+
parser = argparse.ArgumentParser(description=__doc__)
14+
parser.add_argument('yaml_file',
15+
default=sys.stdin,
16+
type=argparse.FileType('r'),
17+
help='Input YAML file')
18+
parser.add_argument('--check-prefix',
19+
default='OPT-REM',
20+
help='FileCheck prefix. Default is "%(default)s".')
21+
parser.add_argument('-o',
22+
default=sys.stdout,
23+
type=argparse.FileType('w'),
24+
help='Output file. Default is stdout.')
25+
parser.add_argument('--ignore-whitespace-lines',
26+
action='store_true',
27+
help='Skip emitting CHECK directives for empty or whitespace lines')
28+
29+
pargs = parser.parse_args()
30+
31+
lines = get_check_lines(pargs)
32+
for l in lines:
33+
pargs.o.write(l)
34+
pargs.o.write('\n')
35+
pargs.o.flush()
36+
return 0
37+
38+
class Directive:
39+
def __init__(self, raw_line:str, prefix:str):
40+
self._prefix = prefix
41+
self._raw_line = raw_line
42+
43+
def get_line(self) -> str:
44+
return f'{self._prefix}{self._raw_line}'
45+
46+
47+
class CheckDirective(Directive):
48+
"""
49+
Represents FileCheck's `CHECK:` directive.
50+
"""
51+
def __init__(self, raw_line:str, prefix:str):
52+
assert isinstance(prefix, str) and len(prefix) > 0
53+
assert isinstance(raw_line, str) and len(raw_line) > 0
54+
super().__init__(raw_line, f'// {prefix}: ')
55+
56+
class CheckNextDirective(Directive):
57+
"""
58+
Represents FileCheck's `CHECK-NEXT:` directive.
59+
"""
60+
def __init__(self, raw_line:str, prefix:str):
61+
assert isinstance(prefix, str) and len(prefix) > 0
62+
assert isinstance(raw_line, str) and len(raw_line) > 0
63+
super().__init__(raw_line, f'// {prefix}-NEXT: ')
64+
65+
class CheckNotDirective(Directive):
66+
"""
67+
Represents FileCheck's `CHECK-NOT:` directive.
68+
"""
69+
def __init__(self, raw_line:str, prefix:str):
70+
assert isinstance(prefix, str) and len(prefix) > 0
71+
assert isinstance(raw_line, str) and len(raw_line) > 0
72+
super().__init__(raw_line, f'// {prefix}-NOT: ')
73+
74+
class CheckEmptyDirective(Directive):
75+
"""
76+
Represents FileCheck's `CHECK-EMPTY:` directive.
77+
"""
78+
def __init__(self, prefix:str):
79+
assert isinstance(prefix, str) and len(prefix) > 0
80+
super().__init__('', f'// {prefix}-EMPTY: ')
81+
82+
class EmptyLine(Directive):
83+
"""
84+
This isn't a real FileCheck directive but its a
85+
useful mechanism for emitting empty lines in the
86+
final output
87+
"""
88+
def __init__(self):
89+
super().__init__('', '')
90+
91+
92+
__RE_DEBUG_LOC_LINE_START = re.compile(r"^(?P<head>\s*DebugLoc:\s+\{ File: ')(?P<file>.+)(?P<tail>',\s*$)$")
93+
def filter_line(line:str) -> str:
94+
"""
95+
Filter lines in opt-remark YAML output.
96+
"""
97+
assert isinstance(line, str)
98+
99+
# Strip paths in 'DebugLoc:' so that they are portable
100+
debug_loc_match = __RE_DEBUG_LOC_LINE_START.match(line)
101+
if debug_loc_match:
102+
file_path = debug_loc_match.group('file'
103+
)
104+
# Strip off the path prefix
105+
basename_file_path = os.path.basename(file_path)
106+
# Do replacement
107+
line = __RE_DEBUG_LOC_LINE_START.sub(
108+
r'\g<head>' + '{{.*}}' + basename_file_path + r'\g<tail>',
109+
line)
110+
111+
return line
112+
113+
_RE_WHITESPACE_ONLY = re.compile(r'^[ \t]+$')
114+
def get_check_lines(pargs: argparse.Namespace) -> list[str]:
115+
"""
116+
Generate a list of lines containing CHECK directives that
117+
try to match the provided YAML file.
118+
"""
119+
directives = [ None ]
120+
lines = pargs.yaml_file.readlines()
121+
idx = [ 0 ] # Hack make lamba capture work
122+
max_line = len(lines) -1
123+
def read_line_and_inc():
124+
if idx[0] > max_line:
125+
raise Exception(
126+
f'Cannot read line {idx[0]+1} which is past last line '
127+
f'{max_line+1}')
128+
line_to_read = lines[idx[0]]
129+
idx[0] += 1
130+
return line_to_read.removesuffix('\n')
131+
def current_idx() -> int:
132+
return idx[0]
133+
134+
NextDirectiveType = CheckDirective
135+
136+
# Loop over lines
137+
while current_idx() <= max_line:
138+
current_line_idx = current_idx()
139+
current_line = read_line_and_inc()
140+
141+
if current_line == '--- !Analysis' and current_line_idx != 0:
142+
# Add empty line between entries
143+
directives.append(EmptyLine())
144+
145+
whitespace_only_match = _RE_WHITESPACE_ONLY.match(current_line)
146+
if whitespace_only_match:
147+
# Whitespace only lines
148+
if pargs.ignore_whitespace_lines:
149+
NextDirectiveType = CheckDirective
150+
continue
151+
152+
# Craft a regex to match a line with just whitespace. This is
153+
# necessary because FileCheck doesn't allow most directives to be
154+
# empty.
155+
directive = NextDirectiveType('{{^[ \t]+$}}', prefix=pargs.check_prefix)
156+
elif len(current_line) == 0:
157+
# Empty line
158+
if pargs.ignore_whitespace_lines:
159+
NextDirectiveType = CheckDirective
160+
continue
161+
directive = CheckEmptyDirective(prefix=pargs.check_prefix)
162+
else:
163+
# Match the line.
164+
filtered_line = filter_line(current_line)
165+
directive = NextDirectiveType(filtered_line, prefix=pargs.check_prefix)
166+
167+
NextDirectiveType = CheckNextDirective
168+
169+
directives.append(directive)
170+
171+
# Push CHECK-NOT to make sure there are no more entries
172+
directives.append(EmptyLine())
173+
directives.append(
174+
CheckNotDirective('--- !Analysis', prefix=pargs.check_prefix))
175+
176+
# Loop over directives and gather the lines
177+
processed_lines = []
178+
for directive in directives:
179+
if directive is None:
180+
continue
181+
processed_lines.append(directive.get_line())
182+
return processed_lines
183+
184+
185+
if __name__ == '__main__':
186+
main()

0 commit comments

Comments
 (0)