xref: /netbsd-src/external/apache2/llvm/dist/clang/utils/clangdiag.py (revision e038c9c4676b0f19b1b7dd08a940c6ed64a6d5ae)
1*e038c9c4Sjoerg#!/usr/bin/env python
27330f729Sjoerg
37330f729Sjoerg#----------------------------------------------------------------------
47330f729Sjoerg# Be sure to add the python path that points to the LLDB shared library.
57330f729Sjoerg#
67330f729Sjoerg# # To use this in the embedded python interpreter using "lldb" just
77330f729Sjoerg# import it with the full path using the "command script import"
87330f729Sjoerg# command
97330f729Sjoerg#   (lldb) command script import /path/to/clandiag.py
107330f729Sjoerg#----------------------------------------------------------------------
117330f729Sjoerg
127330f729Sjoergfrom __future__ import absolute_import, division, print_function
137330f729Sjoergimport lldb
147330f729Sjoergimport argparse
157330f729Sjoergimport shlex
167330f729Sjoergimport os
177330f729Sjoergimport re
187330f729Sjoergimport subprocess
197330f729Sjoerg
207330f729Sjoergclass MyParser(argparse.ArgumentParser):
217330f729Sjoerg    def format_help(self):
227330f729Sjoerg        return '''     Commands for managing clang diagnostic breakpoints
237330f729Sjoerg
247330f729SjoergSyntax: clangdiag enable [<warning>|<diag-name>]
257330f729Sjoerg        clangdiag disable
267330f729Sjoerg        clangdiag diagtool [<path>|reset]
277330f729Sjoerg
287330f729SjoergThe following subcommands are supported:
297330f729Sjoerg
307330f729Sjoerg      enable   -- Enable clang diagnostic breakpoints.
317330f729Sjoerg      disable  -- Disable all clang diagnostic breakpoints.
327330f729Sjoerg      diagtool -- Return, set, or reset diagtool path.
337330f729Sjoerg
347330f729SjoergThis command sets breakpoints in clang, and clang based tools, that
357330f729Sjoergemit diagnostics.  When a diagnostic is emitted, and clangdiag is
367330f729Sjoergenabled, it will use the appropriate diagtool application to determine
377330f729Sjoergthe name of the DiagID, and set breakpoints in all locations that
387330f729Sjoerg'diag::name' appears in the source.  Since the new breakpoints are set
397330f729Sjoergafter they are encountered, users will need to launch the executable a
407330f729Sjoergsecond time in order to hit the new breakpoints.
417330f729Sjoerg
427330f729SjoergFor in-tree builds, the diagtool application, used to map DiagID's to
437330f729Sjoergnames, is found automatically in the same directory as the target
447330f729Sjoergexecutable.  However, out-or-tree builds must use the 'diagtool'
457330f729Sjoergsubcommand to set the appropriate path for diagtool in the clang debug
467330f729Sjoergbin directory.  Since this mapping is created at build-time, it's
477330f729Sjoergimportant for users to use the same version that was generated when
487330f729Sjoergclang was compiled, or else the id's won't match.
497330f729Sjoerg
507330f729SjoergNotes:
517330f729Sjoerg- Substrings can be passed for both <warning> and <diag-name>.
527330f729Sjoerg- If <warning> is passed, only enable the DiagID(s) for that warning.
537330f729Sjoerg- If <diag-name> is passed, only enable that DiagID.
547330f729Sjoerg- Rerunning enable clears existing breakpoints.
557330f729Sjoerg- diagtool is used in breakpoint callbacks, so it can be changed
567330f729Sjoerg  without the need to rerun enable.
577330f729Sjoerg- Adding this to your ~.lldbinit file makes clangdiag available at startup:
587330f729Sjoerg  "command script import /path/to/clangdiag.py"
597330f729Sjoerg
607330f729Sjoerg'''
617330f729Sjoerg
627330f729Sjoergdef create_diag_options():
637330f729Sjoerg    parser = MyParser(prog='clangdiag')
647330f729Sjoerg    subparsers = parser.add_subparsers(
657330f729Sjoerg        title='subcommands',
667330f729Sjoerg        dest='subcommands',
677330f729Sjoerg        metavar='')
687330f729Sjoerg    disable_parser = subparsers.add_parser('disable')
697330f729Sjoerg    enable_parser = subparsers.add_parser('enable')
707330f729Sjoerg    enable_parser.add_argument('id', nargs='?')
717330f729Sjoerg    diagtool_parser = subparsers.add_parser('diagtool')
727330f729Sjoerg    diagtool_parser.add_argument('path', nargs='?')
737330f729Sjoerg    return parser
747330f729Sjoerg
757330f729Sjoergdef getDiagtool(target, diagtool = None):
767330f729Sjoerg    id = target.GetProcess().GetProcessID()
777330f729Sjoerg    if 'diagtool' not in getDiagtool.__dict__:
787330f729Sjoerg        getDiagtool.diagtool = {}
797330f729Sjoerg    if diagtool:
807330f729Sjoerg        if diagtool == 'reset':
817330f729Sjoerg            getDiagtool.diagtool[id] = None
827330f729Sjoerg        elif os.path.exists(diagtool):
837330f729Sjoerg            getDiagtool.diagtool[id] = diagtool
847330f729Sjoerg        else:
857330f729Sjoerg            print('clangdiag: %s not found.' % diagtool)
867330f729Sjoerg    if not id in getDiagtool.diagtool or not getDiagtool.diagtool[id]:
877330f729Sjoerg        getDiagtool.diagtool[id] = None
887330f729Sjoerg        exe = target.GetExecutable()
897330f729Sjoerg        if not exe.Exists():
907330f729Sjoerg            print('clangdiag: Target (%s) not set.' % exe.GetFilename())
917330f729Sjoerg        else:
927330f729Sjoerg            diagtool = os.path.join(exe.GetDirectory(), 'diagtool')
937330f729Sjoerg            if os.path.exists(diagtool):
947330f729Sjoerg                getDiagtool.diagtool[id] = diagtool
957330f729Sjoerg            else:
967330f729Sjoerg                print('clangdiag: diagtool not found along side %s' % exe)
977330f729Sjoerg
987330f729Sjoerg    return getDiagtool.diagtool[id]
997330f729Sjoerg
1007330f729Sjoergdef setDiagBreakpoint(frame, bp_loc, dict):
1017330f729Sjoerg    id = frame.FindVariable("DiagID").GetValue()
1027330f729Sjoerg    if id is None:
1037330f729Sjoerg        print('clangdiag: id is None')
1047330f729Sjoerg        return False
1057330f729Sjoerg
1067330f729Sjoerg    # Don't need to test this time, since we did that in enable.
1077330f729Sjoerg    target = frame.GetThread().GetProcess().GetTarget()
1087330f729Sjoerg    diagtool = getDiagtool(target)
1097330f729Sjoerg    name = subprocess.check_output([diagtool, "find-diagnostic-id", id]).rstrip();
1107330f729Sjoerg    # Make sure we only consider errors, warnings, and extensions.
1117330f729Sjoerg    # FIXME: Make this configurable?
1127330f729Sjoerg    prefixes = ['err_', 'warn_', 'exp_']
1137330f729Sjoerg    if len([prefix for prefix in prefixes+[''] if name.startswith(prefix)][0]):
1147330f729Sjoerg        bp = target.BreakpointCreateBySourceRegex(name, lldb.SBFileSpec())
1157330f729Sjoerg        bp.AddName("clang::Diagnostic")
1167330f729Sjoerg
1177330f729Sjoerg    return False
1187330f729Sjoerg
1197330f729Sjoergdef enable(exe_ctx, args):
1207330f729Sjoerg    # Always disable existing breakpoints
1217330f729Sjoerg    disable(exe_ctx)
1227330f729Sjoerg
1237330f729Sjoerg    target = exe_ctx.GetTarget()
1247330f729Sjoerg    numOfBreakpoints = target.GetNumBreakpoints()
1257330f729Sjoerg
1267330f729Sjoerg    if args.id:
1277330f729Sjoerg        # Make sure we only consider errors, warnings, and extensions.
1287330f729Sjoerg        # FIXME: Make this configurable?
1297330f729Sjoerg        prefixes = ['err_', 'warn_', 'exp_']
1307330f729Sjoerg        if len([prefix for prefix in prefixes+[''] if args.id.startswith(prefix)][0]):
1317330f729Sjoerg            bp = target.BreakpointCreateBySourceRegex(args.id, lldb.SBFileSpec())
1327330f729Sjoerg            bp.AddName("clang::Diagnostic")
1337330f729Sjoerg        else:
1347330f729Sjoerg            diagtool = getDiagtool(target)
1357330f729Sjoerg            list = subprocess.check_output([diagtool, "list-warnings"]).rstrip();
1367330f729Sjoerg            for line in list.splitlines(True):
1377330f729Sjoerg                m = re.search(r' *(.*) .*\[\-W' + re.escape(args.id) + r'.*].*', line)
1387330f729Sjoerg                # Make sure we only consider warnings.
1397330f729Sjoerg                if m and m.group(1).startswith('warn_'):
1407330f729Sjoerg                    bp = target.BreakpointCreateBySourceRegex(m.group(1), lldb.SBFileSpec())
1417330f729Sjoerg                    bp.AddName("clang::Diagnostic")
1427330f729Sjoerg    else:
1437330f729Sjoerg        print('Adding callbacks.')
1447330f729Sjoerg        bp = target.BreakpointCreateByName('DiagnosticsEngine::Report')
1457330f729Sjoerg        bp.SetScriptCallbackFunction('clangdiag.setDiagBreakpoint')
1467330f729Sjoerg        bp.AddName("clang::Diagnostic")
1477330f729Sjoerg
1487330f729Sjoerg    count = target.GetNumBreakpoints() - numOfBreakpoints
1497330f729Sjoerg    print('%i breakpoint%s added.' % (count, "s"[count==1:]))
1507330f729Sjoerg
1517330f729Sjoerg    return
1527330f729Sjoerg
1537330f729Sjoergdef disable(exe_ctx):
1547330f729Sjoerg    target = exe_ctx.GetTarget()
1557330f729Sjoerg    # Remove all diag breakpoints.
1567330f729Sjoerg    bkpts = lldb.SBBreakpointList(target)
1577330f729Sjoerg    target.FindBreakpointsByName("clang::Diagnostic", bkpts)
1587330f729Sjoerg    for i in range(bkpts.GetSize()):
1597330f729Sjoerg        target.BreakpointDelete(bkpts.GetBreakpointAtIndex(i).GetID())
1607330f729Sjoerg
1617330f729Sjoerg    return
1627330f729Sjoerg
1637330f729Sjoergdef the_diag_command(debugger, command, exe_ctx, result, dict):
1647330f729Sjoerg    # Use the Shell Lexer to properly parse up command options just like a
1657330f729Sjoerg    # shell would
1667330f729Sjoerg    command_args = shlex.split(command)
1677330f729Sjoerg    parser = create_diag_options()
1687330f729Sjoerg    try:
1697330f729Sjoerg        args = parser.parse_args(command_args)
1707330f729Sjoerg    except:
1717330f729Sjoerg        return
1727330f729Sjoerg
1737330f729Sjoerg    if args.subcommands == 'enable':
1747330f729Sjoerg        enable(exe_ctx, args)
1757330f729Sjoerg    elif args.subcommands == 'disable':
1767330f729Sjoerg        disable(exe_ctx)
1777330f729Sjoerg    else:
1787330f729Sjoerg        diagtool = getDiagtool(exe_ctx.GetTarget(), args.path)
1797330f729Sjoerg        print('diagtool = %s' % diagtool)
1807330f729Sjoerg
1817330f729Sjoerg    return
1827330f729Sjoerg
1837330f729Sjoergdef __lldb_init_module(debugger, dict):
1847330f729Sjoerg    # This initializer is being run from LLDB in the embedded command interpreter
1857330f729Sjoerg    # Make the options so we can generate the help text for the new LLDB
1867330f729Sjoerg    # command line command prior to registering it with LLDB below
1877330f729Sjoerg    parser = create_diag_options()
1887330f729Sjoerg    the_diag_command.__doc__ = parser.format_help()
1897330f729Sjoerg    # Add any commands contained in this module to LLDB
1907330f729Sjoerg    debugger.HandleCommand(
1917330f729Sjoerg        'command script add -f clangdiag.the_diag_command clangdiag')
1927330f729Sjoerg    print('The "clangdiag" command has been installed, type "help clangdiag" or "clangdiag --help" for detailed help.')
193