Charusso updated this revision to Diff 253777.
Charusso marked 3 inline comments as done.
Charusso added a comment.
Herald added a subscriber: ASDenysPetrov.

- Remove the test of creating a live checker, instead copy over the live 
checker when the script runs.
- Simplify the script by adding the new package to the end of the file.
- In case of the `checkers.rst` a non-alpha package is going to be added before 
the alpha packages.
- According to this change simplify the tests.
- `DummyChecker` -> `ExampleChecker`.


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D73521/new/

https://reviews.llvm.org/D73521

Files:
  clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
  clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
  clang/lib/StaticAnalyzer/Checkers/ExampleChecker.cpp
  clang/test/Analysis/add-new-checker/add-main-package.rst
  clang/test/Analysis/add-new-checker/add-main-package.td
  clang/test/Analysis/add-new-checker/add-multiple-packages.rst
  clang/test/Analysis/add-new-checker/add-multiple-packages.td
  clang/test/Analysis/add-new-checker/check-add-new-checker.py
  clang/test/Analysis/add-new-checker/checker-name.rst
  clang/test/Analysis/add-new-checker/checker-name.td
  clang/test/Analysis/add-new-checker/flow-package-exist-checker.rst
  clang/test/Analysis/add-new-checker/flow-package-exist-checker.td
  clang/test/Analysis/add-new-checker/flow-package-not-exist.rst
  clang/test/Analysis/add-new-checker/flow-package-not-exist.td
  clang/test/Analysis/add-new-checker/lit.local.cfg.py
  clang/test/Analysis/example-checker.cpp
  clang/utils/analyzer/add-new-checker.py

Index: clang/utils/analyzer/add-new-checker.py
===================================================================
--- /dev/null
+++ clang/utils/analyzer/add-new-checker.py
@@ -0,0 +1,637 @@
+#!/usr/bin/env python
+#
+#===- add-new-checker.py - Creates a Static Analyzer checker --*- python -*-===#
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+#===------------------------------------------------------------------------===#
+#
+# Example usage: python add-new-checker.py alpha.package.Foo
+#
+#===------------------------------------------------------------------------===#
+
+import argparse
+import json
+import os
+import re
+import subprocess
+
+
+class Checker:
+    def __init__(self, packages, name):
+        self.packages = packages
+        self.name = name
+
+    def name(self):
+        return self.name
+
+    def packages(self):
+        return self.packages
+
+    def __str__(self):
+        return '.'.join(self.packages) + '.' + self.name
+
+
+# Obtain the longest known package-chain of the 'packages' chain. For example
+# the checker creation of 'alpha.core.a.Checker' would return ['alpha', 'core'].
+def get_best_package_match(packages, known_packages):
+    best_package_match = []
+    for i in range(len(packages)):
+        temp_packages = packages[:i + 1]
+        package_name = '.'.join(temp_packages)
+        if package_name in known_packages:
+            best_package_match = temp_packages
+    return best_package_match
+
+
+# Put every additional package and the checker into an increasing size jagged
+# array. For example the checker creation of 'core.a.Checker' would return
+# '[['core', 'a'], ['core', 'a', 'Checker']]'.
+def get_addition(packages, name, best_package_match):
+    addition = []
+    match_count = len(best_package_match)
+    for i, package in enumerate(packages):
+        if i < match_count:
+            continue
+        addition.append(best_package_match + packages[match_count : i + 1])
+    addition.append(packages + [name])
+    return addition
+
+
+def add_checkers_entry(clang_path, checker, args):
+    checkers_include_path = os.path.join(clang_path, 
+        'include', 'clang', 'StaticAnalyzer', 'Checkers')
+    checkers_path = os.path.join(checkers_include_path, 'Checkers.td')
+
+    # This only could test '.td'.
+    if args.is_test:
+        checkers_path = args.test_path
+        if not checkers_path.endswith('.td.tmp'):
+            return
+
+    packages = checker.packages
+    name = checker.name
+
+    # See if TableGen is found.
+    try:
+        devnull = open(os.devnull)
+        subprocess.Popen([args.tblgen_path, '-h'], stdout=devnull,
+                         stderr=devnull).communicate()
+    except OSError as e:
+        print("[error] llvm-tblgen is not found. Please specify it explicitly. "
+              "For example: '--tblgen-path llvm-tblgen'")
+        raise
+
+    # Load the checkers' TableGen.
+    data = None
+    try:
+        data = subprocess.check_output([args.tblgen_path, '-dump-json',
+                                        checkers_path,
+                                        '-I=' + checkers_include_path])
+    except subprocess.CalledProcessError as e:
+        print('TableGen JSON dump failed:\n' + e.output.decode())
+        raise
+
+    data = json.loads(data)
+
+    # Store the package names with their chained definition. For example the
+    # package 'CoreAlpha' => 'alpha.core'.
+    package_name_def_map = {}
+    for package_def in data['!instanceof']['Package']:
+        package = data[package_def]
+        if package['ParentPackage']:
+            chain = [package]
+            while True:
+                parent_package = chain[-1]['ParentPackage']
+                if parent_package is None:
+                    break
+                chain.append(data[parent_package['def']])
+
+            dump = []
+            for elem in reversed(chain):
+                package_name = elem['PackageName']
+                dump.append(package_name)
+            package_name_def_map['.'.join(dump)] = package['!name']
+        else:
+            package_name = package['PackageName']
+            package_name_def_map[package_name] = package['!name']
+
+    # Store the opposite mapping of the above map as well.
+    package_def_name_map = {}
+    for tmp_name, definition in package_name_def_map.items():
+        package_def_name_map[definition] = tmp_name
+
+    checker_names = data['!instanceof']['Checker']
+    parent_packages = data['!instanceof']['Package']
+
+    # See whether the new checker is already defined.
+    full_name = name + 'Checker'
+    packages_str = '.'.join(packages)
+    for temp_name in checker_names:
+        temp_parent_package_def = data[temp_name]['ParentPackage']['def']
+        temp_package = package_def_name_map[temp_parent_package_def]
+        if full_name == temp_name and temp_package == packages_str:
+            print("[error] '" + full_name + "' exist in package '"
+                  + packages_str + "'.")
+            exit()
+
+    # Store the packages which has checkers.
+    package_defs_in_use = set()
+    for temp_name in checker_names:
+        package_defs_in_use.add(data[temp_name]['ParentPackage']['def'])
+
+    # Store every additional package and the checker we need to create.
+    best_package_match = get_best_package_match(packages, package_name_def_map)
+    addition = get_addition(packages, name, best_package_match)
+
+    # This definition increases if 'write_package_def()' fires. The idea is that
+    # it stores the last written package definition so we could chain new
+    # packages to it.
+    packages_def = package_name_def_map[packages_str] \
+        if packages_str in package_name_def_map else ''
+
+    def write_package_def():
+        for add_packages in addition[:-1]:
+            temp_package = add_packages[-1]
+            if len(add_packages) == 1: # Main package.
+                temp_packages_def = temp_package.capitalize()
+                out_stream.write('def %s : Package<"%s">;\n'
+                                 % (temp_packages_def, temp_package))
+
+                # Update the map with the last known new definition.
+                packages_def = temp_packages_def
+                package_name_def_map[temp_package] = packages_def
+                continue
+
+            # Normal package
+            parent_package = '.'.join(add_packages[:-1])
+            parent_package_def = package_name_def_map[parent_package]
+
+            # Update the map with the last known new definition.
+            packages_def = temp_package.capitalize() + parent_package_def
+            package_name_def_map['.'.join(add_packages)] = packages_def
+
+            out_stream.write('def %s : Package<%s>, ParentPackage<%s>;\n'
+                             % (packages_def, '"' + temp_package + '"',
+                                parent_package_def))
+
+    def write_checker():
+        out_stream.write('def %sChecker : Checker<"%s">,' % (name, name))
+        if args.is_test:
+            out_stream.write('\n\n')
+            return
+        out_stream.write('\n  HelpText<"FIXME">,')
+        out_stream.write('\n  Documentation<HasDocumentation>;\n\n')
+
+    def write_package():
+        all_packages = '.'.join(addition[-1][:-1])
+        all_packages_def = package_name_def_map[all_packages]
+
+        out_stream.write('let ParentPackage = %s in {\n\n' % all_packages_def)
+        write_checker()
+        out_stream.write('} // end "%s"\n' % all_packages)
+
+    look_for_package = '.'.join(best_package_match)
+    look_for_package_def = package_name_def_map[look_for_package] \
+        if len(best_package_match) > 0 else ''
+    is_parent_exist = look_for_package_def in parent_packages
+
+    package_def_str = 'def (.*) : Package<"(.*)">';
+    package_def_re = re.compile(package_def_str + ';$')
+    parent_package_def_re = \
+        re.compile(package_def_str + ', ParentPackage<(.*)>;$')
+    package_or_parent_package_def_re = re.compile(package_def_str)
+    parent_package_let_re = re.compile('^let ParentPackage = (.*) in {$')
+    checker_def_re = re.compile('def (.*) : Checker<.*>')
+
+    # Prefetch the line number of the end of the package definitions.
+    lines = []
+    last_package_def_line_index = 0
+    with open(checkers_path, 'r') as in_stream:
+        for i, line in enumerate(in_stream):
+            lines.append(line)
+            if package_or_parent_package_def_re.match(line):
+                last_package_def_line_index = i
+
+
+    # If the entire package declared add the checker inside its declaration.
+    if packages_def in package_defs_in_use:
+        with open(checkers_path, 'w') as out_stream:
+            is_checker_added = False
+            is_parent_package_let_found = False
+
+            for line in lines:
+                if is_checker_added:
+                    out_stream.write(line)
+                    continue
+
+                # Find the parent declaration.
+                result = parent_package_let_re.match(line)
+                if not is_parent_package_let_found and result:
+                    temp_parent_def = result.group(1)
+                    if packages_def == temp_parent_def:
+                        is_parent_package_let_found = True
+
+                # After that point we want to add the checker.
+                if not is_parent_package_let_found:
+                    out_stream.write(line)
+                    continue
+
+                # Try to add the checker inside its parent package.
+                result = checker_def_re.match(line)
+                if result:
+                    temp_name = result.group(1)
+                    if temp_name.lower() > name.lower():
+                        write_checker()
+                        is_checker_added = True
+
+                # We are at the end of the parent package, add here.
+                if line.startswith('}'):
+                    write_checker()
+                    is_checker_added = True
+
+                out_stream.write(line)
+        return
+
+
+    # If a new main package requested add its definition to the end of the
+    # definitions and create the package declaration at the end of the file.
+    if look_for_package_def not in parent_packages:
+        with open(checkers_path, 'w') as out_stream:
+            for i, line in enumerate(lines):
+                out_stream.write(line)
+                if i == last_package_def_line_index:
+                    write_package_def()
+
+            write_package()
+        return
+
+
+    # Otherwise add the new package definition by alphabetical order and create
+    # the package at the end of the file so the user could adjust its position.
+    with open(checkers_path, 'w') as out_stream:
+        is_checker_added = False
+        is_package_def_added = False
+        is_parent_package_def_found = False
+
+        for i, line in enumerate(lines):
+            if is_package_def_added:
+                out_stream.write(line)
+                continue
+
+            # Find the place for the package definitions.
+            if (not is_package_def_added
+                and package_def_re.match(line) is not None):
+                is_parent_package_def_found = True
+
+            # After that point we want to find the parent package definition.
+            if not is_parent_package_def_found:
+                out_stream.write(line)
+                continue
+
+            # Find the parent definition.
+            if not is_package_def_added:
+                result = parent_package_def_re.match(line)
+                if result:
+                    temp_parent_def = result.group(1)
+                    temp_parent = package_def_name_map[temp_parent_def]
+
+                    if (temp_parent > packages_str
+                        or not temp_parent.startswith(look_for_package)):
+                        write_package_def()
+                        is_package_def_added = True
+
+            # Check for the end of package definitions.
+            if not is_package_def_added:
+                out_stream.write(line)
+                if i == last_package_def_line_index:
+                    write_package_def()
+                    is_package_def_added = True
+                continue
+
+            out_stream.write(line)
+
+        # End of file reached.
+        write_package()
+
+
+def add_doc_entry(clang_path, checker, args):
+    doc_path = os.path.join(clang_path, 'docs', 'analyzer', 'checkers.rst')
+
+    # This only could test '.rst'.
+    if args.is_test:
+        doc_path = args.test_path
+        if not doc_path.endswith('.rst.tmp'):
+            return
+
+    name = checker.name
+    packages = checker.packages
+    packages_str = '.'.join(packages)
+
+    # Normalize the package separation with dots.
+    def normalize(raw_package):
+        return '.'.join(raw_package.split('-')) if '-' in raw_package \
+                                                else raw_package
+
+    # With that we also catch the '.. _package-checkers:' references where we
+    # need to obtain the package name by removing the '-checkers' part.
+    # Note: Simplification, like '([a-z0-9]+([.-][a-z0-9]+)+)' does not work
+    # due to some Python mystery.
+    package_re_str = '^(|\.\.\s*_)([a-z0-9]+([.-][a-z0-9]+)+)'
+    package_re = re.compile(package_re_str)
+    checker_re = re.compile(package_re_str + '[:.-]([A-Z]\w*)')
+
+    known_packages = set()
+    lines = []
+    with open(doc_path, 'r') as in_stream:
+        for line in in_stream:
+            lines.append(line)
+            result = package_re.match(line)
+            if result:
+                temp_package = result.group(2)
+                if temp_package.endswith('-checkers'):
+                    continue
+                known_packages.add(normalize(temp_package))
+
+    # Store every additional package and the checker we need to create.
+    best_package_match = get_best_package_match(packages, known_packages)
+    addition = get_addition(packages, name, best_package_match)
+
+    # Try to add a new main package, then only add the largest package-chain
+    # with the checker.
+    def write_checker():
+        temp_package = '.'.join(addition[-1][:-1])
+        if len(addition[0]) == 1: # New main package.
+            out_stream.write(temp_package)
+            out_stream.write('\n' + '^' * len(temp_package))
+            out_stream.write("\nFIXME: Document '%s'.\n\n" % temp_package)
+
+        title = str(checker) + 'Checker (C)'
+        out_stream.write('.. _%s:\n' % '-'.join(addition[-1]))
+        out_stream.write('\n' + title)
+        if args.is_test:
+            out_stream.write('\n\n')
+            return
+        out_stream.write('\n' + '"' * len(title))
+        out_stream.write("\nFIXME: Document '%s'.\n" % name)
+        out_stream.write('\n.. code-block:: %s\n' % 'c')
+        out_stream.write('\n  void test() {\n    // FIXME\n  }\n\n')
+
+    # If a new non-alpha package requested add it before the alpha packages or
+    # if an alpha package requested add it to the end of the file so that the
+    # user could adjust its position.
+    is_checker_added = False
+    if packages_str not in known_packages:
+        with open(doc_path, 'w') as out_stream:
+            if addition[0][0] == 'alpha': # Main package.
+                out_stream.writelines(lines)
+                write_checker()
+                return
+
+            for line in lines:
+                if is_checker_added:
+                    out_stream.write(line)
+                    continue
+
+                # Find the '.. _alpha-checkers:' reference.
+                result = package_re.match(line)
+                if result and result.group(2) == 'alpha-checkers':
+                    write_checker()
+                    is_checker_added = True
+
+                out_stream.write(line)
+
+            # End of file reached.
+            if not is_checker_added:
+                write_checker()
+        return
+
+    # Otherwise look for the existing parent package and add a checker to it.
+    with open(doc_path, 'w') as out_stream:
+        look_for_package = '.'.join(best_package_match)
+        is_parent_package_found = False
+
+        for line in lines:
+            if is_checker_added:
+                out_stream.write(line)
+                continue
+
+            # Find the parent package reference.
+            result = package_re.match(line)
+            if not is_parent_package_found and result:
+                temp_package = normalize(result.group(2))
+                if temp_package.endswith('checkers'):
+                    temp_package = temp_package[:-9]
+
+                if temp_package == packages_str:
+                    is_parent_package_found = True
+
+                # Past the parent package.
+                if (is_parent_package_found and
+                    not temp_package.startswith(look_for_package)):
+                    write_checker()
+                    is_checker_added = True
+                    out_stream.write(line)
+                    continue
+
+            if not is_parent_package_found:
+                out_stream.write(line)
+                continue
+
+            # Try to add the checker.
+            result = checker_re.match(line)
+            if result:
+                temp_name = result.group(4)
+                if temp_name.lower() > name.lower():
+                    write_checker()
+                    is_checker_added = True
+
+            out_stream.write(line)
+
+        # End of file reached.
+        if not is_checker_added:
+            write_checker()
+
+
+def add_release_notes_entry(clang_path, checker):
+    release_notes_path = os.path.join(clang_path, 'docs', 'ReleaseNotes.rst')
+    name = checker.name
+
+    # See whether we have already got some new checkers.
+    lines = []
+    is_new_checker_found = False
+    new_checker_re = re.compile("^- New checker: :doc:`(.*)<")
+
+    with open(release_notes_path, 'r') as in_stream:
+        for line in in_stream:
+            lines.append(line)
+            if not is_new_checker_found and new_checker_re.match(line):
+                is_new_checker_found = True
+
+    with open(release_notes_path, 'w') as out_stream:
+        is_added = False
+        is_static_analyzer_line_passed = False
+        look_for_name = '-'.join(checker.packages) + '-' + name
+
+        def write_checker():
+            out_stream.write('- New checker: :doc:`%s<%s>`.\n'
+                             % (checker, look_for_name))
+            out_stream.write("\n  FIXME: Document '%s'.\n\n" % name)
+
+        for i, line in enumerate(lines):
+            if is_added:
+                out_stream.write(line)
+                continue
+
+            if (not is_static_analyzer_line_passed
+                and lines[i - 2].strip() == 'Static Analyzer'):
+                is_static_analyzer_line_passed = True
+
+            if not is_static_analyzer_line_passed:
+                out_stream.write(line)
+                continue
+
+            if not is_new_checker_found:
+                out_stream.write(line)
+                write_checker()
+                is_added = True
+                continue
+
+            if (is_new_checker_found and line.startswith('- ')
+                and not new_checker_re.match(line)):
+                write_checker()
+                is_added = True
+
+            out_stream.write(line)
+
+
+def add_cmake_entry(clang_path, checker):
+    cmake_lists_path = os.path.join(clang_path, 'lib', 'StaticAnalyzer',
+                                    'Checkers', 'CMakeLists.txt')
+    name = checker.name
+    full_name = name + 'Checker.cpp'
+
+    lines = []
+    with open(cmake_lists_path, 'r') as in_stream:
+        lines = in_stream.readlines()
+
+    with open(cmake_lists_path, 'w') as out_stream:
+        is_added = False
+        for raw_line in lines:
+            line = raw_line.strip()
+            if (not is_added and line.endswith('.cpp')
+                and line.lower() > name.lower()):
+                out_stream.write('  ' + full_name + '\n')
+                is_added = True
+            out_stream.write(raw_line)
+
+
+def add_checker(clang_path, checker):
+    name = checker.name + 'Checker'
+    checkers_path = \
+        os.path.join(clang_path, 'lib', 'StaticAnalyzer', 'Checkers')
+    checker_path = os.path.join(checkers_path, name + '.cpp')
+    example_checker_path = os.path.join(checkers_path, 'ExampleChecker.cpp')
+
+    lines = []
+    with open(example_checker_path, 'r') as in_stream:
+        lines = in_stream.readlines()
+
+    with open(checker_path, 'w') as out_stream:
+        lhs = '//===- ' + name + ' - FIXME: Tiny doc '
+        rhs = '-*- C++ -*-===//'
+        header = lhs + ('-' * (80 - len(lhs) - len(rhs))) + rhs
+        out_stream.write(header + '\n')
+
+        # Skip the first line.
+        for line in lines[1:]:
+            out_stream.write(line.replace('ExampleChecker', name))
+
+
+def add_test(clang_path, checker):
+    name = checker.name
+    tests_path = os.path.join(clang_path, 'test', 'Analysis')
+    test_name = ''
+    for c in name:
+        if c.isupper():
+            test_name += '-'
+        test_name += c.lower()
+    test_name = test_name.lstrip('-') + '-checker'
+
+    test_path = os.path.join(tests_path, test_name + '.cpp')
+    example_test_path = os.path.join(tests_path, 'example-checker.cpp')
+
+    lines = ''
+    with open(example_test_path, 'r') as in_stream:
+        lines = in_stream.read()
+
+    with open(test_path, 'w') as out_stream:
+        out_stream.write(lines.replace('alpha.Example', str(checker)))
+
+
+def main():
+    parser = argparse.ArgumentParser(description='Creates a new checker.')
+    parser.add_argument('name', help='the full name of the checker with the '
+                                     'packages, for example: '
+                                     'alpha.package.Checker')
+    parser.add_argument('--tblgen-path', default='llvm-tblgen',
+                        help='the path to llvm-tblgen')
+    parser.add_argument('--test', dest='is_test',
+                        action='store_true', help=argparse.SUPPRESS)
+    parser.add_argument('--test-path', help=argparse.SUPPRESS)
+    args = parser.parse_args()
+
+    raw_packages_and_name = args.name
+    if '.' not in raw_packages_and_name:
+        print('[error] No package found. Please specify it explicitly. '
+              'For example: alpha.package.Checker.')
+        exit()
+
+    raw_checker_parts = raw_packages_and_name.split('.')
+    raw_name = raw_checker_parts[-1]
+    raw_package_names = raw_checker_parts[:-1]
+
+    # We will add the 'Checker' suffix where appropriate.
+    name = raw_name[0].upper() + raw_name[1:]
+    if name != 'Checker' and name.lower().endswith('checker'):
+        name = name[:-7]
+
+    packages_and_name = ''
+    for package in raw_package_names:
+        packages_and_name += package.lower() + '.'
+    packages_and_name += name
+
+    clang_path = os.path.abspath(__file__)
+    clang_path = clang_path[:clang_path.rfind('clang') + 5]
+
+    # See whether the file of the new checker is already made.
+    full_name = name + 'Checker.cpp'
+    checker_path = os.path.join(clang_path, 'lib', 'StaticAnalyzer',
+                                'Checkers', full_name)
+    if os.path.isfile(checker_path):
+        print("[error] '" + full_name + "' already exist.")
+        exit()
+
+    checker = Checker(packages_and_name.split('.')[:-1], name)
+    print("Creating checker '" + str(checker) + "'...")
+
+    add_checkers_entry(clang_path, checker, args)
+    add_doc_entry(clang_path, checker, args)
+
+    # At the moment only 'Checkers.td' and 'checkers.rst' are tested.
+    if args.is_test:
+        return
+
+    add_cmake_entry(clang_path, checker)
+    add_release_notes_entry(clang_path, checker)
+    add_checker(clang_path, checker)
+    add_test(clang_path, checker)
+
+    # FIXME: Add resources to study checker-writing(?)
+    print('Done.')
+
+
+if __name__ == '__main__':
+    main();
Index: clang/test/Analysis/example-checker.cpp
===================================================================
--- /dev/null
+++ clang/test/Analysis/example-checker.cpp
@@ -0,0 +1,37 @@
+// RUN: %clang_analyze_cc1 \
+// RUN:  -analyzer-checker=core,alpha.Example \
+// RUN:  -analyzer-output=text -verify %s
+
+namespace std {
+void mark_not_awesome(void());
+} // namespace std
+
+// FIXME: Add positive tests.
+namespace test_bad {
+void f();
+bool lunarPhase();
+void test() {
+  if (lunarPhase()) {
+    // expected-note@-1 {{Assuming the condition is true}}
+    // expected-note@-2 {{Taking true branch}}
+    std::mark_not_awesome(f);
+    // expected-note@-1 {{Function 'f' marked as not awesome}}
+  }
+
+  f();
+  // expected-note@-1 {{There is a path where 'f' is insufficiently awesome}}
+  // expected-warning@-2 {{There is a path where 'f' is insufficiently awesome}}
+}
+} // namespace test_bad
+
+// FIXME: Add negative tests.
+namespace test_good {
+void f();
+void test() {
+  {
+    void f();
+    std::mark_not_awesome(f);
+  }
+  f(); // no-warning
+}
+} // namespace test_good
Index: clang/test/Analysis/add-new-checker/lit.local.cfg.py
===================================================================
--- /dev/null
+++ clang/test/Analysis/add-new-checker/lit.local.cfg.py
@@ -0,0 +1,33 @@
+import lit.util
+import lit.formats
+import os
+
+# Choose between lit's internal shell pipeline runner and a real shell.  If
+# LIT_USE_INTERNAL_SHELL is in the environment, we use that as an override.
+use_lit_shell = os.environ.get("LIT_USE_INTERNAL_SHELL")
+if use_lit_shell:
+    # 0 is external, "" is default, and everything else is internal.
+    execute_external = (use_lit_shell == "0")
+else:
+    # Otherwise we default to internal on Windows and external elsewhere, as
+    # bash on Windows is usually very slow.
+    execute_external = (not sys.platform in ['win32'])
+
+config.test_format = lit.formats.ShTest(execute_external)
+
+config.suffixes = ['.td', '.rst']
+
+check_add_new_checker_path = \
+    os.path.join(os.path.abspath(os.path.join(__file__, os.pardir)),
+                 'check-add-new-checker.py')
+
+config.substitutions.append(('%check_add_new_checker',
+                             '%s %s' % (config.python_executable,
+                                        check_add_new_checker_path)))
+
+add_new_checker_path = os.path.join(config.clang_src_dir, 'utils', 'analyzer',
+                                    'add-new-checker.py')
+
+config.substitutions.append(('%add_new_checker',
+                             '%s %s' % (config.python_executable,
+                                        add_new_checker_path)))
Index: clang/test/Analysis/add-new-checker/flow-package-not-exist.td
===================================================================
--- /dev/null
+++ clang/test/Analysis/add-new-checker/flow-package-not-exist.td
@@ -0,0 +1,39 @@
+// RUN: %check_add_new_checker %s %t a.b.B
+// RUN: %check_add_new_checker %s %t a.d.D
+
+include "CheckerBase.td"
+
+def A : Package<"a">;
+// CHECK-A-B-B:      def A : Package<"a">;
+// CHECK-A-B-B-NEXT: def BA : Package<"b">, ParentPackage<A>;
+// CHECK-A-B-B-NEXT: def CA : Package<"c">, ParentPackage<A>;
+def CA : Package<"c">, ParentPackage<A>;
+// CHECK-A-D-D:      def CA : Package<"c">, ParentPackage<A>;
+// CHECK-A-D-D-NEXT: def DA : Package<"d">, ParentPackage<A>;
+// CHECK-A-D-D-NEXT: def EA : Package<"e">, ParentPackage<A>;
+def EA : Package<"e">, ParentPackage<A>;
+
+//===----------------------------------------------------------------------===//
+
+let ParentPackage = CA in {
+def CChecker : Checker<"C">;
+} // end "a.c"
+
+let ParentPackage = EA in {
+def EChecker : Checker<"E">;
+} // end "a.e"
+// CHECK-A-B-B:       } // end "a.e"
+// CHECK-A-B-B-EMPTY:
+// CHECK-A-B-B-NEXT:  let ParentPackage = BA in {
+// CHECK-A-B-B-EMPTY:
+// CHECK-A-B-B-NEXT:  def BChecker : Checker<"B">,
+// CHECK-A-B-B-EMPTY:
+// CHECK-A-B-B-NEXT:  } // end "a.b"
+
+// CHECK-A-D-D:       } // end "a.e"
+// CHECK-A-D-D-EMPTY:
+// CHECK-A-D-D-NEXT:  let ParentPackage = DA in {
+// CHECK-A-D-D-EMPTY:
+// CHECK-A-D-D-NEXT:  def DChecker : Checker<"D">,
+// CHECK-A-D-D-EMPTY:
+// CHECK-A-D-D-NEXT:  } // end "a.d"
Index: clang/test/Analysis/add-new-checker/flow-package-not-exist.rst
===================================================================
--- /dev/null
+++ clang/test/Analysis/add-new-checker/flow-package-not-exist.rst
@@ -0,0 +1,28 @@
+.. RUN: %check_add_new_checker %s %t core.b.A
+
+.. _default-checkers:
+
+Default Checkers
+----------------
+
+.. _core-checkers:
+
+core
+^^^^
+Foo.
+
+.. _core-c-A:
+
+core.c.A (C)
+""""""""""""
+Bar.
+
+.. CHECK-CORE-B-A:       Bar
+.. CHECK-CORE-B-A-EMPTY:
+.. CHECK-CORE-B-A-NEXT:  core.b
+.. CHECK-CORE-B-A-NEXT:  ^^^^^^
+.. CHECK-CORE-B-A-NEXT:  FIXME: Document 'core.b'.
+.. CHECK-CORE-B-A-EMPTY:
+.. CHECK-CORE-B-A-NEXT:   _core-b-A:
+.. CHECK-CORE-B-A-EMPTY:
+.. CHECK-CORE-B-A-NEXT:  core.b.AChecker (C)
Index: clang/test/Analysis/add-new-checker/flow-package-exist-checker.td
===================================================================
--- /dev/null
+++ clang/test/Analysis/add-new-checker/flow-package-exist-checker.td
@@ -0,0 +1,31 @@
+// RUN: %check_add_new_checker %s %t a.A
+// RUN: %check_add_new_checker %s %t a.C
+// RUN: %check_add_new_checker %s %t a.E
+
+include "CheckerBase.td"
+
+def A : Package<"a">;
+
+let ParentPackage = A in {
+
+// CHECK-A-A:       let ParentPackage = A in {
+// CHECK-A-A-EMPTY:
+// CHECK-A-A-NEXT:    def AChecker : Checker<"A">,
+// CHECK-A-A-EMPTY:
+// CHECK-A-A-NEXT:    def BChecker : Checker<"B">;
+def BChecker : Checker<"B">;
+
+// CHECK-A-C:         def BChecker : Checker<"B">;
+// CHECK-A-C-EMPTY:
+// CHECK-A-C-NEXT:    def CChecker : Checker<"C">,
+// CHECK-A-C-EMPTY:
+// CHECK-A-C-NEXT:    def DChecker : Checker<"D">;
+def DChecker : Checker<"D">;
+
+// CHECK-A-E:         def DChecker : Checker<"D">;
+// CHECK-A-E-EMPTY:
+// CHECK-A-E:         def EChecker : Checker<"E">,
+// CHECK-A-E-EMPTY:
+// CHECK-A-E-NEXT:   } // end "a"
+} // end "a"
+
Index: clang/test/Analysis/add-new-checker/flow-package-exist-checker.rst
===================================================================
--- /dev/null
+++ clang/test/Analysis/add-new-checker/flow-package-exist-checker.rst
@@ -0,0 +1,44 @@
+.. RUN: %check_add_new_checker %s %t core.a.A
+.. RUN: %check_add_new_checker %s %t core.a.C
+.. RUN: %check_add_new_checker %s %t core.a.E
+
+.. _default-checkers:
+
+Default Checkers
+----------------
+
+.. _core-checkers:
+
+core
+^^^^
+Foo.
+
+.. CHECK-CORE-A-A:       .. _core-a-A:
+.. CHECK-CORE-A-A-EMPTY:
+.. CHECK-CORE-A-A-NEXT:  core.a.AChecker (C)
+.. CHECK-CORE-A-A-EMPTY:
+.. CHECK-CORE-A-A-NEXT:  .. _core-a-B:
+
+.. _core-a-B:
+
+core.a.B (C)
+""""""""""""
+Bar.
+
+.. CHECK-CORE-A-C:       .. _core-a-C:
+.. CHECK-CORE-A-C-EMPTY:
+.. CHECK-CORE-A-C-NEXT:  core.a.CChecker (C)
+.. CHECK-CORE-A-C-EMPTY:
+.. CHECK-CORE-A-C-NEXT:  .. _core-a-D:
+
+.. _core-a-D:
+
+core.a.D (C)
+""""""""""""
+Baz.
+
+.. CHECK-CORE-A-E:       Baz.
+.. CHECK-CORE-A-E-EMPTY:
+.. CHECK-CORE-A-E-NEXT:  .. _core-a-E:
+.. CHECK-CORE-A-E-EMPTY:
+.. CHECK-CORE-A-E-NEXT:  core.a.EChecker (C)
Index: clang/test/Analysis/add-new-checker/checker-name.td
===================================================================
--- /dev/null
+++ clang/test/Analysis/add-new-checker/checker-name.td
@@ -0,0 +1,19 @@
+// RUN: %check_add_new_checker %s %t a.Ab
+
+include "CheckerBase.td"
+
+// In ASCII 'b' < 'A', so that the checker names are compared in lowercase.
+
+def A : Package<"a">;
+
+let ParentPackage = A in {
+
+def AAChecker : Checker<"AA">;
+
+// CHECK-A-AB:         def AAChecker : Checker<"AA">;
+// CHECK-A-AB-EMPTY:
+// CHECK-A-AB-NEXT:    def AbChecker : Checker<"Ab">,
+// CHECK-A-AB-EMPTY:
+// CHECK-A-AB-NEXT:  } // end "a"
+} // end "a"
+
Index: clang/test/Analysis/add-new-checker/checker-name.rst
===================================================================
--- /dev/null
+++ clang/test/Analysis/add-new-checker/checker-name.rst
@@ -0,0 +1,27 @@
+.. RUN: %check_add_new_checker %s %t core.a.Ab
+
+.. In ASCII 'b' < 'A', so that the checker names are compared in lowercase.
+
+.. _default-checkers:
+
+Default Checkers
+----------------
+
+.. _core-checkers:
+
+core
+^^^^
+Foo.
+
+.. _core-a-AA:
+
+core.a.AA (C)
+""""""""""""
+Bar.
+
+.. CHECK-CORE-A-AB:       Bar.
+.. CHECK-CORE-A-AB-EMPTY:
+.. CHECK-CORE-A-AB-NEXT:  .. _core-a-Ab:
+.. CHECK-CORE-A-AB-EMPTY:
+.. CHECK-CORE-A-AB-NEXT:  core.a.AbChecker (C)
+.. CHECK-CORE-A-AB-EMPTY:
Index: clang/test/Analysis/add-new-checker/check-add-new-checker.py
===================================================================
--- /dev/null
+++ clang/test/Analysis/add-new-checker/check-add-new-checker.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+#
+#===- check-add-new-checker.py - Static Analyzer test helper --*- python -*-===#
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+#
+#===------------------------------------------------------------------------===#
+#
+# Example usage: // RUN: %check_add_new_checker %s %t alpha.package.Foo
+#
+#===------------------------------------------------------------------------===#
+
+
+import argparse
+import os
+import re
+import subprocess
+
+
+def run_test(args, extra_args):
+    input_file_path = args.input_file_path
+    temp_file_path = args.temp_file_path
+
+    lines = []
+    with open(input_file_path, 'r') as in_stream:
+        lines = in_stream.readlines()
+
+    # Remove every 'CHECK' lines to make sure the test does not match on itself.
+    check_comment_re = re.compile('^..\sCHECK-')
+    addition = []
+    for line in lines:
+        if not check_comment_re.match(line):
+            addition.append(line)
+
+    # Dump out the test file: one is modifiable, one is the original.
+    with open(temp_file_path, 'w') as out_stream:
+        out_stream.writelines(addition)
+
+    original_file_path = temp_file_path + ".orig"
+    with open(original_file_path, 'w') as out_stream:
+        out_stream.writelines(addition)
+
+    clang_path = os.path.abspath(__file__)
+    clang_path = clang_path[:clang_path.find('clang') + 5]
+    add_new_checker_path = os.path.join(
+        clang_path, 'utils', 'analyzer', 'add-new-checker.py')
+
+    add_new_checker = (['python', add_new_checker_path, '--test',
+                        '--test-path', temp_file_path] + extra_args)
+    try:
+        add_new_checker_output = \
+            subprocess.check_output(add_new_checker, stderr=subprocess.STDOUT)
+    except subprocess.CalledProcessError as e:
+        print('Adding a new checker failed:\n' + e.output.decode())
+        raise
+
+    try:
+        subprocess.check_output(
+            ['diff', '-u', original_file_path, temp_file_path],
+            stderr=subprocess.STDOUT)
+    except subprocess.CalledProcessError as e:
+        print('Diff:\n' + e.output.decode())
+
+    # Build the 'CHECK' prefix based on the package-chain and the checker name.
+    prefixes = ''
+    for package_or_checker in extra_args[0].split('.'):
+        prefixes += package_or_checker.upper() + '-'
+    prefixes = prefixes[:-1]
+
+    try:
+        subprocess.check_output(
+            ['FileCheck', '-input-file=' + temp_file_path, input_file_path,
+             '-check-prefixes=CHECK-' + prefixes, '-strict-whitespace'],
+            stderr=subprocess.STDOUT)
+    except subprocess.CalledProcessError as e:
+        print('FileCheck failed:\n' + e.output.decode())
+        print(add_new_checker_output) # Dump the script output as well.
+        raise
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('input_file_path')
+    parser.add_argument('temp_file_path')
+
+    args, extra_args = parser.parse_known_args()
+    run_test(args, extra_args)
+
+
+if __name__ == '__main__':
+    main()
Index: clang/test/Analysis/add-new-checker/add-multiple-packages.td
===================================================================
--- /dev/null
+++ clang/test/Analysis/add-new-checker/add-multiple-packages.td
@@ -0,0 +1,26 @@
+// RUN: %check_add_new_checker %s %t a.b.c.A
+
+include "CheckerBase.td"
+
+def A : Package<"a">;
+def B : Package<"b">;
+// CHECK-A-B-C-A:      def A : Package<"a">;
+// CHECK-A-B-C-A-NEXT: def B : Package<"b">;
+// CHECK-A-B-C-A-NEXT: def BA : Package<"b">, ParentPackage<A>;
+// CHECK-A-B-C-A-NEXT: def CBA : Package<"c">, ParentPackage<BA>;
+
+let ParentPackage = A in {
+def AChecker : Checker<"A">;
+} // end "a"
+
+let ParentPackage = B in {
+def BChecker : Checker<"B">;
+} // end "b"
+
+// CHECK-A-B-C-A:       } // end "b"
+// CHECK-A-B-C-A-EMPTY:
+// CHECK-A-B-C-A-NEXT:  let ParentPackage = CBA in {
+// CHECK-A-B-C-A-EMPTY:
+// CHECK-A-B-C-A-NEXT:  def AChecker : Checker<"A">,
+// CHECK-A-B-C-A-EMPTY:
+// CHECK-A-B-C-A-NEXT:  } // end "a.b.c"
Index: clang/test/Analysis/add-new-checker/add-multiple-packages.rst
===================================================================
--- /dev/null
+++ clang/test/Analysis/add-new-checker/add-multiple-packages.rst
@@ -0,0 +1,24 @@
+.. RUN: %check_add_new_checker %s %t core.a.b.c.A
+
+.. _default-checkers:
+
+Default Checkers
+----------------
+
+.. _core-checkers:
+
+core
+^^^^
+Foo.
+
+.. _core-a-A:
+
+core.a.A (C)
+""""""""""""
+Bar.
+
+.. CHECK-CORE-A-B-C-A:       Bar.
+.. CHECK-CORE-A-B-C-A-EMPTY:
+.. CHECK-CORE-A-B-C-A-NEXT:  .. _core-a-b-c-A:
+.. CHECK-CORE-A-B-C-A-EMPTY:
+.. CHECK-CORE-A-B-C-A-NEXT:  core.a.b.c.AChecker (C)
Index: clang/test/Analysis/add-new-checker/add-main-package.td
===================================================================
--- /dev/null
+++ clang/test/Analysis/add-new-checker/add-main-package.td
@@ -0,0 +1,20 @@
+// RUN: %check_add_new_checker %s %t a.A
+
+include "CheckerBase.td"
+
+def Z : Package<"z">;
+
+// CHECK-A-A:      def Z : Package<"z">;
+// CHECK-A-A-NEXT: def A : Package<"a">;
+
+let ParentPackage = Z in {
+def ZChecker : Checker<"Z">;
+} // end "z"
+
+// CHECK-A-A:       } // end "z"
+// CHECK-A-A-EMPTY:
+// CHECK-A-A-NEXT:  let ParentPackage = A in {
+// CHECK-A-A-EMPTY:
+// CHECK-A-A-NEXT:  def AChecker : Checker<"A">,
+// CHECK-A-A-EMPTY:
+// CHECK-A-A-NEXT:  } // end "a"
Index: clang/test/Analysis/add-new-checker/add-main-package.rst
===================================================================
--- /dev/null
+++ clang/test/Analysis/add-new-checker/add-main-package.rst
@@ -0,0 +1,52 @@
+.. RUN: %check_add_new_checker %s %t b.A
+.. RUN: %check_add_new_checker %s %t alpha.b.A
+
+.. _default-checkers:
+
+Default Checkers
+----------------
+
+.. _core-checkers:
+
+core
+^^^^
+Foo.
+
+.. _core-z:
+
+core.z.Z (C)
+""""""""""""
+Bar.
+
+.. CHECK-B-A:       Bar.
+.. CHECK-B-A-EMPTY:
+.. CHECK-B-A-NEXT:  b
+.. CHECK-B-A-NEXT:  ^
+.. CHECK-B-A-NEXT:  FIXME: Document 'b'.
+.. CHECK-B-A-EMPTY:
+.. CHECK-B-A-NEXT:  .. _b-A:
+.. CHECK-B-A-EMPTY:
+.. CHECK-B-A-NEXT:  b.AChecker (C)
+.. CHECK-B-A-EMPTY:
+.. CHECK-B-A-NEXT:  .. _alpha-checkers:
+.. _alpha-checkers:
+
+Experimental Checkers
+---------------------
+Baz.
+
+.. _alpha-z:
+
+alpha.z.Z (C)
+"""""""""""""
+Qux.
+
+.. CHECK-ALPHA-B-A:       Qux.
+.. CHECK-ALPHA-B-A-EMPTY:
+.. CHECK-ALPHA-B-A-NEXT:  alpha.b
+.. CHECK-ALPHA-B-A-NEXT:  ^^^^^^^
+.. CHECK-ALPHA-B-A-NEXT:  FIXME: Document 'alpha.b'.
+.. CHECK-ALPHA-B-A-EMPTY:
+.. CHECK-ALPHA-B-A-NEXT:  .. _alpha-b-A:
+.. CHECK-ALPHA-B-A-EMPTY:
+.. CHECK-ALPHA-B-A-NEXT:  alpha.b.AChecker (C)
Index: clang/lib/StaticAnalyzer/Checkers/ExampleChecker.cpp
===================================================================
--- /dev/null
+++ clang/lib/StaticAnalyzer/Checkers/ExampleChecker.cpp
@@ -0,0 +1,101 @@
+//===- ExampleChecker - Copy-pasted by 'add-new-checker.py' -----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// FIXME: Document 'ExampleChecker'.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+class ExampleChecker : public Checker<check::PostCall> {
+public:
+  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+
+private:
+  BugType BT{this, "Insufficiently awesome function call",
+             categories::LogicError};
+  // Example call description. Note: for multiple ones use CallDescriptionMap.
+  CallDescription MarkNotAwesome{{"std", "mark_not_awesome"}, 1};
+};
+} // namespace
+
+// Example state trait. Note: It is usual to map values to memory regions.
+REGISTER_MAP_WITH_PROGRAMSTATE(AwesomeMap, const MemRegion *, bool)
+
+// Example subscription. Here we are interested in already called functions.
+void ExampleChecker::checkPostCall(const CallEvent &Call,
+                                   CheckerContext &C) const {
+  ProgramStateRef State = C.getState();
+
+  // Check if the origin of 'Call' is not awesome, if so emit a bug report.
+  // 'dyn_cast_or_null' (or cast_or_null) on nullable.
+  if (const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr())) {
+    if (const auto *FD = dyn_cast<FunctionDecl>(CE->getCalleeDecl())) {
+      // SVal is a simple value type. Note: Values transcends time and space,
+      // therefore we could ask the Analyzer engine to give a name for any
+      // kind of value using the SValBuilder.
+      SVal FunctionV = C.getSValBuilder().getFunctionPointer(FD);
+      const MemRegion *MR = FunctionV.getAsRegion();
+      // State traits returns 'const *'
+      if (const bool *Entry = State->get<AwesomeMap>(MR)) {
+        bool IsNotAwesome = *Entry;
+        if (!IsNotAwesome)
+          return;
+
+        // Usual out-stream boilerplate.
+        SmallString<128> Msg;
+        llvm::raw_svector_ostream Out(Msg);
+
+        // 'cast' on known types.
+        Out << "There is a path where '" << cast<NamedDecl>(FD)->getName()
+            << "' is insufficiently awesome";
+
+        // Usual bug-reporting boilerplate.
+        auto Report = std::make_unique<PathSensitiveBugReport>(
+            BT, Out.str(), C.generateErrorNode());
+        C.emitReport(std::move(Report));
+      }
+    }
+  }
+
+  // Check if 'Call' is 'mark_not_awesome()', if so emit a helper report.
+  // Note: These reports helps the user to understand how a bug could happen.
+  if (Call.isCalled(MarkNotAwesome)) {
+    const MemRegion *MR = Call.getArgSVal(0).getAsRegion();
+    State = State->set<AwesomeMap>(MR, true);
+
+    // NoteTags are emitted on the fly during analysis with this callback.
+    // Here we emit a note on the event when the function became not awesome.
+    // Note: for reverse-engineering such notes use BugReporterVisitor.
+    const NoteTag *Tag = C.getNoteTag([=]() -> std::string {
+      // Usual boilerplate for cheap string concatenation.
+      return (Twine("Function '") +
+              cast<FunctionCodeRegion>(MR)->getDecl()->getName() +
+              "' marked as not awesome")
+          .str();
+    });
+
+    // Emit the NoteTag.
+    C.addTransition(State, Tag);
+  }
+}
+
+void ento::registerExampleChecker(CheckerManager &Mgr) {
+  Mgr.registerChecker<ExampleChecker>();
+}
+
+bool ento::shouldRegisterExampleChecker(const LangOptions &) { return true; }
Index: clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -39,6 +39,7 @@
   DynamicTypePropagation.cpp
   DynamicTypeChecker.cpp
   EnumCastOutOfRangeChecker.cpp
+  ExampleChecker.cpp
   ExprInspectionChecker.cpp
   FixedAddressChecker.cpp
   FuchsiaHandleChecker.cpp
Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
===================================================================
--- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -21,7 +21,7 @@
 
 def Core : Package<"core">;
 def CoreBuiltin : Package<"builtin">, ParentPackage<Core>, Hidden;
-def CoreUninitialized  : Package<"uninitialized">, ParentPackage<Core>;
+def CoreUninitialized : Package<"uninitialized">, ParentPackage<Core>;
 def CoreAlpha : Package<"core">, ParentPackage<Alpha>;
 
 // The OptIn package is for checkers that are not alpha and that would normally
@@ -1501,3 +1501,15 @@
 
 } // end fuchsia
 
+//===----------------------------------------------------------------------===//
+// Pure alpha checkers.
+//===----------------------------------------------------------------------===//
+
+let ParentPackage = Alpha in {
+
+def ExampleChecker : Checker<"Example">,
+  HelpText<"FIXME">,
+  Documentation<NotDocumented>,
+  Hidden;
+
+} // end "alpha"
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to