[flang-commits] [clang] [flang] [Clang][Driver][Test] Created test for unsupported driver options (PR #120900)
Mészáros Gergely via flang-commits
flang-commits at lists.llvm.org
Mon Mar 24 14:45:45 PDT 2025
================
@@ -0,0 +1,534 @@
+#!/usr/bin/env python3
+
+"""generate_unsupported_in_drivermode.py
+
+This script generates Lit regression test files that validate 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 LLVM TableGen executable can optionally be provided along with the path to the LLVM build tree bin directory.
+Otherwise, the script will search for it.
+
+The primary maintenance task for this script would be updating the expected return message for a driver mode if
+there are changes over time. See the instantiations of DriverData, specifically the check_str.
+
+Logic:
+1) For each option, (records of class "Option"), and for each driver, (records of class "OptionVisibility")
+ a. if the option's "Visibility" field includes the driver flavour, skip processing this option for this driver
+ b. if the option is part of an option group, (the record has the "Group" property),
+ and the group's "Visibility" field includes the driver flavour, skip processing this option for this driver
+ c. otherwise this option is not supported by this driver flavour, and this pairing is saved for testing
+2) For each unsupported pairing, generate a Lit RUN line, and a CHECK line to parse for expected output. Ex: "error: unknown argument"
+"""
+
+import os
+import json
+import subprocess
+from bisect import bisect_left
+from dataclasses import dataclass
+import argparse
+import dataclasses
+import itertools
+
+# Strings defined in Options.td for the various driver flavours. See "OptionVisibility"
+VISIBILITY_CC1AS = "CC1AsOption"
+VISIBILITY_CC1 = "CC1Option"
+VISIBILITY_CL = "CLOption"
+VISIBILITY_DXC = "DXCOption"
+VISIBILITY_DEFAULT = "DefaultVis"
+VISIBILITY_FC1 = "FC1Option"
+VISIBILITY_FLANG = "FlangOption"
+
+# Lit test prefix strings
+SLASH_SLASH = "// "
+EXCLAMATION = "! "
+
+exceptions_sequence = [
+ # Invalid usage of the driver options below causes unique output, so skip testing
+ "cc1",
+ "cc1as",
+ # There is currently a bug with "_no_warnings", i.e. --no-warnings. Diagnostic related options
+ # are parsed first, and always with CC1 visibility. They're used to set up the diagnostic
+ # engine, which parses "_no_warnings" (and its alias "w", i.e. -w) and sets an internal flag
+ # that suppresses all warnings.
+ "_no_warnings",
+ "w",
+]
+
+
+ at dataclass
+class DriverOption:
+ """Defines an unsupported driver-option combination
+ driver: The driver string as defined by OptionVisibility in Options.td
+ option: The option object from Options.td
+ option_name: Corresponding string for an option. See "Name" for a given option in Options.td
+ prefix: String that precedes the option. Ex. "-"
+ """
+
+ driver: str
+ option: str
+ option_name: str
+ prefix: str
+
+
+ at dataclass
+class DriverData:
+ """Dataclass for data specific to each driver
+ lit_cmd_prefix: The beginning string of the Lit command
+ lit_cmd_options: Strings containing additional options for this driver
+ visibility_str: The corresponding visibility string from OptionVisibility in Options.td
+ lit_cmd_end: String at the end of the Lit command
+ check_str: The string or regex to be sent to FileCheck
+ supported_joined_option_sequence: List of DriverOption objects for supported options
+ that are Kind *JOINED*, as defined in Options.td
+ supported_non_joined_option_sequence: List of DriverOption objects for supported options
+ that are not Kind *JOINED*, as defined in Options.td
+ test_option_sequence: A list of all the prefix-option pairs that will be tested for this driver
+ """
+
+ lit_cmd_prefix: str
+ lit_cmd_options: str
+ visibility_str: str
+ lit_cmd_end: str
+ check_str: str
+ supported_joined_option_sequence: list[DriverOption] = dataclasses.field(
+ default_factory=list
+ )
+ supported_non_joined_option_sequence: list[DriverOption] = dataclasses.field(
+ default_factory=list
+ )
+ test_option_sequence: list[str] = dataclasses.field(default_factory=list)
+
+
+def collect_transitive_groups(member, options_dictionary):
+ """Find the groups for a given member, where a member can be an option or a group.
+ Note that groups can themselves be part of groups, hence the recursion
+
+ For example, considering option 'C', it has the following 'Group' field as defined by Options.td:
+ "C": {
+ "Group": {
+ "def": "Preprocessor_Group",
+ // ...
+ },
+ // ...
+ },
+ 'Preprocessor_Group' is itself part of 'CompileOnly_Group', so option 'C' would be part of both groups
+ "Preprocessor_Group": {
+ // ...
+ "Group": {
+ "def": "CompileOnly_Group",
+ // ...
+ },
+ // ...
+ },
+
+ member: An option object or group object from Options.td.
+ options_dictionary: The converted Python dictionary from the Options.td json string
+
+ Return: A set including the group(s) found for the member. If no groups found, returns an empty set
+ """
+ parent_field = options_dictionary[member]["Group"]
+ if parent_field is None:
+ return set()
+
+ parent_name = parent_field["def"]
+ return {parent_name} | collect_transitive_groups(parent_name, options_dictionary)
+
+
+def get_visibility(option):
+ """Get a list of drivers that a given option is exposed to
+ option: The option object from Options.td
+ Return: Set that contains the visibilities of the given option
+ """
+ visibility_set = set()
+ # Check for the option's explicit visibility
+ for visibility in options_dictionary[option]["Visibility"]:
+ if visibility is not None:
+ visibility_set.add(visibility["def"])
+
+ # Check for the option's group's visibility
+ group_set = collect_transitive_groups(option, options_dictionary)
+ if group_set:
+ for group_name in group_set:
+ for visibility in options_dictionary[group_name]["Visibility"]:
+ visibility_set.add(visibility["def"])
+
+ return visibility_set
+
+
+def get_lit_test_note(test_visibility):
+ """Return the note to be included at the start of the Lit test file
+ test_visibility: Any VISIBILITY_* variable. VISIBILITY_DEFAULT will return the .c formatted test note.
+ All other will return the .f90 formatted test note
+ """
+ test_prefix = SLASH_SLASH if test_visibility == VISIBILITY_DEFAULT else EXCLAMATION
+
+ return (
+ f"{test_prefix}NOTE: This lit test was automatically generated to validate "
+ "unintentionally exposed arguments to various driver flavours.\n"
+ f"{test_prefix}NOTE: To make changes, see llvm-project/clang/utils/generate_unsupported_in_drivermode.py"
+ + " from which it was generated.\n"
+ f"{test_prefix}NOTE: Regenerate this Lit test with the following:\n"
+ f"{test_prefix}NOTE: python generate_unsupported_in_drivermode.py "
+ "--options-td-dir llvm-project/clang/include/clang/Driver --llvm-include-dir llvm-project/llvm/include --llvm-tblgen llvm-project/build/bin/llvm-tblgen\n\n"
+ )
+
+
+def write_lit_test(test_path, test_visibility):
+ """Write the Lit tests to file
+ test_path: File write path
+ test_visibility: VISIBILITY_DEFAULT, VISIBILITY_FLANG, or VISIBILITY_FC1 which indicates whether to write
+ to the main Lit test file, the flang test file, or the flang -fc1 test file
+ """
+ lit_file = open(test_path, "w")
+
+ lit_file.write(get_lit_test_note(test_visibility))
+ batch_size = 100
+
+ for visibility, driver_data in driver_data_dict.items():
+ is_flang_pair = visibility == VISIBILITY_FLANG or visibility == VISIBILITY_FC1
+
+ if (
+ (test_visibility == VISIBILITY_FLANG and visibility != VISIBILITY_FLANG)
+ or (test_visibility == VISIBILITY_FC1 and visibility != VISIBILITY_FC1)
+ or (test_visibility == VISIBILITY_DEFAULT and is_flang_pair)
+ ):
+ continue
+
+ comment_str = EXCLAMATION if is_flang_pair else SLASH_SLASH
----------------
Maetveis wrote:
```suggestion
comment_str = "! " if is_flang_pair else "// "
```
And remove `SLASH_SLASH` and `EXCLAMATION`
https://github.com/llvm/llvm-project/pull/120900
More information about the flang-commits
mailing list