Skip to content

Commit faf8597

Browse files
committed
[Clang][Driver][Test] Created test for unsupported driver options
Created generate_unsupported_in_drivermode.py which generates a Lit regression test file that validates that options are only exposed to intended driver modes. The options and driver modes are parsed from Options.td, whose path should be provided on the command line. See clang/include/clang/Driver/Options.td The path to the TableGen executable can optionally be provided. Otherwise, the script will search for it.
1 parent 342fa15 commit faf8597

File tree

1 file changed

+249
-0
lines changed

1 file changed

+249
-0
lines changed
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
#!/usr/bin/env python3
2+
3+
""" generate_unsupported_in_drivermode.py
4+
5+
usage: python generate_unsupported_in_drivermode.py <path>/Options.td [<path>/llvm-tblgen]
6+
7+
This script generates a Lit regression test file that validates that options
8+
are only exposed to intended driver modes.
9+
10+
The options and driver modes are parsed from Options.td, whose path should be
11+
provided on the command line. See clang/include/clang/Driver/Options.td
12+
13+
The path to the TableGen executable can optionally be provided. Otherwise, the
14+
script will search for it.
15+
16+
Logic:
17+
1) For each option, (records of class "Option"), and for each driver, (records of class "OptionVisibility")
18+
a. if the option's "Visibility" field includes the driver flavour, skip processing this option for this driver
19+
b. if the option is part of an option group, (the record has the "Group" property),
20+
and the group's "Visibility" field includes the driver flavor, skip processing this option for this driver
21+
c. otherwise this option is not supported by this driver flavor, and this pairing is saved for testing
22+
2) For each unsupported pairing, generate a Lit RUN line, and a CHECK line to parse for expected output. Ex: "error: unknown argument"
23+
"""
24+
25+
import sys
26+
import shutil
27+
import os
28+
import json
29+
import subprocess
30+
from pathlib import Path
31+
32+
LLVM_TABLEGEN = "llvm-tblgen"
33+
LIT_TEST_PATH = "../test/Driver/Inputs/unsupported-driver-options-check.ll"
34+
INCLUDE_PATH = "../../llvm/include"
35+
PREFIX = "CHECK-"
36+
37+
# Strings used in Options.td for various driver flavours
38+
OPTION_CC1AS = "CC1AsOption"
39+
OPTION_CC1 = "CC1Option"
40+
OPTION_CL = "CLOption"
41+
OPTION_DXC = "DXCOption"
42+
OPTION_DEFAULT = "DefaultVis"
43+
OPTION_FC1 = "FC1Option"
44+
OPTION_FLANG = "FlangOption"
45+
46+
# Error messages output from each driver
47+
ERROR_MSG_CC1AS = ": error: unknown argument"
48+
ERROR_MSG_CC1 = "error: unknown argument"
49+
ERROR_MSG_CL = "" # TODO
50+
ERROR_MSG_DXC = "" # TODO
51+
ERROR_MSG_DEFAULT = "clang: error: unknown argument"
52+
ERROR_MSG_FC1 = "error: unknown argument"
53+
ERROR_MSG_FLANG = "flang: error: unknown argument"
54+
55+
# Lit CHECK prefixes
56+
CHECK_PREFIX_CC1AS = PREFIX + OPTION_CC1AS
57+
CHECK_PREFIX_CC1 = PREFIX + OPTION_CC1
58+
CHECK_PREFIX_CL = PREFIX + OPTION_CL
59+
CHECK_PREFIX_DXC = PREFIX + OPTION_DXC
60+
CHECK_PREFIX_DEFAULT = PREFIX + OPTION_DEFAULT
61+
CHECK_PREFIX_FC1 = PREFIX + OPTION_FC1
62+
CHECK_PREFIX_FLANG = PREFIX + OPTION_FLANG
63+
64+
LIT_TEST_NOTE = ("; NOTE: This lit test was automatically generated to validate " +
65+
"unintentionally exposed arguments to various driver flavours.\n"
66+
"; NOTE: To make changes, see " + Path(__file__).resolve().as_posix()
67+
+ " from which it was generated.\n\n")
68+
69+
def print_usage():
70+
""" Print valid usage of this script
71+
"""
72+
sys.exit( "usage: python " + sys.argv[0] + " <path>/Options.td [<path>/llvm-tblgen]" )
73+
74+
def find_file(file_name, search_path):
75+
""" Find the given file name under a search path
76+
"""
77+
result = []
78+
79+
for root, dir, files in os.walk(search_path):
80+
if file_name in files:
81+
result.append(os.path.join(root, file_name))
82+
return result
83+
84+
def is_valid_file(path, expected_name):
85+
""" Is a file valid
86+
Check if a given path is to a file, and if it matches the expected file name
87+
"""
88+
if path.is_file() and path.name == expected_name:
89+
return True
90+
else:
91+
return False
92+
93+
def find_tablegen():
94+
""" Validate the TableGen executable
95+
"""
96+
result = shutil.which(LLVM_TABLEGEN)
97+
if result is None:
98+
sys.exit("Unable to find " + LLVM_TABLEGEN + ".\nExiting")
99+
else:
100+
print("TableGen found: " + result)
101+
return result
102+
103+
def find_groups(group_sequence, options_json, option):
104+
""" Find the groups for a given option
105+
Note that groups can themselves be part of groups, hence the recursion
106+
"""
107+
group_json = options_json[option]["Group"]
108+
109+
if group_json is None:
110+
return
111+
112+
# Prevent circular group membership lookup
113+
for group in group_sequence:
114+
if group_json["def"] == group:
115+
return
116+
117+
group_sequence.append(group_json["def"])
118+
return find_groups(group_sequence, options_json, option)
119+
120+
121+
class UnsupportedDriverOption():
122+
def __init__(self, driver, option):
123+
self.driver = driver
124+
self.option = option
125+
126+
# Validate the number of arguments have been passed
127+
argc = len(sys.argv)
128+
if argc < 2 or argc > 3:
129+
print_usage()
130+
131+
options_input_path = Path(sys.argv[1])
132+
tablegen_input_path = ""
133+
tablegen = None
134+
options_td = ""
135+
driver_sequence = []
136+
options_sequence = []
137+
unsupported_sequence = []
138+
139+
current_path = os.path.dirname(__file__)
140+
141+
# Validate Options.td
142+
if not is_valid_file(options_input_path, "Options.td"):
143+
print("Invalid Options.td path. Searching for valid path...")
144+
145+
relative_path = "../"
146+
search_path = os.path.join(current_path, relative_path)
147+
148+
file_search_list = find_file("Options.td", search_path)
149+
if len(file_search_list) != 1:
150+
print_usage()
151+
sys.exit("Unable to find Options.td.\nExiting")
152+
else:
153+
options_td = file_search_list[0]
154+
print(options_td)
155+
else:
156+
options_td = options_input_path.resolve().as_posix()
157+
158+
# Validate TableGen executable
159+
if argc > 2:
160+
tablegen_input_path = Path(sys.argv[2])
161+
if not is_valid_file(tablegen_input_path, "llvm-tblgen"):
162+
print("Invalid tablegen path. Searching for valid path...")
163+
tablegen = find_tablegen()
164+
else:
165+
tablegen = tablegen_input_path.resolve().as_posix()
166+
else:
167+
tablegen = find_tablegen()
168+
169+
# Run TableGen to convert Options.td to json
170+
options_json_str = subprocess.run([ tablegen, "-I", os.path.join(current_path, INCLUDE_PATH), options_td, "-dump-json"], stdout=subprocess.PIPE)
171+
options_json = json.loads(options_json_str.stdout.decode('utf-8'))
172+
173+
# Gather list of driver flavours
174+
for i in options_json["!instanceof"]["OptionVisibility"]:
175+
driver_sequence.append(i)
176+
177+
# Gather list of options
178+
for i in options_json["!instanceof"]["Option"]:
179+
options_sequence.append(i)
180+
181+
# Walk through the options list and find which drivers shouldn't be visible to each option
182+
for option in options_sequence:
183+
tmp_vis_list = []
184+
group_sequence = []
185+
186+
# Check for the option's explicit visibility
187+
for visibility in options_json[option]["Visibility"]:
188+
tmp_vis_list.append(visibility["def"])
189+
190+
# Check for the option's group's visibility
191+
find_groups(group_sequence, options_json, option)
192+
if len(group_sequence) > 0:
193+
for group_name in group_sequence:
194+
for visibility in options_json[group_name]["Visibility"]:
195+
tmp_vis_list.append(visibility["def"])
196+
197+
# Append to the unsupported list
198+
for driver in driver_sequence:
199+
if driver not in tmp_vis_list:
200+
unsupported_sequence.append(UnsupportedDriverOption(driver, option))
201+
202+
# Generate the lit test
203+
try:
204+
with open(LIT_TEST_PATH, "w") as lit_file:
205+
try:
206+
lit_file.write(LIT_TEST_NOTE)
207+
208+
for i in unsupported_sequence:
209+
if i.driver == OPTION_CC1AS:
210+
lit_file.write(
211+
"; RUN: not clang -cc1as -" + i.option + " -help 2>&1 | FileCheck -check-prefix=" + CHECK_PREFIX_CC1AS + " %s\n")
212+
continue
213+
if i.driver == OPTION_CC1:
214+
lit_file.write(
215+
"; RUN: not clang -cc1 -" + i.option + " -help 2>&1 | FileCheck -check-prefix=" + CHECK_PREFIX_CC1 + " %s\n")
216+
continue
217+
# if i.driver == OPTION_CL:
218+
# lit_file.write(
219+
# "; RUN: not clang-cl -" + i.option + " -help 2>&1 | FileCheck -check-prefix=" + CHECK_PREFIX_CL + " %s\n")
220+
# continue
221+
# if i.driver == OPTION_DXC:
222+
# lit_file.write(
223+
# "; RUN: not clang-dxc -" + i.option + " -help 2>&1 | FileCheck -check-prefix=" + CHECK_PREFIX_DXC + " %s\n")
224+
# continue
225+
if i.driver == OPTION_DEFAULT:
226+
lit_file.write(
227+
"; RUN: not clang -" + i.option + " -help 2>&1 | FileCheck -check-prefix=" + CHECK_PREFIX_DEFAULT + " %s\n")
228+
continue
229+
if i.driver == OPTION_FC1:
230+
lit_file.write(
231+
"; RUN: not flang -fc1 -" + i.option + " -help 2>&1 | FileCheck -check-prefix=" + CHECK_PREFIX_FC1 + " %s\n")
232+
continue
233+
if i.driver == OPTION_FLANG:
234+
lit_file.write(
235+
"; RUN: not flang -" + i.option + " -help 2>&1 | FileCheck -check-prefix=" + CHECK_PREFIX_FLANG + " %s\n")
236+
237+
lit_file.write("; " + CHECK_PREFIX_CC1AS + ": " + ERROR_MSG_CC1AS + "\n")
238+
lit_file.write("; " + CHECK_PREFIX_CC1 + ": " + ERROR_MSG_CC1 + "\n")
239+
lit_file.write("; " + CHECK_PREFIX_CL + ": " + ERROR_MSG_CL + "\n")
240+
lit_file.write("; " + CHECK_PREFIX_DXC + ": " + ERROR_MSG_DXC + "\n")
241+
lit_file.write("; " + CHECK_PREFIX_DEFAULT + ": " + ERROR_MSG_DEFAULT + "\n")
242+
lit_file.write("; " + CHECK_PREFIX_FC1 + ": " + ERROR_MSG_FC1 + "\n")
243+
lit_file.write("; " + CHECK_PREFIX_FLANG + ": " + ERROR_MSG_FLANG + "\n")
244+
except(IOError, OSError):
245+
sys.exit("Error writing to " + "LIT_TEST_PATH. Exiting")
246+
except(FileNotFoundError, PermissionError, OSError):
247+
sys.exit("Error opening " + "LIT_TEST_PATH" + ". Exiting")
248+
else:
249+
lit_file.close()

0 commit comments

Comments
 (0)